com.microsoft.tfs.client.clc.Application.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.clc.Application.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.clc;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.microsoft.tfs.client.clc.commands.Command;
import com.microsoft.tfs.client.clc.exceptions.ArgumentException;
import com.microsoft.tfs.client.clc.exceptions.CLCException;
import com.microsoft.tfs.client.clc.exceptions.InvalidOptionValueException;
import com.microsoft.tfs.client.clc.exceptions.LicenseException;
import com.microsoft.tfs.client.clc.exceptions.LicenseException.LicenseExceptionType;
import com.microsoft.tfs.client.clc.exceptions.UnknownCommandException;
import com.microsoft.tfs.client.clc.exceptions.UnknownOptionException;
import com.microsoft.tfs.client.clc.options.Option;
import com.microsoft.tfs.client.clc.options.shared.OptionContinueOnError;
import com.microsoft.tfs.client.clc.options.shared.OptionExitCode;
import com.microsoft.tfs.client.clc.options.shared.OptionHelp;
import com.microsoft.tfs.client.clc.options.shared.OptionOutputSeparator;
import com.microsoft.tfs.client.clc.telemetry.CLCTelemetryHelper;
import com.microsoft.tfs.console.application.AbstractConsoleApplication;
import com.microsoft.tfs.console.display.ConsoleDisplay;
import com.microsoft.tfs.console.display.Display;
import com.microsoft.tfs.console.input.ConsoleInput;
import com.microsoft.tfs.console.input.Input;
import com.microsoft.tfs.core.exceptions.InputValidationException;
import com.microsoft.tfs.core.exceptions.TECoreException;
import com.microsoft.tfs.core.exceptions.TFSFederatedAuthException;
import com.microsoft.tfs.core.httpclient.auth.AuthenticationSecurityException;
import com.microsoft.tfs.core.product.ProductInformation;
import com.microsoft.tfs.core.product.ProductName;
import com.microsoft.tfs.core.ws.runtime.exceptions.ProxyException;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.StringUtil;
import com.microsoft.tfs.util.tasks.CanceledException;

/**
 * The entry point for the command-line client. Instances of this class should
 * not be used by multiple threads.
 */
public abstract class Application implements AbstractConsoleApplication {
    private static final Log log = LogFactory.getLog(Application.class);

    public static final String VENDOR_NAME = Messages.getString("Application.VendorName"); //$NON-NLS-1$

    private Display display = new ConsoleDisplay(false);
    private Input input = new ConsoleInput();

    private OptionsMap optionsMap;
    private CommandsMap commandsMap;

    /**
     * Called by the base Application class to create an OptionsMap. This method
     * will be called once and the result will be cached by the base Application
     * class.
     *
     * @return the OptionsMap specific for this client
     */
    protected abstract OptionsMap createOptionsMap();

    /**
     * Called by the base Application class to create a CommandsMap. This method
     * will be called once and the result will be cached by the base Application
     * class.
     *
     * @return the CommandsMap specific for this client
     */
    protected abstract CommandsMap createCommandsMap();

    /*
     * (non-Javadoc)
     *
     * @see com.microsoft.tfs.console.application.AbstractConsoleApplication#
     * getDisplay ()
     */
    @Override
    public Display getDisplay() {
        return display;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.microsoft.tfs.console.application.AbstractConsoleApplication#
     * setDisplay (com.microsoft.tfs.console.Display)
     */
    @Override
    public void setDisplay(final Display display) {
        Check.notNull(display, "display"); //$NON-NLS-1$
        this.display = display;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.microsoft.tfs.console.application.AbstractConsoleApplication#getInput
     * ()
     */
    @Override
    public Input getInput() {
        return input;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.microsoft.tfs.console.application.AbstractConsoleApplication#setInput
     * (com.microsoft.tfs.console.input.Input)
     */
    @Override
    public void setInput(final Input input) {
        Check.notNull(input, "input"); //$NON-NLS-1$
        this.input = input;
    }

    /**
     * Does almost all of the work of the command-line client. This method is
     * invoked from the static main() method, and also recursively when a
     * command file is encountered.
     *
     *
     * @param args
     *        the command-line arguments as passed into the process by the Java
     *        virtual machine (or in that style, but parsed from a command
     *        file).
     * @return the status code to exit the process with.
     */
    @Override
    public int run(final String[] args) {
        /*
         * Set up the running product configuration.
         */
        ProductInformation.initialize(ProductName.CLC);

        // We need to check for the no telemetry flag before we send any
        // telemetry to the server
        CLCTelemetryHelper.checkNoTelemetryProperty();

        CLCTelemetryHelper.sendSessionBegins();
        final int ret = run(args, false);
        CLCTelemetryHelper.sendSessionEnds();

        return ret;
    }

    /**
     * Does almost all of the work of the command-line client. This method is
     * invoked from the static main() method, and also recursively when a
     * command file is encountered.
     *
     * @param args
     *        the command-line arguments as passed into the process by the Java
     *        virtual machine (or in that style, but parsed from a command
     *        file).
     * @param recursiveCall
     *        true if this method was called recursively from itself, false
     *        otherwise.
     * @return the status code to exit the process with.
     */
    private int run(final String[] args, final boolean recursiveCall) {
        log.debug("Entering CLC application"); //$NON-NLS-1$
        log.debug("Command line: "); //$NON-NLS-1$
        for (int i = 0; i < args.length; i++) {
            final int p = args[i].toLowerCase().indexOf("login:"); //$NON-NLS-1$
            if (p < 0) {
                log.debug("     args[" + i + "]: " + args[i]); //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                log.debug("     args[" + i + "]: " + args[i].substring(0, p + 6) + "*******"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            }

        }
        /*
         * create and cache the options map and commands map we also set the
         * maps on the static Help class so the Help class always has access to
         * them
         */
        optionsMap = createOptionsMap();
        commandsMap = createCommandsMap();
        Help.init(commandsMap, optionsMap);

        Command c = null;
        int ret = ExitCode.UNKNOWN;
        boolean printExitCode = false;

        try {
            final String[] tokens = args.clone();

            log.debug("Parse and prepare arguments."); //$NON-NLS-1$
            /*
             * Check to see if the first argument is a file containing commands.
             * Don't allow this if we're being called recursively.
             */
            if (recursiveCall == false && tokens.length > 0 && tokens[0].startsWith("@")) //$NON-NLS-1$
            {
                /*
                 * Search all the arguments for the "continue on error" and
                 * output separator options.
                 */
                boolean continueOnError = false;
                String outputSeparator = null;
                for (int i = 0; i < tokens.length; i++) {
                    if (tokens[i] == null || tokens[i].length() == 0) {
                        continue;
                    }

                    try {
                        final Option o = optionsMap.findOption(tokens[i]);

                        if (o instanceof OptionContinueOnError) {
                            continueOnError = true;
                        } else if (o instanceof OptionOutputSeparator) {
                            outputSeparator = ((OptionOutputSeparator) o).getValue();
                        }
                    } catch (final Exception e) {
                        // Ignore.
                    }
                }

                try {
                    ret = runCommandFile(tokens, continueOnError, outputSeparator);
                } catch (final FileNotFoundException e) {
                    final String messageFormat = Messages.getString("Application.CommandFileCoundNotBeFoundFormat"); //$NON-NLS-1$
                    final String message = MessageFormat.format(messageFormat, tokens[0].substring(1));
                    display.printErrorLine(message);
                } catch (final IOException e) {
                    final String messageFormat = Messages
                            .getString("Application.ErrorReadingFromCommandFileFormat"); //$NON-NLS-1$
                    final String message = MessageFormat.format(messageFormat, tokens[0].substring(1),
                            e.getLocalizedMessage());
                    log.warn(message, e);
                    display.printErrorLine(message);
                }

                return ret;
            }

            final ArrayList<Option> options = new ArrayList<Option>();
            final ArrayList<String> freeArguments = new ArrayList<String>();

            /*
             * Parse all the args into a command, its options, and free
             * arguments.
             */

            final AtomicReference<Exception> outException = new AtomicReference<Exception>();
            c = parseTokens(args, options, freeArguments, outException);

            /*
             * Set the display on the command as soon as possible, so it can
             * write errors/messages.
             */
            if (c != null) {
                c.setInput(input);
                c.setDisplay(display);
            }

            /*
             * Search for the help option anywhere in the command line.
             * Microsoft's client does this for user convenience. Also look for
             * the exit code option while we're searching.
             */
            boolean foundHelpOption = false;
            for (int i = 0; i < options.size(); i++) {
                if (options.get(i) instanceof OptionHelp) {
                    foundHelpOption = true;
                }

                if (options.get(i) instanceof OptionExitCode) {
                    printExitCode = true;
                }
            }

            final boolean invalidCommandArguments = outException.get() != null;

            if (tokens.length == 0 || c == null || foundHelpOption || invalidCommandArguments) {
                if (invalidCommandArguments) {
                    final String messageFormat = Messages.getString("Application.AnArgumentErrorOccurredFormat"); //$NON-NLS-1$
                    final String message = MessageFormat.format(messageFormat,
                            outException.get().getLocalizedMessage());
                    display.printErrorLine(message);
                }

                Help.show(c, display);

                return invalidCommandArguments ? ExitCode.FAILURE : ExitCode.SUCCESS;
            }

            c.setOptions(options.toArray(new Option[0]), commandsMap.getGlobalOptions());
            c.setFreeArguments(freeArguments.toArray(new String[0]));

            log.debug("Execute the command implementation."); //$NON-NLS-1$

            c.run();

            log.debug("Close the command: Flush any remaining notifications and remove the manager"); //$NON-NLS-1$
            c.close();

            ret = c.getExitCode();
        } catch (final CanceledException e) {
            getDisplay().printErrorLine(""); //$NON-NLS-1$
            getDisplay().printErrorLine(Messages.getString("Application.CommandCanceled")); //$NON-NLS-1$

            ret = ExitCode.FAILURE;
        } catch (final InputValidationException e) {
            final String messageFormat = Messages.getString("Application.AnInputValidationErrorOccurredFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            display.printErrorLine(message);

            ret = ExitCode.FAILURE;
        } catch (final ArgumentException e) {
            /*
             * Argument exceptions happen when the user supplies an incorrect
             * command, misspelled options, the wrong option values, is missing
             * an option, or other similar error. We should show the help to the
             * user in this case.
             */
            final String messageFormat = Messages.getString("Application.AnArgumentErrorOccurredFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            display.printErrorLine(message);

            Help.show(c, display);

            ret = ExitCode.FAILURE;
        } catch (final IllegalArgumentException e) {
            /*
             * Same as above but returned by some low level Core or Common
             * classes, e.g. HttpHost.
             */
            CLCTelemetryHelper.sendException(e);

            final String messageFormat = Messages.getString("Application.AnArgumentErrorOccurredFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            display.printErrorLine(message);

            ret = ExitCode.FAILURE;
        } catch (final CLCException e) {
            /*
             * CLCExceptions have messages that are meaningful to users.
             */
            final String messageFormat = Messages.getString("Application.AClientErrorOccurredFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            log.error(message, e);
            display.printErrorLine(message);

            ret = ExitCode.FAILURE;
        } catch (final MalformedURLException e) {
            final String messageFormat = Messages.getString("Application.StringCouldNotBeConvertedToURLFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            log.info(message, e);
            display.printErrorLine(message);
            ret = ExitCode.FAILURE;
        } catch (final LicenseException e) {
            final String messageFormat = Messages.getString("Application.LicenseErrorFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            log.error(message, e);
            display.printErrorLine(message);

            if (e.getType() == LicenseExceptionType.EULA) {
                display.printErrorLine(Messages.getString("Application.RunTfEulaToAccept")); //$NON-NLS-1$
            } else {
                display.printErrorLine(Messages.getString("Application.RunTfProductkeyToInstall")); //$NON-NLS-1$
            }

            ret = ExitCode.FAILURE;
        } catch (final AuthenticationSecurityException e) {
            /*
             * Thrown when using insecure credentials over an insecure channel.
             */
            log.error(e);
            display.printErrorLine(e.getLocalizedMessage());
            ret = ExitCode.FAILURE;
        } catch (final TFSFederatedAuthException e) {
            /*
             * FederatedAuthenticationException is thrown when
             * DefaultFederatedAuthenticationHandler decided not to try to auth,
             * which only happens because the username and/or password weren't
             * available.
             */
            final String message = Messages.getString("Command.FedAuthRequiresUsernamePassword"); //$NON-NLS-1$
            log.error(message, e);
            display.printErrorLine(message);
            ret = ExitCode.FAILURE;
        } catch (final ProxyException e) {
            final String message = MessageFormat.format(
                    Messages.getString("Application.ProblemContactingServerFormat"), //$NON-NLS-1$
                    e.getLocalizedMessage());
            log.error(message, e);
            display.printErrorLine(message);
            ret = ExitCode.FAILURE;
        } catch (final TECoreException e) {
            /*
             * The most basic core exception class. All lower level (SOAP)
             * exceptions are wrapped in these.
             */
            CLCTelemetryHelper.sendException(e);

            final String messageFormat = Messages.getString("Application.AnErrorOccurredFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, e.getLocalizedMessage());
            log.error(message, e);
            display.printErrorLine(message);
            ret = ExitCode.FAILURE;
        } catch (final Throwable e) {
            CLCTelemetryHelper.sendException(new Exception("Unexpected exception.", e)); //$NON-NLS-1$

            log.error("Unexpected exception: ", e); //$NON-NLS-1$
        }

        // If the exit code never got set, set it to 0.
        if (ret == ExitCode.UNKNOWN) {
            ret = ExitCode.SUCCESS;
        }

        if (printExitCode) {
            final String messageFormat = Messages.getString("Application.ExitCodeFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, Integer.toString(ret));
            display.printLine(message);
            display.printLine(""); //$NON-NLS-1$
        }

        CLCTelemetryHelper.sendCommandFinishedEvent(c, ret);
        log.debug("Leaving CLC application"); //$NON-NLS-1$

        return ret;
    }

    /**
     * Parse the tokens into a command, any options, and free arguments. If the
     * command is not present (or is not recognized), returns null.
     *
     * @param tokens
     *        the tokens to parse (command-line arguments, not including the
     *        program being exeucted). Not null.
     * @param foundOptions
     *        an allocated ArrayList the found options will be stored in.
     * @param foundFreeArguments
     *        an allocated ArrayList the found free arguments will be stored in.
     * @return the command that was parsed from the tokens, null if none was
     *         found or recognized.
     * @throws UnknownOptionException
     *         if an unknown option is encountered.
     * @throws UnknownCommandException
     *         if an unknown command is encountered.
     * @throws InvalidOptionValueException
     *         if an invalid value was passed to an option.
     */
    private Command parseTokens(final String[] tokens, final ArrayList<Option> foundOptions,
            final ArrayList<String> foundFreeArguments, final AtomicReference<Exception> outException) {
        Command c = null;

        for (int i = 0; i < tokens.length; i++) {
            final String token = tokens[i];

            if (token == null || token.length() == 0) {
                continue;
            }

            /*
             * If this token looks like an option...
             */
            boolean startsWithOptionCharacter = false;
            if (token.length() >= 2) {
                for (int j = 0; j < OptionsMap.getSupportedOptionPrefixes().length; j++) {
                    if (token.charAt(0) == OptionsMap.getSupportedOptionPrefixes()[j]) {
                        startsWithOptionCharacter = true;
                        break;
                    }
                }
            }

            if (startsWithOptionCharacter) {
                final Option o;

                try {
                    o = optionsMap.findOption(token);
                } catch (final InvalidOptionValueException e) {
                    outException.set(e);
                    break;
                }

                if (o == null) {
                    outException.set(new UnknownOptionException(token));
                    break;
                }

                foundOptions.add(o);
                continue;
            }

            /*
             * We didn't parse it as an option, or command file trigger, so it
             * is the command (unless we've already found the command).
             */
            if (c == null) {
                final Command possibleCommand = commandsMap.findCommand(token);

                if (possibleCommand == null) {
                    outException.set(new UnknownCommandException(token));
                    break;
                }

                c = possibleCommand;
                continue;
            }

            /*
             * The remaining items must be free arguments.
             */
            foundFreeArguments.add(token);
        }

        return c;
    }

    /**
     * Run commands from a file (including standard input) using the extra
     * parameters as defaults for each command.
     *
     * @param commandLineArguments
     *        the arguments from the command line, the first of which must be a
     *        string that starts with the \@ symbol. If this string is 1
     *        character long (contains only the \@ character), commands are read
     *        from standard input. If the string is longer, the remaining
     *        characters compose the name of the file to read commands from.
     *        Additional array elements are substituted (positional arguments in
     *        the style "%1", "%2", etc.) in the command lines read.
     * @param continueOnError
     *        true if execution should continue after a command returns a
     *        non-success, false if execution should stop in that case
     * @param outputSeparator
     *        if non-null, this string is printed as a new line between every
     *        command run from the command file. If null, no separator line is
     *        printed between commands.
     * @throws FileNotFoundException
     *         if the command file named in the first token cannot be found.
     * @throws IOException
     *         if an error occurred reading from the command file.
     * @return the status code to exit the process with.
     */
    private int runCommandFile(final String[] commandLineArguments, final boolean continueOnError,
            final String outputSeparator) throws FileNotFoundException, IOException {
        if (commandLineArguments == null || commandLineArguments.length == 0) {
            return ExitCode.FAILURE;
        }

        InputStreamReader isr = null;
        BufferedReader br = null;

        int ret = ExitCode.UNKNOWN;

        try {
            /*
             * commandLineArguments[0] will contain the command file. Other
             * tokens are positional arguments for the lines in the command
             * file.
             *
             * If commandLineArguments[0] is simply the character "@" (no file
             * name), we should read from standard input.
             */
            if (commandLineArguments[0].equals("@")) //$NON-NLS-1$
            {
                isr = new InputStreamReader(input.getInputStream());
            } else {
                isr = new InputStreamReader(new FileInputStream(commandLineArguments[0].substring(1)));
            }

            br = new BufferedReader(isr);

            String line = null;
            while ((line = br.readLine()) != null) {
                // Break this line into tokens we'll run the usual way.
                final String[] conventionalJavaArgs = tokenizeCommandFileLine(line, commandLineArguments);

                // Null means nothing to do on this line (comment, etc.).
                if (conventionalJavaArgs == null) {
                    continue;
                }

                final int thisRet = run(conventionalJavaArgs, true);

                /*
                 * Print the output separator if the user desired it.
                 */
                if (outputSeparator != null) {
                    getDisplay().printLine(outputSeparator);
                    getDisplay().printErrorLine(outputSeparator);

                    /*
                     * Flush the streams in case they're being piped somewhere
                     * that might buffer (REXX wrapper on z/OS?).
                     */
                    getDisplay().getPrintStream().flush();
                    getDisplay().getErrorPrintStream().flush();
                }

                /*
                 * If this command didn't succeed, we might have to exit early.
                 */
                if (thisRet != ExitCode.SUCCESS) {
                    /*
                     * This wasn't successful, so stop processing if
                     * continueOnError was not set or if the command itself
                     * tells us to stop processing now ("exit" command).
                     */
                    if (continueOnError == false || thisRet == ExitCode.SUCCESS_BUT_STOP_NOW) {
                        ret = thisRet;
                        break;
                    }

                    /*
                     * Continue processing, but use partial success.
                     */
                    ret = ExitCode.PARTIAL_SUCCESS;
                } else {
                    /*
                     * Set the success code, but only if there wasn't a previous
                     * partial success.
                     */
                    if (ret != ExitCode.PARTIAL_SUCCESS) {
                        ret = ExitCode.SUCCESS;
                    }
                }
            }
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (final IOException e) {
                }
            }
        }

        // The script stopped executing, so use the offical return value.
        if (ret == ExitCode.SUCCESS_BUT_STOP_NOW) {
            ret = ExitCode.SUCCESS;
        }

        return ret;
    }

    /**
     * Parse the arguments from a command file line. When we're invoked from a
     * shell on Unix, the shell does the argument parsing (on Windows, the JVM
     * does the parsing). When we read from a command file, we have to parse in
     * a way similar to Microsoft's parsing rules.
     *
     * From Microsoft's code: [" The only recognized "special" sequence is that
     * a pair of double quotes inside a quoted string is translated to a single
     * double quote. Other double quote characters are used to bound arguments.
     * Examples: abc -> abc; a bc -> a, bc; "a bc" -> a bc; "a""bc" -> a"bc;
     * """a""" -> "a" "]
     *
     * @param line
     *        the command line to parse (not null).
     * @param positionalArguments
     *        the original command line's arguments, which will be substituted
     *        for instances of "%1", "%2", etc. in the given line.
     * @return the parsed tokens, null if no command at all was found on this
     *         line.
     */
    private String[] tokenizeCommandFileLine(String line, final String[] positionalArguments) {
        Check.notNull(line, "line"); //$NON-NLS-1$
        Check.notNull(positionalArguments, "positionalArguments"); //$NON-NLS-1$

        // Skip blank lines and whitespace-only lines.
        if (line.trim().length() == 0) {
            return null;
        }

        // We always skip leading whitespace.
        line = StringUtil.trimBegin(line);

        // Skip lines that begin with a comment indicator
        if (line.toLowerCase().startsWith("rem ") || line.toLowerCase().startsWith("#")) //$NON-NLS-1$ //$NON-NLS-2$
        {
            return null;
        }

        /*
         * Expand any positional parameter variables in the command line to the
         * values at that index in the positionalArguments array.
         *
         * Start with index at 1, not 0, because we don't expand %0.
         */
        for (int i = 1; i < positionalArguments.length; i++) {
            if (positionalArguments[i] != null) {
                line = replace(line, "%" + i, positionalArguments[i]); //$NON-NLS-1$
            }
        }

        final ArrayList<String> tokens = new ArrayList<String>();

        int i = 0;
        char c;
        boolean insideQuote = false;

        while (i < line.length()) {
            // Eat any whitespace.
            for (; i < line.length(); ++i) {
                if (line.charAt(i) != ' ' && line.charAt(i) != '\t') {
                    break;
                }
            }

            if (i < line.length()) {
                // Collect the argument.
                final StringBuffer thisToken = new StringBuffer();

                for (; i < line.length(); ++i) {
                    c = line.charAt(i);
                    if (c == '"') {
                        // Two double-quotes inside quotes are smashed into one
                        // double-quote.
                        if (insideQuote && i + 1 < line.length() && line.charAt(i + 1) == '"') {
                            ++i;
                        } else {
                            // Flip the quote state but don't add the quote to
                            // the
                            // token.
                            insideQuote = !insideQuote;
                            continue;
                        }
                    } else if (!insideQuote && (c == ' ' || c == '\t')) {
                        // Whitespace outside of quotes is end of token.
                        break;
                    }

                    thisToken.append(c);
                }

                tokens.add(thisToken.toString());
            }
        }

        return tokens.toArray(new String[0]);
    }

    /**
     * Quick hack of a String.replace method for Java 1.4
     *
     * @param source
     *        The string you want to search
     * @param pattern
     *        The pattern you would like to match
     * @param replace
     *        The string you want to replace the pattern with
     * @return the source string with all occurances of the patterm replaced
     */
    private String replace(final String source, final String pattern, final String replace) {
        if (source != null) {
            final int len = pattern.length();
            final StringBuffer sb = new StringBuffer();

            int found = -1;
            int start = 0;

            while ((found = source.indexOf(pattern, start)) != -1) {
                sb.append(source.substring(start, found));
                sb.append(replace);
                start = found + len;
            }

            sb.append(source.substring(start));

            return sb.toString();
        }

        return ""; //$NON-NLS-1$
    }

}