org.pentaho.di.core.Const.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.core.Const.java

Source

// CHECKSTYLE:FileLength:OFF
/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * 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.pentaho.di.core;

import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.util.EnvUtil;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.laf.BasePropertyHandler;
import org.pentaho.di.version.BuildVersion;

/**
 * This class is used to define a number of default values for various settings throughout Kettle. It also contains a
 * number of static final methods to make your life easier.
 *
 * @author Matt
 * @since 07-05-2003
 *
 */
public class Const {
    private static Class<?> PKG = Const.class; // for i18n purposes, needed by Translator2!!

    /**
     * Version number
     *
     * @deprecated Use {@link BuildVersion#getVersion()} instead
     */
    @Deprecated
    public static final String VERSION = BuildVersion.getInstance().getVersion();

    /**
     * Copyright year
     */
    public static final String COPYRIGHT_YEAR = "2014";

    /**
     * Release Type
     */
    public enum ReleaseType {
        RELEASE_CANDIDATE {
            public String getMessage() {
                return BaseMessages.getString(PKG, "Const.PreviewRelease.HelpAboutText");
            }
        },
        MILESTONE {
            public String getMessage() {
                return BaseMessages.getString(PKG, "Const.Candidate.HelpAboutText");
            }
        },
        PREVIEW {
            public String getMessage() {
                return BaseMessages.getString(PKG, "Const.Milestone.HelpAboutText");
            }
        },
        GA {
            public String getMessage() {
                return BaseMessages.getString(PKG, "Const.GA.HelpAboutText");
            }
        };
        public abstract String getMessage();
    }

    /**
     * Sleep time waiting when buffer is empty (the default)
     */
    public static final int TIMEOUT_GET_MILLIS = 50;

    /**
     * Sleep time waiting when buffer is full (the default)
     */
    public static final int TIMEOUT_PUT_MILLIS = 50;

    /**
     * print update every ... lines
     */
    public static final int ROWS_UPDATE = 50000;

    /**
     * Size of rowset: bigger = faster for large amounts of data
     */
    public static final int ROWS_IN_ROWSET = 10000;

    /**
     * Fetch size in rows when querying a database
     */
    public static final int FETCH_SIZE = 10000;

    /**
     * Sort size: how many rows do we sort in memory at once?
     */
    public static final int SORT_SIZE = 5000;

    /**
     * What's the file systems file separator on this operating system?
     */
    public static final String FILE_SEPARATOR = System.getProperty("file.separator");

    /**
     * What's the path separator on this operating system?
     */
    public static final String PATH_SEPARATOR = System.getProperty("path.separator");

    /**
     * CR: operating systems specific Carriage Return
     */
    public static final String CR = System.getProperty("line.separator");

    /**
     * DOSCR: MS-DOS specific Carriage Return
     */
    public static final String DOSCR = "\n\r";

    /**
     * An empty ("") String.
     */
    public static final String EMPTY_STRING = "";

    /**
     * The Java runtime version
     */
    public static final String JAVA_VERSION = System.getProperty("java.vm.version");

    /**
     * Path to the users home directory (keep this entry above references to getKettleDirectory())
     *
     * @deprecated Use {@link Const#getUserHomeDirectory()} instead.
     */
    @Deprecated
    public static final String USER_HOME_DIRECTORY = NVL(System.getProperty("KETTLE_HOME"),
            System.getProperty("user.home"));

    /**
     * Path to the simple-jndi directory
     */

    public static String JNDI_DIRECTORY = NVL(System.getProperty("KETTLE_JNDI_ROOT"),
            System.getProperty("org.osjava.sj.root"));

    /*
     * The images directory
     *
     * public static final String IMAGE_DIRECTORY = "/ui/images/";
     */

    public static final String PLUGIN_BASE_FOLDERS_PROP = "KETTLE_PLUGIN_BASE_FOLDERS";
    /**
     * the default comma separated list of base plugin folders.
     */
    public static final String DEFAULT_PLUGIN_BASE_FOLDERS = "plugins,"
            + (Const.isEmpty(getDIHomeDirectory()) ? "" : getDIHomeDirectory() + FILE_SEPARATOR + "plugins,")
            + getKettleDirectory() + FILE_SEPARATOR + "plugins";

    /**
     * Default minimum date range...
     */
    public static final Date MIN_DATE = new Date(-2208992400000L); // 1900/01/01 00:00:00.000

    /**
     * Default maximum date range...
     */
    public static final Date MAX_DATE = new Date(7258114799468L); // 2199/12/31 23:59:59.999

    /**
     * The default minimum year in a dimension date range
     */
    public static final int MIN_YEAR = 1900;

    /**
     * The default maximum year in a dimension date range
     */
    public static final int MAX_YEAR = 2199;

    /**
     * Specifies the number of pixels to the right we have to go in dialog boxes.
     */
    public static final int RIGHT = 400;

    /**
     * Specifies the length (width) of fields in a number of pixels in dialog boxes.
     */
    public static final int LENGTH = 350;

    /**
     * The margin between the different dialog components & widgets
     */
    public static final int MARGIN = 4;

    /**
     * The default percentage of the width of screen where we consider the middle of a dialog.
     */
    public static final int MIDDLE_PCT = 35;

    /**
     * The default width of an arrow in the Graphical Views
     */
    public static final int ARROW_WIDTH = 1;

    /**
     * The horizontal and vertical margin of a dialog box.
     */
    public static final int FORM_MARGIN = 5;

    /**
     * The default shadow size on the graphical view.
     */
    public static final int SHADOW_SIZE = 4;

    /**
     * The size of relationship symbols
     */
    public static final int SYMBOLSIZE = 10;

    /**
     * Max nr. of files to remember
     */
    public static final int MAX_FILE_HIST = 9; // Having more than 9 files in the file history is not compatible with pre
                                               // 5.0 versions

    /**
     * The default locale for the kettle environment (system defined)
     */
    public static final Locale DEFAULT_LOCALE = Locale.getDefault(); // new Locale("nl", "BE");

    /**
     * The default decimal separator . or ,
     */
    public static final char DEFAULT_DECIMAL_SEPARATOR = (new DecimalFormatSymbols(DEFAULT_LOCALE))
            .getDecimalSeparator();

    /**
     * The default grouping separator , or .
     */
    public static final char DEFAULT_GROUPING_SEPARATOR = (new DecimalFormatSymbols(DEFAULT_LOCALE))
            .getGroupingSeparator();

    /**
     * The default currency symbol
     */
    public static final String DEFAULT_CURRENCY_SYMBOL = (new DecimalFormatSymbols(DEFAULT_LOCALE))
            .getCurrencySymbol();

    /**
     * The default number format
     */
    public static final String DEFAULT_NUMBER_FORMAT = ((DecimalFormat) (NumberFormat.getInstance())).toPattern();

    /**
     * Default string representing Null String values (empty)
     */
    public static final String NULL_STRING = "";

    /**
     * Default string representing Null Number values (empty)
     */
    public static final String NULL_NUMBER = "";

    /**
     * Default string representing Null Date values (empty)
     */
    public static final String NULL_DATE = "";

    /**
     * Default string representing Null BigNumber values (empty)
     */
    public static final String NULL_BIGNUMBER = "";

    /**
     * Default string representing Null Boolean values (empty)
     */
    public static final String NULL_BOOLEAN = "";

    /**
     * Default string representing Null Integer values (empty)
     */
    public static final String NULL_INTEGER = "";

    /**
     * Default string representing Null Binary values (empty)
     */
    public static final String NULL_BINARY = "";

    /**
     * Default string representing Null Undefined values (empty)
     */
    public static final String NULL_NONE = "";

    /**
     * The base name of the Chef logfile
     */
    public static final String CHEF_LOG_FILE = "chef";

    /**
     * The base name of the Spoon logfile
     */
    public static final String SPOON_LOG_FILE = "spoon";

    /**
     * The base name of the Menu logfile
     */
    public static final String MENU_LOG_FILE = "menu";

    /**
     * A number of tips that are shown when the application first starts.
     */
    private static String[] tips;

    /**
     * An array of date conversion formats
     */
    private static String[] dateFormats;

    /**
     * An array of number conversion formats
     */
    private static String[] numberFormats;

    /**
     * Generalized date/time format: Wherever dates are used, date and time values are organized from the most to the
     * least significant. see also method StringUtil.getFormattedDateTime()
     */
    public static final String GENERALIZED_DATE_TIME_FORMAT = "yyyyddMM_hhmmss";
    public static final String GENERALIZED_DATE_TIME_FORMAT_MILLIS = "yyyyddMM_hhmmssSSS";

    /**
     * Default we store our information in Unicode UTF-8 character set.
     */
    public static final String XML_ENCODING = "UTF-8";

    /** The possible extensions a transformation XML file can have. */
    public static final String[] STRING_TRANS_AND_JOB_FILTER_EXT = new String[] { "*.ktr;*.kjb;*.xml",
            "*.ktr;*.xml", "*.kjb;*.xml", "*.xml", "*.*" };

    /** The discriptions of the possible extensions a transformation XML file can have. */
    private static String[] STRING_TRANS_AND_JOB_FILTER_NAMES;

    /** The extension of a Kettle transformation XML file */
    public static final String STRING_TRANS_DEFAULT_EXT = "ktr";

    /** The possible extensions a transformation XML file can have. */
    public static final String[] STRING_TRANS_FILTER_EXT = new String[] { "*.ktr;*.xml", "*.xml", "*.*" };

    /** The discriptions of the possible extensions a transformation XML file can have. */
    private static String[] STRING_TRANS_FILTER_NAMES;

    /** The extension of a Kettle job XML file */
    public static final String STRING_JOB_DEFAULT_EXT = "kjb";

    /** The possible extensions a job XML file can have. */
    public static final String[] STRING_JOB_FILTER_EXT = new String[] { "*.kjb;*.xml", "*.xml", "*.*" };

    /** The discriptions of the possible extensions a job XML file can have. */
    private static String[] STRING_JOB_FILTER_NAMES;

    /** Name of the kettle parameters file */
    public static final String KETTLE_PROPERTIES = "kettle.properties";

    /** The prefix that all internal kettle variables should have */
    public static final String INTERNAL_VARIABLE_PREFIX = "Internal";

    /** The version number as an internal variable */
    public static final String INTERNAL_VARIABLE_KETTLE_VERSION = INTERNAL_VARIABLE_PREFIX + ".Kettle.Version";

    /** The build version as an internal variable */
    public static final String INTERNAL_VARIABLE_KETTLE_BUILD_VERSION = INTERNAL_VARIABLE_PREFIX
            + ".Kettle.Build.Version";

    /** The build date as an internal variable */
    public static final String INTERNAL_VARIABLE_KETTLE_BUILD_DATE = INTERNAL_VARIABLE_PREFIX
            + ".Kettle.Build.Date";

    /** The job filename directory */
    public static final String INTERNAL_VARIABLE_JOB_FILENAME_DIRECTORY = INTERNAL_VARIABLE_PREFIX
            + ".Job.Filename.Directory";

    /** The job filename name */
    public static final String INTERNAL_VARIABLE_JOB_FILENAME_NAME = INTERNAL_VARIABLE_PREFIX
            + ".Job.Filename.Name";

    /** The job name */
    public static final String INTERNAL_VARIABLE_JOB_NAME = INTERNAL_VARIABLE_PREFIX + ".Job.Name";

    /** The job directory */
    public static final String INTERNAL_VARIABLE_JOB_REPOSITORY_DIRECTORY = INTERNAL_VARIABLE_PREFIX
            + ".Job.Repository.Directory";

    /** The job run ID */
    public static final String INTERNAL_VARIABLE_JOB_RUN_ID = INTERNAL_VARIABLE_PREFIX + ".Job.Run.ID";

    /** The job run attempt nr */
    public static final String INTERNAL_VARIABLE_JOB_RUN_ATTEMPTNR = INTERNAL_VARIABLE_PREFIX
            + ".Job.Run.AttemptNr";

    /**
     * All the internal transformation variables
     */
    public static final String[] INTERNAL_TRANS_VARIABLES = new String[] {
            Const.INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_DIRECTORY,
            Const.INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_NAME, Const.INTERNAL_VARIABLE_TRANSFORMATION_NAME,
            Const.INTERNAL_VARIABLE_TRANSFORMATION_REPOSITORY_DIRECTORY,

    };

    /**
     * All the internal job variables
     */
    public static final String[] INTERNAL_JOB_VARIABLES = new String[] {
            Const.INTERNAL_VARIABLE_JOB_FILENAME_DIRECTORY, Const.INTERNAL_VARIABLE_JOB_FILENAME_NAME,
            Const.INTERNAL_VARIABLE_JOB_NAME, Const.INTERNAL_VARIABLE_JOB_REPOSITORY_DIRECTORY,
            Const.INTERNAL_VARIABLE_JOB_RUN_ID, Const.INTERNAL_VARIABLE_JOB_RUN_ATTEMPTNR, };

    /** The transformation filename directory */
    public static final String INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_DIRECTORY = INTERNAL_VARIABLE_PREFIX
            + ".Transformation.Filename.Directory";

    /** The transformation filename name */
    public static final String INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_NAME = INTERNAL_VARIABLE_PREFIX
            + ".Transformation.Filename.Name";

    /** The transformation name */
    public static final String INTERNAL_VARIABLE_TRANSFORMATION_NAME = INTERNAL_VARIABLE_PREFIX
            + ".Transformation.Name";

    /** The transformation directory */
    public static final String INTERNAL_VARIABLE_TRANSFORMATION_REPOSITORY_DIRECTORY = INTERNAL_VARIABLE_PREFIX
            + ".Transformation.Repository.Directory";

    /** The step partition ID */
    public static final String INTERNAL_VARIABLE_STEP_PARTITION_ID = INTERNAL_VARIABLE_PREFIX
            + ".Step.Partition.ID";

    /** The step partition number */
    public static final String INTERNAL_VARIABLE_STEP_PARTITION_NR = INTERNAL_VARIABLE_PREFIX
            + ".Step.Partition.Number";

    /** The slave transformation number */
    public static final String INTERNAL_VARIABLE_SLAVE_SERVER_NUMBER = INTERNAL_VARIABLE_PREFIX
            + ".Slave.Transformation.Number";

    /** The slave transformation name */
    public static final String INTERNAL_VARIABLE_SLAVE_SERVER_NAME = INTERNAL_VARIABLE_PREFIX
            + ".Slave.Server.Name";

    /** The size of the cluster : number of slaves */
    public static final String INTERNAL_VARIABLE_CLUSTER_SIZE = INTERNAL_VARIABLE_PREFIX + ".Cluster.Size";

    /** The slave transformation number */
    public static final String INTERNAL_VARIABLE_STEP_UNIQUE_NUMBER = INTERNAL_VARIABLE_PREFIX
            + ".Step.Unique.Number";

    /** Is this transformation running clustered, on the master? */
    public static final String INTERNAL_VARIABLE_CLUSTER_MASTER = INTERNAL_VARIABLE_PREFIX + ".Cluster.Master";

    /**
     * The internal clustered run ID, unique across a clustered execution, important while doing parallel clustered runs
     */
    public static final String INTERNAL_VARIABLE_CLUSTER_RUN_ID = INTERNAL_VARIABLE_PREFIX + ".Cluster.Run.ID";

    /** The size of the cluster : number of slaves */
    public static final String INTERNAL_VARIABLE_STEP_UNIQUE_COUNT = INTERNAL_VARIABLE_PREFIX
            + ".Step.Unique.Count";

    /** The step name */
    public static final String INTERNAL_VARIABLE_STEP_NAME = INTERNAL_VARIABLE_PREFIX + ".Step.Name";

    /** The step copy nr */
    public static final String INTERNAL_VARIABLE_STEP_COPYNR = INTERNAL_VARIABLE_PREFIX + ".Step.CopyNr";

    /** The default maximum for the nr of lines in the GUI logs */
    public static final int MAX_NR_LOG_LINES = 5000;

    /** The default maximum for the nr of lines in the history views */
    public static final int MAX_NR_HISTORY_LINES = 50;

    /** The default fetch size for lines of history. */
    public static final int HISTORY_LINES_FETCH_SIZE = 10;

    /** The default log line timeout in minutes : 12 hours */
    public static final int MAX_LOG_LINE_TIMEOUT_MINUTES = 12 * 60;

    /** UI-agnostic flag for warnings */
    public static final int WARNING = 1;

    /** UI-agnostic flag for warnings */
    public static final int ERROR = 2;

    /** UI-agnostic flag for warnings */
    public static final int INFO = 3;

    /**
     * The margin between the text of a note and its border.
     */
    public static final int NOTE_MARGIN = 5;

    /**
     * The default undo level for Kettle
     */
    public static final int MAX_UNDO = 100;

    /**
     * The file that documents these variables.
     */
    public static final String KETTLE_VARIABLES_FILE = "kettle-variables.xml";

    /**
     * If you set this environment variable you can limit the log size of all transformations and jobs that don't have the
     * "log size limit" property set in their respective properties.
     */
    public static final String KETTLE_LOG_SIZE_LIMIT = "KETTLE_LOG_SIZE_LIMIT";

    /**
     * The name of the variable that defines the log database connection by default for all transformations
     */
    public static final String KETTLE_TRANS_LOG_DB = "KETTLE_TRANS_LOG_DB";

    /**
     * The name of the variable that defines the logging schema for all transformations
     */
    public static final String KETTLE_TRANS_LOG_SCHEMA = "KETTLE_TRANS_LOG_SCHEMA";

    /**
     * The name of the variable that defines the logging table for all transformations
     */
    public static final String KETTLE_TRANS_LOG_TABLE = "KETTLE_TRANS_LOG_TABLE";

    /**
     * The name of the variable that defines the log database connection by default for all jobs
     */
    public static final String KETTLE_JOB_LOG_DB = "KETTLE_JOB_LOG_DB";

    /**
     * The name of the variable that defines the logging schema for all jobs
     */
    public static final String KETTLE_JOB_LOG_SCHEMA = "KETTLE_JOB_LOG_SCHEMA";

    /**
     * The name of the variable that defines the logging table for all jobs
     */
    public static final String KETTLE_JOB_LOG_TABLE = "KETTLE_JOB_LOG_TABLE";

    /**
     * The name of the variable that defines the transformation performance log schema by default for all transformations
     */
    public static final String KETTLE_TRANS_PERFORMANCE_LOG_DB = "KETTLE_TRANS_PERFORMANCE_LOG_DB";

    /**
     * The name of the variable that defines the transformation performance log database connection by default for all
     * transformations
     */
    public static final String KETTLE_TRANS_PERFORMANCE_LOG_SCHEMA = "KETTLE_TRANS_PERFORMANCE_LOG_SCHEMA";

    /**
     * The name of the variable that defines the transformation performance log table by default for all transformations
     */
    public static final String KETTLE_TRANS_PERFORMANCE_LOG_TABLE = "KETTLE_TRANS_PERFORMANCE_LOG_TABLE";

    /**
     * The name of the variable that defines the job entry log database by default for all jobs
     */
    public static final String KETTLE_JOBENTRY_LOG_DB = "KETTLE_JOBENTRY_LOG_DB";

    /**
     * The name of the variable that defines the job entry log schema by default for all jobs
     */
    public static final String KETTLE_JOBENTRY_LOG_SCHEMA = "KETTLE_JOBENTRY_LOG_SCHEMA";

    /**
     * The name of the variable that defines the job entry log table by default for all jobs
     */
    public static final String KETTLE_JOBENTRY_LOG_TABLE = "KETTLE_JOBENTRY_LOG_TABLE";

    /**
     * The name of the variable that defines the steps log database by default for all transformations
     */
    public static final String KETTLE_STEP_LOG_DB = "KETTLE_STEP_LOG_DB";

    /**
     * The name of the variable that defines the steps log schema by default for all transformations
     */
    public static final String KETTLE_STEP_LOG_SCHEMA = "KETTLE_STEP_LOG_SCHEMA";

    /**
     * The name of the variable that defines the steps log table by default for all transformations
     */
    public static final String KETTLE_STEP_LOG_TABLE = "KETTLE_STEP_LOG_TABLE";

    /**
     * The name of the variable that defines the log channel log database by default for all transformations and jobs
     */
    public static final String KETTLE_CHANNEL_LOG_DB = "KETTLE_CHANNEL_LOG_DB";

    /**
     * The name of the variable that defines the log channel log schema by default for all transformations and jobs
     */
    public static final String KETTLE_CHANNEL_LOG_SCHEMA = "KETTLE_CHANNEL_LOG_SCHEMA";

    /**
     * The name of the variable that defines the log channel log table by default for all transformations and jobs
     */
    public static final String KETTLE_CHANNEL_LOG_TABLE = "KETTLE_CHANNEL_LOG_TABLE";

    /**
     * The name of the variable that defines the metrics log database by default for all transformations and jobs
     */
    public static final String KETTLE_METRICS_LOG_DB = "KETTLE_METRICS_LOG_DB";

    /**
     * The name of the variable that defines the metrics log schema by default for all transformations and jobs
     */
    public static final String KETTLE_METRICS_LOG_SCHEMA = "KETTLE_METRICS_LOG_SCHEMA";

    /**
     * The name of the variable that defines the metrics log table by default for all transformations and jobs
     */
    public static final String KETTLE_METRICS_LOG_TABLE = "KETTLE_METRICS_LOG_TABLE";

    /**
     * The name of the variable that defines the checkpoint log database by default for all jobs
     */
    public static final String KETTLE_CHECKPOINT_LOG_DB = "KETTLE_CHECKPOINT_LOG_DB";

    /**
     * The name of the variable that defines the checkpoint log schema by default for all jobs
     */
    public static final String KETTLE_CHECKPOINT_LOG_SCHEMA = "KETTLE_CHECKPOINT_LOG_SCHEMA";

    /**
     * The name of the variable that defines the checkpoint log table by default for all jobs
     */
    public static final String KETTLE_CHECKPOINT_LOG_TABLE = "KETTLE_CHECKPOINT_LOG_TABLE";

    /**
     * Name of the environment variable to set the location of the shared object file (xml) for transformations and jobs
     */
    public static final String KETTLE_SHARED_OBJECTS = "KETTLE_SHARED_OBJECTS";

    /**
     * System wide flag to drive the evaluation of null in ValueMeta. If this setting is set to "Y", an empty string and
     * null are different. Otherwise they are not.
     */
    public static final String KETTLE_EMPTY_STRING_DIFFERS_FROM_NULL = "KETTLE_EMPTY_STRING_DIFFERS_FROM_NULL";

    /**
     * System wide flag to allow non-strict string to number conversion for backward compatibility. If this setting is set
     * to "Y", an string starting with digits will be converted successfully into a number. (example: 192.168.1.1 will be
     * converted into 192 or 192.168 depending on the decimal symbol). The default (N) will be to throw an error if
     * non-numeric symbols are found in the string.
     */
    public static final String KETTLE_LENIENT_STRING_TO_NUMBER_CONVERSION = "KETTLE_LENIENT_STRING_TO_NUMBER_CONVERSION";

    /**
     * System wide flag to ignore timezone while writing date/timestamp value to the database. See PDI-10749 for details.
     */
    public static final String KETTLE_COMPATIBILITY_DB_IGNORE_TIMEZONE = "KETTLE_COMPATIBILITY_DB_IGNORE_TIMEZONE";

    /**
     * System wide flag to set or not append and header options dependency on Text file output step. See PDI-5252 for
     * details.
     */
    public static final String KETTLE_COMPATIBILITY_TEXT_FILE_OUTPUT_APPEND_NO_HEADER = "KETTLE_COMPATIBILITY_TEXT_FILE_OUTPUT_APPEND_NO_HEADER";

    /**
     * You can use this variable to speed up hostname lookup. 
     * Hostname lookup is performed by Kettle so that it is capable of logging the server on which a job or transformation is executed.
     */
    public static final String KETTLE_SYSTEM_HOSTNAME = "KETTLE_SYSTEM_HOSTNAME";

    /**
     * System wide flag to set the maximum number of log lines that are kept internally by Kettle. Set to 0 to keep all
     * rows (default)
     */
    public static final String KETTLE_MAX_LOG_SIZE_IN_LINES = "KETTLE_MAX_LOG_SIZE_IN_LINES";

    /**
     * System wide flag to set the maximum age (in minutes) of a log line while being kept internally by Kettle. Set to 0
     * to keep all rows indefinitely (default)
     */
    public static final String KETTLE_MAX_LOG_TIMEOUT_IN_MINUTES = "KETTLE_MAX_LOG_TIMEOUT_IN_MINUTES";

    /**
     * System wide flag to determine whether standard error will be redirected to Kettle logging facilities. Will redirect
     * if the value is equal ignoring case to the string "Y"
     */
    public static final String KETTLE_REDIRECT_STDERR = "KETTLE_REDIRECT_STDERR";

    /**
     * System wide flag to determine whether standard out will be redirected to Kettle logging facilities. Will redirect
     * if the value is equal ignoring case to the string "Y"
     */
    public static final String KETTLE_REDIRECT_STDOUT = "KETTLE_REDIRECT_STDOUT";

    /**
     * This environment variable will set a time-out after which waiting, completed or stopped transformations and jobs
     * will be automatically cleaned up. The default value is 1440 (one day).
     */
    public static final String KETTLE_CARTE_OBJECT_TIMEOUT_MINUTES = "KETTLE_CARTE_OBJECT_TIMEOUT_MINUTES";

    /**
     * System wide parameter: the maximum number of step performance snapshots to keep in memory. Set to 0 to keep all
     * snapshots indefinitely (default)
     */
    public static final String KETTLE_STEP_PERFORMANCE_SNAPSHOT_LIMIT = "KETTLE_STEP_PERFORMANCE_SNAPSHOT_LIMIT";

    /**
     * A variable to configure the maximum number of job trackers kept in memory.
     */
    public static final String KETTLE_MAX_JOB_TRACKER_SIZE = "KETTLE_MAX_JOB_TRACKER_SIZE";

    /**
     * A variable to configure the maximum number of job entry results kept in memory for logging purposes.
     */
    public static final String KETTLE_MAX_JOB_ENTRIES_LOGGED = "KETTLE_MAX_JOB_ENTRIES_LOGGED";

    /**
     * A variable to configure the maximum number of logging registry entries kept in memory for logging purposes.
     */
    public static final String KETTLE_MAX_LOGGING_REGISTRY_SIZE = "KETTLE_MAX_LOGGING_REGISTRY_SIZE";

    /**
     * The name of the system wide variable that can contain the name of the SAP Connection factory for the test button in
     * the DB dialog. This defaults to
     */
    public static final String KETTLE_SAP_CONNECTION_FACTORY = "KETTLE_SAP_CONNECTION_FACTORY";

    /**
     * The default SAP ERP connection factory
     */
    public static final String KETTLE_SAP_CONNECTION_FACTORY_DEFAULT_NAME = "org.pentaho.di.trans.steps.sapinput.sap.SAPConnectionFactory";

    /**
     * Name of the environment variable to specify additional classes to scan for plugin annotations
     */
    public static final String KETTLE_PLUGIN_CLASSES = "KETTLE_PLUGIN_CLASSES";

    /**
     * Name of the environment variable to specify additional packaged to scan for plugin annotations (warning: slow!)
     */
    public static final String KETTLE_PLUGIN_PACKAGES = "KETTLE_PLUGIN_PACKAGES";

    /**
     * Name of the environment variable that contains the size of the transformation rowset size. This overwrites values
     * that you set transformation settings.
     */
    public static final String KETTLE_TRANS_ROWSET_SIZE = "KETTLE_TRANS_ROWSET_SIZE";

    /**
     * A general initial version comment
     */
    public static final String VERSION_COMMENT_INITIAL_VERSION = "Creation of initial version";

    /**
     * A general edit version comment
     */
    public static final String VERSION_COMMENT_EDIT_VERSION = "Modification by user";

    /**
     * The XML file that contains the list of native Kettle steps
     */
    public static final String XML_FILE_KETTLE_STEPS = "kettle-steps.xml";

    /**
     * The name of the environment variable that will contain the alternative location of the kettle-steps.xml file
     */
    public static final String KETTLE_CORE_STEPS_FILE = "KETTLE_CORE_STEPS_FILE";

    /**
     * The XML file that contains the list of native partition plugins
     */
    public static final String XML_FILE_KETTLE_PARTITION_PLUGINS = "kettle-partition-plugins.xml";

    /**
     * The name of the environment variable that will contain the alternative location of the kettle-job-entries.xml file
     */
    public static final String KETTLE_CORE_JOBENTRIES_FILE = "KETTLE_CORE_JOBENTRIES_FILE";

    /**
     * The XML file that contains the list of native Kettle Carte Servlets
     */
    public static final String XML_FILE_KETTLE_SERVLETS = "kettle-servlets.xml";

    /**
     * The XML file that contains the list of native Kettle value metadata plugins
     */
    public static final String XML_FILE_KETTLE_VALUEMETA_PLUGINS = "kettle-valuemeta-plugins.xml";

    /**
     * The XML file that contains the list of native Kettle two-way password encoder plugins
     */
    public static final String XML_FILE_KETTLE_PASSWORD_ENCODER_PLUGINS = "kettle-password-encoder-plugins.xml";

    /**
     * The name of the environment variable that will contain the alternative location of the kettle-valuemeta-plugins.xml
     * file
     */
    public static final String KETTLE_VALUEMETA_PLUGINS_FILE = "KETTLE_VALUEMETA_PLUGINS_FILE";

    /**
     * Specifies the password encoding plugin to use by ID (Kettle is the default).
     */
    public static final String KETTLE_PASSWORD_ENCODER_PLUGIN = "KETTLE_PASSWORD_ENCODER_PLUGIN";

    /**
     * The name of the environment variable that will contain the alternative location of the kettle-password-encoder-plugins.xml
     * file
     */
    public static final String KETTLE_PASSWORD_ENCODER_PLUGINS_FILE = "KETTLE_PASSWORD_ENCODER_PLUGINS_FILE";

    /**
     * The XML file that contains the list of native Kettle logging plugins
     */
    public static final String XML_FILE_KETTLE_LOGGING_PLUGINS = "kettle-logging-plugins.xml";

    /**
     * The name of the environment variable that will contain the alternative location of the kettle-logging-plugins.xml
     * file
     */
    public static final String KETTLE_LOGGING_PLUGINS_FILE = "KETTLE_LOGGING_PLUGINS_FILE";

    /**
     * The name of the environment variable that will contain the alternative location of the kettle-servlets.xml file
     */
    public static final String KETTLE_CORE_SERVLETS_FILE = "KETTLE_CORE_SERVLETS_FILE";

    /**
     * The name of the variable that optionally contains an alternative rowset get timeout (in ms). This only makes a
     * difference for extremely short lived transformations.
     */
    public static final String KETTLE_ROWSET_GET_TIMEOUT = "KETTLE_ROWSET_GET_TIMEOUT";

    /**
     * The name of the variable that optionally contains an alternative rowset put timeout (in ms). This only makes a
     * difference for extremely short lived transformations.
     */
    public static final String KETTLE_ROWSET_PUT_TIMEOUT = "KETTLE_ROWSET_PUT_TIMEOUT";

    /**
     * Set this variable to Y if you want to test a more efficient batching row set. (default = N)
     */
    public static final String KETTLE_BATCHING_ROWSET = "KETTLE_BATCHING_ROWSET";

    /**
     * Set this variable to Y to disable standard Kettle logging to the console. (stdout)
     */
    public static final String KETTLE_DISABLE_CONSOLE_LOGGING = "KETTLE_DISABLE_CONSOLE_LOGGING";

    /**
     * The XML file that contains the list of native Kettle job entries
     */
    public static final String XML_FILE_KETTLE_JOB_ENTRIES = "kettle-job-entries.xml";

    /**
     * The XML file that contains the list of native Kettle repository types (DB, File, etc)
     */
    public static final String XML_FILE_KETTLE_REPOSITORIES = "kettle-repositories.xml";

    /**
     * The XML file that contains the list of native Kettle database types (MySQL, Oracle, etc)
     */
    public static final String XML_FILE_KETTLE_DATABASE_TYPES = "kettle-database-types.xml";

    /**
     * The XML file that contains the list of native Kettle compression providers (None, ZIP, GZip, etc.)
     */
    public static final String XML_FILE_KETTLE_COMPRESSION_PROVIDERS = "kettle-compression-providers.xml";

    /**
     * The XML file that contains the list of native Kettle compression providers (None, ZIP, GZip, etc.)
     */
    public static final String XML_FILE_KETTLE_AUTHENTICATION_PROVIDERS = "kettle-authentication-providers.xml";

    /**
     * The XML file that contains the list of native extension points (None by default, this is mostly for OEM purposes)
     */
    public static final String XML_FILE_KETTLE_EXTENSION_POINTS = "kettle-extension-points.xml";

    /**
     * the value the Pan JVM should return on exit.
     */
    public static final String KETTLE_TRANS_PAN_JVM_EXIT_CODE = "KETTLE_TRANS_PAN_JVM_EXIT_CODE";

    /**
     * The name of the variable containing an alternative default number format
     */
    public static final String KETTLE_DEFAULT_NUMBER_FORMAT = "KETTLE_DEFAULT_NUMBER_FORMAT";

    /**
     * The name of the variable containing an alternative default bignumber format
     */
    public static final String KETTLE_DEFAULT_BIGNUMBER_FORMAT = "KETTLE_DEFAULT_BIGNUMBER_FORMAT";

    /**
     * The name of the variable containing an alternative default integer format
     */
    public static final String KETTLE_DEFAULT_INTEGER_FORMAT = "KETTLE_DEFAULT_INTEGER_FORMAT";

    /**
     * The name of the variable containing an alternative default date format
     */
    public static final String KETTLE_DEFAULT_DATE_FORMAT = "KETTLE_DEFAULT_DATE_FORMAT";

    // Null values tweaks
    public static final String KETTLE_AGGREGATION_MIN_NULL_IS_VALUED = "KETTLE_AGGREGATION_MIN_NULL_IS_VALUED";
    public static final String KETTLE_AGGREGATION_ALL_NULLS_ARE_ZERO = "KETTLE_AGGREGATION_ALL_NULLS_ARE_ZERO";

    /**
     * The name of the variable containing an alternative default timestamp format
     */
    public static final String KETTLE_DEFAULT_TIMESTAMP_FORMAT = "KETTLE_DEFAULT_TIMESTAMP_FORMAT";

    /**
     * Compatibility settings for setNrErrors
     */
    // see PDI-10270 for details.
    public static final String KETTLE_COMPATIBILITY_SET_ERROR_ON_SPECIFIC_JOB_ENTRIES = "KETTLE_COMPATIBILITY_SET_ERROR_ON_SPECIFIC_JOB_ENTRIES";

    /**
     * The XML file that contains the list of native import rules
     */
    public static final String XML_FILE_KETTLE_IMPORT_RULES = "kettle-import-rules.xml";

    private static String[] emptyPaddedSpacesStrings;

    /**
     * The release type of this compilation
     */
    public static final ReleaseType RELEASE = ReleaseType.GA;

    /**
     * The system environment variable indicating where the alternative location for the Pentaho metastore folder is
     * located.
     */
    public static final String PENTAHO_METASTORE_FOLDER = "PENTAHO_METASTORE_FOLDER";

    /**
     * The name of the local client MetaStore
     *
     */
    public static final String PENTAHO_METASTORE_NAME = "Pentaho Local Client Metastore";

    /**
     * A variable to configure turning on/off detailed subjects in log.
     */
    public static final String KETTLE_LOG_MARK_MAPPINGS = "KETTLE_LOG_MARK_MAPPINGS";

    /**
    * rounds double f to any number of places after decimal point Does arithmetic using BigDecimal class to avoid integer
    * overflow while rounding
    *
    * @param f
    *          The value to round
    * @param places
    *          The number of decimal places
    * @return The rounded floating point value
    */

    public static final double round(double f, int places) {
        return round(f, places, java.math.BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * rounds double f to any number of places after decimal point Does arithmetic using BigDecimal class to avoid integer
     * overflow while rounding
     *
     * @param f
     *          The value to round
     * @param places
     *          The number of decimal places
     * @param roundingMode
     *          The mode for rounding, e.g. java.math.BigDecimal.ROUND_HALF_EVEN
     * @return The rounded floating point value
     */

    public static final double round(double f, int places, int roundingMode) {
        // We can't round non-numbers or infinite values
        //
        if (f == Double.NaN || f == Double.NEGATIVE_INFINITY || f == Double.POSITIVE_INFINITY) {
            return f;
        }

        // Do the rounding...
        //
        java.math.BigDecimal bdtemp = java.math.BigDecimal.valueOf(f);
        bdtemp = bdtemp.setScale(places, roundingMode);
        return bdtemp.doubleValue();
    }

    /*
     * OLD code: caused a lot of problems with very small and very large numbers. It's a miracle it worked at all. Go
     * ahead, have a laugh... public static final float round(double f, int places) { float temp = (float) (f *
     * (Math.pow(10, places)));
     *
     * temp = (Math.round(temp));
     *
     * temp = temp / (int) (Math.pow(10, places));
     *
     * return temp;
     *
     * }
     */

    /**
     * Convert a String into an integer. If the conversion fails, assign a default value.
     *
     * @param str
     *          The String to convert to an integer
     * @param def
     *          The default value
     * @return The converted value or the default.
     */
    public static final int toInt(String str, int def) {
        int retval;
        try {
            retval = Integer.parseInt(str);
        } catch (Exception e) {
            retval = def;
        }
        return retval;
    }

    /**
     * Convert a String into a long integer. If the conversion fails, assign a default value.
     *
     * @param str
     *          The String to convert to a long integer
     * @param def
     *          The default value
     * @return The converted value or the default.
     */
    public static final long toLong(String str, long def) {
        long retval;
        try {
            retval = Long.parseLong(str);
        } catch (Exception e) {
            retval = def;
        }
        return retval;
    }

    /**
     * Convert a String into a double. If the conversion fails, assign a default value.
     *
     * @param str
     *          The String to convert to a double
     * @param def
     *          The default value
     * @return The converted value or the default.
     */
    public static final double toDouble(String str, double def) {
        double retval;
        try {
            retval = Double.parseDouble(str);
        } catch (Exception e) {
            retval = def;
        }
        return retval;
    }

    /**
     * Convert a String into a date. The date format is <code>yyyy/MM/dd HH:mm:ss.SSS</code>. If the conversion fails,
     * assign a default value.
     *
     * @param str
     *          The String to convert into a Date
     * @param def
     *          The default value
     * @return The converted value or the default.
     */
    public static final Date toDate(String str, Date def) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS", Locale.US);
        try {
            return df.parse(str);
        } catch (ParseException e) {
            return def;
        }
    }

    /**
     * Determines whether or not a character is considered a space. A character is considered a space in Kettle if it is a
     * space, a tab, a newline or a cariage return.
     *
     * @param c
     *          The character to verify if it is a space.
     * @return true if the character is a space. false otherwise.
     */
    public static final boolean isSpace(char c) {
        return c == ' ' || c == '\t' || c == '\r' || c == '\n' || Character.isWhitespace(c);
    }

    /**
     * Left trim: remove spaces to the left of a String.
     *
     * @param source
     *          The String to left trim
     * @return The left trimmed String
     */
    public static String ltrim(String source) {
        if (source == null) {
            return null;
        }
        int from = 0;
        while (from < source.length() && isSpace(source.charAt(from))) {
            from++;
        }

        return source.substring(from);
    }

    /**
     * Right trim: remove spaces to the right of a string
     *
     * @param source
     *          The string to right trim
     * @return The trimmed string.
     */
    public static String rtrim(String source) {
        if (source == null) {
            return null;
        }

        int max = source.length();
        while (max > 0 && isSpace(source.charAt(max - 1))) {
            max--;
        }

        return source.substring(0, max);
    }

    /**
     * Trims a string: removes the leading and trailing spaces of a String.
     *
     * @param str
     *          The string to trim
     * @return The trimmed string.
     */
    public static final String trim(String str) {
        if (str == null) {
            return null;
        }

        int max = str.length() - 1;
        int min = 0;

        while (min <= max && isSpace(str.charAt(min))) {
            min++;
        }
        while (max >= 0 && isSpace(str.charAt(max))) {
            max--;
        }

        if (max < min) {
            return "";
        }

        return str.substring(min, max + 1);
    }

    /**
     * Right pad a string: adds spaces to a string until a certain length. If the length is smaller then the limit
     * specified, the String is truncated.
     *
     * @param ret
     *          The string to pad
     * @param limit
     *          The desired length of the padded string.
     * @return The padded String.
     */
    public static final String rightPad(String ret, int limit) {
        if (ret == null) {
            return rightPad(new StringBuffer(), limit);
        } else {
            return rightPad(new StringBuffer(ret), limit);
        }
    }

    /**
     * Right pad a StringBuffer: adds spaces to a string until a certain length. If the length is smaller then the limit
     * specified, the String is truncated.
     *
     * @param ret
     *          The StringBuffer to pad
     * @param limit
     *          The desired length of the padded string.
     * @return The padded String.
     */
    public static final String rightPad(StringBuffer ret, int limit) {
        int len = ret.length();
        int l;

        if (len > limit) {
            ret.setLength(limit);
        } else {
            for (l = len; l < limit; l++) {
                ret.append(' ');
            }
        }
        return ret.toString();
    }

    /**
     * Replace values in a String with another.
     *
     * @param string
     *          The original String.
     * @param repl
     *          The text to replace
     * @param with
     *          The new text bit
     * @return The resulting string with the text pieces replaced.
     */
    public static final String replace(String string, String repl, String with) {
        StringBuffer str = new StringBuffer(string);
        for (int i = str.length() - 1; i >= 0; i--) {
            if (str.substring(i).startsWith(repl)) {
                str.delete(i, i + repl.length());
                str.insert(i, with);
            }
        }
        return str.toString();
    }

    /**
     * Alternate faster version of string replace using a stringbuffer as input.
     *
     * @param str
     *          The string where we want to replace in
     * @param code
     *          The code to search for
     * @param repl
     *          The replacement string for code
     */
    public static void repl(StringBuffer str, String code, String repl) {
        int clength = code.length();

        int i = str.length() - clength;

        while (i >= 0) {
            String look = str.substring(i, i + clength);
            // Look for a match!
            if (look.equalsIgnoreCase(code)) {
                str.replace(i, i + clength, repl);
            }
            i--;
        }
    }

    /**
     * Count the number of spaces to the left of a text. (leading)
     *
     * @param field
     *          The text to examine
     * @return The number of leading spaces found.
     */
    public static final int nrSpacesBefore(String field) {
        int nr = 0;
        int len = field.length();
        while (nr < len && field.charAt(nr) == ' ') {
            nr++;
        }
        return nr;
    }

    /**
     * Count the number of spaces to the right of a text. (trailing)
     *
     * @param field
     *          The text to examine
     * @return The number of trailing spaces found.
     */
    public static final int nrSpacesAfter(String field) {
        int nr = 0;
        int len = field.length();
        while (nr < len && field.charAt(field.length() - 1 - nr) == ' ') {
            nr++;
        }
        return nr;
    }

    /**
     * Checks whether or not a String consists only of spaces.
     *
     * @param str
     *          The string to check
     * @return true if the string has nothing but spaces.
     */
    public static final boolean onlySpaces(String str) {
        for (int i = 0; i < str.length(); i++) {
            if (!isSpace(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * determine the OS name
     *
     * @return The name of the OS
     */
    public static final String getOS() {
        return System.getProperty("os.name");
    }

    /**
     * Determine the quoting character depending on the OS. Often used for shell calls, gives back " for Windows systems
     * otherwise '
     *
     * @return quoting character
     */
    public static String getQuoteCharByOS() {
        if (isWindows()) {
            return "\"";
        } else {
            return "'";
        }
    }

    /**
     * Quote a string depending on the OS. Often used for shell calls.
     *
     * @return quoted string
     */
    public static String optionallyQuoteStringByOS(String string) {
        String quote = getQuoteCharByOS();
        if (isEmpty(string)) {
            return quote;
        }

        // If the field already contains quotes, we don't touch it anymore, just
        // return the same string...
        // also return it if no spaces are found
        if (string.indexOf(quote) >= 0 || (string.indexOf(' ') < 0 && string.indexOf('=') < 0)) {
            return string;
        } else {
            return quote + string + quote;
        }
    }

    /**
     * @return True if the OS is a Windows derivate.
     */
    public static final boolean isWindows() {
        return getOS().startsWith("Windows");
    }

    /**
     * @return True if the OS is a Linux derivate.
     */
    public static final boolean isLinux() {
        return getOS().startsWith("Linux");
    }

    /**
     * @return True if the OS is an OSX derivate.
     */
    public static final boolean isOSX() {
        return getOS().toUpperCase().contains("OS X");
    }

    private static String cachedHostname;

    /**
     * Determine the hostname of the machine Kettle is running on
     *
     * @return The hostname
     */
    public static final String getHostname() {

        if (cachedHostname != null) {
            return cachedHostname;
        }

        // In case we don't want to leave anything to doubt...
        //
        String systemHostname = EnvUtil.getSystemProperty(KETTLE_SYSTEM_HOSTNAME);
        if (!isEmpty(systemHostname)) {
            cachedHostname = systemHostname;
            return systemHostname;
        }

        String lastHostname = "localhost";
        try {
            Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
            while (en.hasMoreElements()) {
                NetworkInterface nwi = en.nextElement();
                Enumeration<InetAddress> ip = nwi.getInetAddresses();

                while (ip.hasMoreElements()) {
                    InetAddress in = ip.nextElement();
                    lastHostname = in.getHostName();
                    // System.out.println("  ip address bound : "+in.getHostAddress());
                    // System.out.println("  hostname         : "+in.getHostName());
                    // System.out.println("  Cann.hostname    : "+in.getCanonicalHostName());
                    // System.out.println("  ip string        : "+in.toString());
                    if (!lastHostname.equalsIgnoreCase("localhost") && !(lastHostname.indexOf(':') >= 0)) {
                        break;
                    }
                }
            }
        } catch (SocketException e) {
            // Eat exception, just return what you have
        }

        cachedHostname = lastHostname;

        return lastHostname;
    }

    /**
     * Determine the hostname of the machine Kettle is running on
     *
     * @return The hostname
     */
    public static final String getHostnameReal() {

        // In case we don't want to leave anything to doubt...
        //
        String systemHostname = EnvUtil.getSystemProperty(KETTLE_SYSTEM_HOSTNAME);
        if (!isEmpty(systemHostname)) {
            return systemHostname;
        }

        if (isWindows()) {
            // Windows will always set the 'COMPUTERNAME' variable
            return System.getenv("COMPUTERNAME");
        } else {
            // If it is not Windows then it is most likely a Unix-like operating system
            // such as Solaris, AIX, HP-UX, Linux or MacOS.
            // Most modern shells (such as Bash or derivatives) sets the
            // HOSTNAME variable so lets try that first.
            String hostname = System.getenv("HOSTNAME");
            if (hostname != null) {
                return hostname;
            } else {
                BufferedReader br;
                try {
                    Process pr = Runtime.getRuntime().exec("hostname");
                    br = new BufferedReader(new InputStreamReader(pr.getInputStream()));
                    String line;
                    if ((line = br.readLine()) != null) {
                        return line;
                    }
                    pr.waitFor();
                    br.close();
                } catch (IOException e) {
                    return getHostname();
                } catch (InterruptedException e) {
                    return getHostname();
                }
            }
        }
        return getHostname();
    }

    /**
     * Determins the IP address of the machine Kettle is running on.
     *
     * @return The IP address
     */
    public static final String getIPAddress() throws Exception {
        Enumeration<NetworkInterface> enumInterfaces = NetworkInterface.getNetworkInterfaces();
        while (enumInterfaces.hasMoreElements()) {
            NetworkInterface nwi = enumInterfaces.nextElement();
            Enumeration<InetAddress> ip = nwi.getInetAddresses();
            while (ip.hasMoreElements()) {
                InetAddress in = ip.nextElement();
                if (!in.isLoopbackAddress() && in.toString().indexOf(":") < 0) {
                    return in.getHostAddress();
                }
            }
        }
        return "127.0.0.1";
    }

    /**
     * Get the primary IP address tied to a network interface (excluding loop-back etc)
     *
     * @param networkInterfaceName
     *          the name of the network interface to interrogate
     * @return null if the network interface or address wasn't found.
     *
     * @throws SocketException
     *           in case of a security or network error
     */
    public static final String getIPAddress(String networkInterfaceName) throws SocketException {
        NetworkInterface networkInterface = NetworkInterface.getByName(networkInterfaceName);
        Enumeration<InetAddress> ipAddresses = networkInterface.getInetAddresses();
        while (ipAddresses.hasMoreElements()) {
            InetAddress inetAddress = ipAddresses.nextElement();
            if (!inetAddress.isLoopbackAddress() && inetAddress.toString().indexOf(":") < 0) {
                String hostname = inetAddress.getHostAddress();
                return hostname;
            }
        }
        return null;
    }

    /**
     * Tries to determine the MAC address of the machine Kettle is running on.
     *
     * @return The MAC address.
     */
    public static final String getMACAddress() throws Exception {
        String ip = getIPAddress();
        String mac = "none";
        String os = getOS();
        String s = "";
        @SuppressWarnings("unused")
        Boolean errorOccured = false;
        // System.out.println("os = "+os+", ip="+ip);

        if (os.equalsIgnoreCase("Windows NT") || os.equalsIgnoreCase("Windows 2000")
                || os.equalsIgnoreCase("Windows XP") || os.equalsIgnoreCase("Windows 95")
                || os.equalsIgnoreCase("Windows 98") || os.equalsIgnoreCase("Windows Me")
                || os.startsWith("Windows")) {
            try {
                // System.out.println("EXEC> nbtstat -a "+ip);

                Process p = Runtime.getRuntime().exec("nbtstat -a " + ip);

                // read the standard output of the command
                BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));

                while (!procDone(p)) {
                    while ((s = stdInput.readLine()) != null) {
                        // System.out.println("NBTSTAT> "+s);
                        if (s.indexOf("MAC") >= 0) {
                            int idx = s.indexOf('=');
                            mac = s.substring(idx + 2);
                        }
                    }
                }
                stdInput.close();
            } catch (Exception e) {
                errorOccured = true;
            }
        } else if (os.equalsIgnoreCase("Linux")) {
            try {
                Process p = Runtime.getRuntime().exec("/sbin/ifconfig -a");

                // read the standard output of the command
                BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));

                while (!procDone(p)) {
                    while ((s = stdInput.readLine()) != null) {
                        int idx = s.indexOf("HWaddr");
                        if (idx >= 0) {
                            mac = s.substring(idx + 7);
                        }
                    }
                }
                stdInput.close();
            } catch (Exception e) {
                errorOccured = true;

            }
        } else if (os.equalsIgnoreCase("Solaris")) {
            try {
                Process p = Runtime.getRuntime().exec("/usr/sbin/ifconfig -a");

                // read the standard output of the command
                BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));

                while (!procDone(p)) {
                    while ((s = stdInput.readLine()) != null) {
                        int idx = s.indexOf("ether");
                        if (idx >= 0) {
                            mac = s.substring(idx + 6);
                        }
                    }
                }
                stdInput.close();
            } catch (Exception e) {
                errorOccured = true;

            }
        } else if (os.equalsIgnoreCase("HP-UX")) {
            try {
                Process p = Runtime.getRuntime().exec("/usr/sbin/lanscan -a");

                // read the standard output of the command
                BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));

                while (!procDone(p)) {
                    while ((s = stdInput.readLine()) != null) {
                        if (s.indexOf("MAC") >= 0) {
                            int idx = s.indexOf("0x");
                            mac = s.substring(idx + 2);
                        }
                    }
                }
                stdInput.close();
            } catch (Exception e) {
                errorOccured = true;

            }
        }
        // should do something if we got an error processing!
        return Const.trim(mac);
    }

    private static final boolean procDone(Process p) {
        try {
            p.exitValue();
            return true;
        } catch (IllegalThreadStateException e) {
            return false;
        }
    }

    /**
     * Looks up the user's home directory (or KETTLE_HOME) for every invocation. This is no longer a static property so
     * the value may be set after this class is loaded.
     *
     * @return The path to the users home directory, or the System property {@code KETTLE_HOME} if set.
     */
    public static final String getUserHomeDirectory() {
        return NVL(System.getProperty("KETTLE_HOME"), System.getProperty("user.home"));
    }

    /**
     * Determines the Kettle directory in the user's home directory.
     *
     * @return The Kettle directory.
     */
    public static final String getKettleDirectory() {
        return getUserHomeDirectory() + FILE_SEPARATOR + BasePropertyHandler.getProperty("userBaseDir", ".kettle");
    }

    /**
     * Returns the value of DI_HOME.
     */
    public static final String getDIHomeDirectory() {
        return System.getProperty("DI_HOME");
    }

    /**
     * Determines the location of the shared objects file
     *
     * @return the name of the shared objects file
     */
    public static final String getSharedObjectsFile() {
        return getKettleDirectory() + FILE_SEPARATOR + "shared.xml";
    }

    /**
     * Returns the path to the Kettle local (current directory) repositories XML file.
     *
     * @return The local repositories file.
     */
    public static final String getKettleLocalRepositoriesFile() {
        return "repositories.xml";
    }

    /**
     * Returns the full path to the Kettle repositories XML file.
     *
     * @return The Kettle repositories file.
     */
    public static final String getKettleUserRepositoriesFile() {
        return getKettleDirectory() + FILE_SEPARATOR + getKettleLocalRepositoriesFile();
    }

    /**
     * Returns the path to the Kettle local (current directory) Carte password file:
     * <p>
     * ./pwd/kettle.pwd<br>
     *
     * @return The local Carte password file.
     */
    public static final String getKettleLocalCartePasswordFile() {
        return "pwd/kettle.pwd";
    }

    /**
     * Returns the path to the Kettle Carte password file in the home directory:
     * <p>
     * $KETTLE_HOME/.kettle/kettle.pwd<br>
     *
     * @return The Carte password file in the home directory.
     */
    public static final String getKettleCartePasswordFile() {
        return getKettleDirectory() + FILE_SEPARATOR + "kettle.pwd";
    }

    /**
     * Retrieves the content of an environment variable
     *
     * @param variable
     *          The name of the environment variable
     * @param deflt
     *          The default value in case no value was found
     * @return The value of the environment variable or the value of deflt in case no variable was defined.
     */
    public static String getEnvironmentVariable(String variable, String deflt) {
        return System.getProperty(variable, deflt);
    }

    /**
     * Replaces environment variables in a string. For example if you set KETTLE_HOME as an environment variable, you can
     * use %%KETTLE_HOME%% in dialogs etc. to refer to this value. This procedures looks for %%...%% pairs and replaces
     * them including the name of the environment variable with the actual value. In case the variable was not set,
     * nothing is replaced!
     *
     * @param string
     *          The source string where text is going to be replaced.
     *
     * @return The expanded string.
     * @deprecated use StringUtil.environmentSubstitute(): handles both Windows and unix conventions
     */
    @Deprecated
    public static final String replEnv(String string) {
        if (string == null) {
            return null;
        }
        StringBuffer str = new StringBuffer(string);

        int idx = str.indexOf("%%");
        while (idx >= 0) {
            // OK, so we found a marker, look for the next one...
            int to = str.indexOf("%%", idx + 2);
            if (to >= 0) {
                // OK, we found the other marker also...
                String marker = str.substring(idx, to + 2);
                String var = str.substring(idx + 2, to);

                if (var != null && var.length() > 0) {
                    // Get the environment variable
                    String newval = getEnvironmentVariable(var, null);

                    if (newval != null) {
                        // Replace the whole bunch
                        str.replace(idx, to + 2, newval);
                        // System.out.println("Replaced ["+marker+"] with ["+newval+"]");

                        // The last position has changed...
                        to += newval.length() - marker.length();
                    }
                }

            } else {
                // We found the start, but NOT the ending %% without closing %%
                to = idx;
            }

            // Look for the next variable to replace...
            idx = str.indexOf("%%", to + 1);
        }

        return str.toString();
    }

    /**
     * Replaces environment variables in an array of strings.
     * <p>
     * See also: replEnv(String string)
     *
     * @param string
     *          The array of strings that wants its variables to be replaced.
     * @return the array with the environment variables replaced.
     * @deprecated please use StringUtil.environmentSubstitute now.
     */
    @Deprecated
    public static final String[] replEnv(String[] string) {
        String[] retval = new String[string.length];
        for (int i = 0; i < string.length; i++) {
            retval[i] = Const.replEnv(string[i]);
        }
        return retval;
    }

    /**
     * Implements Oracle style NVL function
     *
     * @param source
     *          The source argument
     * @param def
     *          The default value in case source is null or the length of the string is 0
     * @return source if source is not null, otherwise return def
     */
    public static final String NVL(String source, String def) {
        if (source == null || source.length() == 0) {
            return def;
        }
        return source;
    }

    /**
     * Return empty string "" in case the given parameter is null, otherwise return the same value.
     *
     * @param source
     *          The source value to check for null.
     * @return empty string if source is null, otherwise simply return the source value.
     */
    public static final String nullToEmpty(String source) {
        if (source == null) {
            return "";
        }
        return source;
    }

    /**
     * Search for a string in an array of strings and return the index.
     *
     * @param lookup
     *          The string to search for
     * @param array
     *          The array of strings to look in
     * @return The index of a search string in an array of strings. -1 if not found.
     */
    public static final int indexOfString(String lookup, String[] array) {
        if (array == null) {
            return -1;
        }
        if (lookup == null) {
            return -1;
        }

        for (int i = 0; i < array.length; i++) {
            if (lookup.equalsIgnoreCase(array[i])) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Search for strings in an array of strings and return the indexes.
     *
     * @param lookup
     *          The strings to search for
     * @param array
     *          The array of strings to look in
     * @return The indexes of strings in an array of strings. -1 if not found.
     */
    public static final int[] indexsOfStrings(String[] lookup, String[] array) {
        int[] indexes = new int[lookup.length];
        for (int i = 0; i < indexes.length; i++) {
            indexes[i] = indexOfString(lookup[i], array);
        }
        return indexes;
    }

    /**
     * Search for strings in an array of strings and return the indexes. If a string is not found, the index is not
     * returned.
     *
     * @param lookup
     *          The strings to search for
     * @param array
     *          The array of strings to look in
     * @return The indexes of strings in an array of strings. Only existing indexes are returned (no -1)
     */
    public static final int[] indexsOfFoundStrings(String[] lookup, String[] array) {
        List<Integer> indexesList = new ArrayList<Integer>();
        for (int i = 0; i < lookup.length; i++) {
            int idx = indexOfString(lookup[i], array);
            if (idx >= 0) {
                indexesList.add(Integer.valueOf(idx));
            }
        }
        int[] indexes = new int[indexesList.size()];
        for (int i = 0; i < indexesList.size(); i++) {
            indexes[i] = (indexesList.get(i)).intValue();
        }
        return indexes;
    }

    /**
     * Search for a string in a list of strings and return the index.
     *
     * @param lookup
     *          The string to search for
     * @param list
     *          The ArrayList of strings to look in
     * @return The index of a search string in an array of strings. -1 if not found.
     */
    public static final int indexOfString(String lookup, List<String> list) {
        if (list == null) {
            return -1;
        }

        for (int i = 0; i < list.size(); i++) {
            String compare = list.get(i);
            if (lookup.equalsIgnoreCase(compare)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Sort the strings of an array in alphabetical order.
     *
     * @param input
     *          The array of strings to sort.
     * @return The sorted array of strings.
     */
    public static final String[] sortStrings(String[] input) {
        Arrays.sort(input);
        return input;
    }

    /**
     * Convert strings separated by a string into an array of strings.
     * <p>
     * <code>
    Example: a;b;c;d    ==>    new String[] { a, b, c, d }
     * </code>
     *
     * <p>
     * <b>NOTE: this differs from String.split() in a way that the built-in method uses regular expressions and this one
     * does not.</b>
     *
     * @param string
     *          The string to split
     * @param separator
     *          The separator used.
     * @return the string split into an array of strings
     */
    public static final String[] splitString(String string, String separator) {
        /*
         * 0123456 Example a;b;c;d --> new String[] { a, b, c, d }
         */
        // System.out.println("splitString ["+path+"] using ["+separator+"]");
        List<String> list = new ArrayList<String>();

        if (string == null || string.length() == 0) {
            return new String[] {};
        }

        int sepLen = separator.length();
        int from = 0;
        int end = string.length() - sepLen + 1;

        for (int i = from; i < end; i += sepLen) {
            if (string.substring(i, i + sepLen).equalsIgnoreCase(separator)) {
                // OK, we found a separator, the string to add to the list
                // is [from, i[
                list.add(NVL(string.substring(from, i), ""));
                from = i + sepLen;
            }
        }

        // Wait, if the string didn't end with a separator, we still have information at the end of the string...
        // In our example that would be "d"...
        if (from + sepLen <= string.length()) {
            list.add(NVL(string.substring(from, string.length()), ""));
        }

        return list.toArray(new String[list.size()]);
    }

    /**
     * Convert strings separated by a character into an array of strings.
     * <p>
     * <code>
     Example: a;b;c;d    ==  new String[] { a, b, c, d }
     * </code>
     *
     * @param string
     *          The string to split
     * @param separator
     *          The separator used.
     * @return the string split into an array of strings
     */
    public static final String[] splitString(String string, char separator) {
        return splitString(string, separator, false);
    }

    /**
     * Convert strings separated by a character into an array of strings.
     * <p>
     * <code>
      Example: a;b;c;d    ==  new String[] { a, b, c, d }
     * </code>
     *
     * @param string
     *          The string to split
     * @param separator
     *          The separator used.
     * @param escape
     *          in case the separator can be escaped (\;) The escape characters are NOT removed!
     * @return the string split into an array of strings
     */
    public static final String[] splitString(String string, char separator, boolean escape) {
        /*
         * 0123456 Example a;b;c;d --> new String[] { a, b, c, d }
         */
        // System.out.println("splitString ["+path+"] using ["+separator+"]");
        List<String> list = new ArrayList<String>();

        if (string == null || string.length() == 0) {
            return new String[] {};
        }

        int from = 0;
        int end = string.length();

        for (int i = from; i < end; i += 1) {
            boolean found = string.charAt(i) == separator;
            if (found && escape && i > 0) {
                found &= string.charAt(i - 1) != '\\';
            }
            if (found) {
                // OK, we found a separator, the string to add to the list
                // is [from, i[
                list.add(NVL(string.substring(from, i), ""));
                from = i + 1;
            }
        }

        // Wait, if the string didn't end with a separator, we still have information at the end of the string...
        // In our example that would be "d"...
        if (from + 1 <= string.length()) {
            list.add(NVL(string.substring(from, string.length()), ""));
        }

        return list.toArray(new String[list.size()]);
    }

    /**
     * Convert strings separated by a string into an array of strings.
     * <p>
     * <code>
     *   Example /a/b/c --> new String[] { a, b, c }
     * </code>
     *
     * @param path
     *          The string to split
     * @param separator
     *          The separator used.
     * @return the string split into an array of strings
     */
    public static final String[] splitPath(String path, String separator) {
        //
        // Example /a/b/c --> new String[] { a, b, c }
        //
        // Make sure training slashes are removed
        //
        // Example /a/b/c/ --> new String[] { a, b, c }
        //

        // Check for empty paths...
        //
        if (path == null || path.length() == 0 || path.equals(separator)) {
            return new String[] {};
        }

        // lose trailing separators
        //
        while (path.endsWith(separator)) {
            path = path.substring(0, path.length() - 1);
        }

        int sepLen = separator.length();
        int nr_separators = 1;
        int from = path.startsWith(separator) ? sepLen : 0;

        for (int i = from; i < path.length(); i += sepLen) {
            if (path.substring(i, i + sepLen).equalsIgnoreCase(separator)) {
                nr_separators++;
            }
        }

        String[] spath = new String[nr_separators];
        int nr = 0;
        for (int i = from; i < path.length(); i += sepLen) {
            if (path.substring(i, i + sepLen).equalsIgnoreCase(separator)) {
                spath[nr] = path.substring(from, i);
                nr++;

                from = i + sepLen;
            }
        }
        if (nr < spath.length) {
            spath[nr] = path.substring(from);
        }

        //
        // a --> { a }
        //
        if (spath.length == 0 && path.length() > 0) {
            spath = new String[] { path };
        }

        return spath;
    }

    /**
     * Split the given string using the given delimiter and enclosure strings.
     *
     * The delimiter and enclosures are not regular expressions (regexes); rather they are literal strings that will be
     * quoted so as not to be treated like regexes.
     *
     * This method expects that the data contains an even number of enclosure strings in the input; otherwise the results
     * are undefined
     *
     * @param stringToSplit
     *          the String to split
     * @param delimiter
     *          the delimiter string
     * @param enclosure
     *          the enclosure string
     * @return an array of strings split on the delimiter (ignoring those in enclosures), or null if the string to split
     *         is null.
     */
    public static String[] splitString(String stringToSplit, String delimiter, String enclosure) {

        ArrayList<String> splitList = null;

        // Handle "bad input" cases
        if (stringToSplit == null) {
            return null;
        }
        if (delimiter == null) {
            return (new String[] { stringToSplit });
        }

        // Split the string on the delimiter, we'll build the "real" results from the partial results
        String[] delimiterSplit = stringToSplit.split(Pattern.quote(delimiter));

        // At this point, if the enclosure is null or empty, we will return the delimiter split
        if (isEmpty(enclosure)) {
            return delimiterSplit;
        }

        // Keep track of partial splits and concatenate them into a legit split
        StringBuffer concatSplit = null;

        if (delimiterSplit != null && delimiterSplit.length > 0) {

            // We'll have at least one result so create the result list object
            splitList = new ArrayList<String>();

            // Proceed through the partial splits, concatenating if the splits are within the enclosure
            for (String currentSplit : delimiterSplit) {
                if (!currentSplit.contains(enclosure)) {

                    // If we are currently concatenating a split, we are inside an enclosure. Since this
                    // split doesn't contain an enclosure, we can concatenate it (with a delimiter in front).
                    // If we're not concatenating, the split is fine so add it to the result list.
                    if (concatSplit != null) {
                        concatSplit.append(delimiter);
                        concatSplit.append(currentSplit);
                    } else {
                        splitList.add(currentSplit);
                    }
                } else {
                    // Find number of enclosures in the split, and whether that number is odd or even.
                    int numEnclosures = StringUtils.countMatches(currentSplit, enclosure);
                    boolean oddNumberOfEnclosures = (numEnclosures % 2 != 0);
                    boolean addSplit = false;

                    // This split contains an enclosure, so either start or finish concatenating
                    if (concatSplit == null) {
                        concatSplit = new StringBuffer(currentSplit); // start concatenation
                        addSplit = !oddNumberOfEnclosures;
                    } else {
                        // Check to make sure a new enclosure hasn't started within this split. This method expects
                        // that there are no non-delimiter characters between a delimiter and a starting enclosure.

                        // At this point in the code, the split shouldn't start with the enclosure, so add a delimiter
                        concatSplit.append(delimiter);

                        // Add the current split to the concatenated split
                        concatSplit.append(currentSplit);

                        // If the number of enclosures is odd, the enclosure is closed so add the split to the list
                        // and reset the "concatSplit" buffer. Otherwise continue
                        addSplit = oddNumberOfEnclosures;
                    }
                    if (addSplit) {
                        splitList.add(concatSplit.toString());
                        concatSplit = null;
                        addSplit = false;
                    }
                }
            }
        }

        // Return list as array
        return splitList.toArray(new String[splitList.size()]);
    }

    /**
     * Sorts the array of Strings, determines the uniquely occurring strings.
     *
     * @param strings
     *          the array that you want to do a distinct on
     * @return a sorted array of uniquely occurring strings
     */
    public static final String[] getDistinctStrings(String[] strings) {
        if (strings == null) {
            return null;
        }
        if (strings.length == 0) {
            return new String[] {};
        }

        String[] sorted = sortStrings(strings);
        List<String> result = new ArrayList<String>();
        String previous = "";
        for (int i = 0; i < sorted.length; i++) {
            if (!sorted[i].equalsIgnoreCase(previous)) {
                result.add(sorted[i]);
            }
            previous = sorted[i];
        }

        return result.toArray(new String[result.size()]);
    }

    /**
     * Returns a string of the stack trace of the specified exception
     */
    public static final String getStackTracker(Throwable e) {
        return getClassicStackTrace(e);
    }

    public static final String getClassicStackTrace(Throwable e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        e.printStackTrace(printWriter);
        String string = stringWriter.toString();
        try {
            stringWriter.close();
        } catch (IOException ioe) {
            // is this really required?
        }
        return string;
    }

    public static String getCustomStackTrace(Throwable aThrowable) {
        final StringBuilder result = new StringBuilder();
        String errorMessage = aThrowable.toString();
        result.append(errorMessage);
        if (!errorMessage.contains(Const.CR)) {
            result.append(CR);
        }

        // add each element of the stack trace
        //
        for (StackTraceElement element : aThrowable.getStackTrace()) {
            result.append(element);
            result.append(CR);
        }
        return result.toString();
    }

    /**
     * Check if the string supplied is empty. A String is empty when it is null or when the length is 0
     *
     * @param string
     *          The string to check
     * @return true if the string supplied is empty
     */
    public static final boolean isEmpty(String string) {
        return string == null || string.length() == 0;
    }

    /**
     * Check if the stringBuffer supplied is empty. A StringBuffer is empty when it is null or when the length is 0
     *
     * @param string
     *          The stringBuffer to check
     * @return true if the stringBuffer supplied is empty
     */
    public static final boolean isEmpty(StringBuffer string) {
        return string == null || string.length() == 0;
    }

    /**
     * Check if the string array supplied is empty. A String array is empty when it is null or when the number of elements
     * is 0
     *
     * @param strings
     *          The string array to check
     * @return true if the string array supplied is empty
     */
    public static final boolean isEmpty(String[] strings) {
        return strings == null || strings.length == 0;
    }

    /**
     * Check if the array supplied is empty. An array is empty when it is null or when the length is 0
     *
     * @param array
     *          The array to check
     * @return true if the array supplied is empty
     */
    public static final boolean isEmpty(Object[] array) {
        return array == null || array.length == 0;
    }

    /**
     * Check if the list supplied is empty. An array is empty when it is null or when the length is 0
     *
     * @param list
     *          the list to check
     * @return true if the supplied list is empty
     */
    public static final boolean isEmpty(List<?> list) {
        return list == null || list.size() == 0;
    }

    /**
     * @return a new ClassLoader
     */
    public static final ClassLoader createNewClassLoader() throws KettleException {
        try {
            // Nothing really in URL, everything is in scope.
            URL[] urls = new URL[] {};
            URLClassLoader ucl = new URLClassLoader(urls);

            return ucl;
        } catch (Exception e) {
            throw new KettleException("Unexpected error during classloader creation", e);
        }
    }

    /**
     * Utility class for use in JavaScript to create a new byte array. This is surprisingly difficult to do in JavaScript.
     *
     * @return a new java byte array
     */
    public static final byte[] createByteArray(int size) {
        return new byte[size];
    }

    /**
     * Sets the first character of each word in upper-case.
     *
     * @param string
     *          The strings to convert to initcap
     * @return the input string but with the first character of each word converted to upper-case.
     */
    public static final String initCap(String string) {
        StringBuffer change = new StringBuffer(string);
        boolean new_word;
        int i;
        char lower, upper, ch;

        new_word = true;
        for (i = 0; i < string.length(); i++) {
            lower = change.substring(i, i + 1).toLowerCase().charAt(0); // Lowercase is default.
            upper = change.substring(i, i + 1).toUpperCase().charAt(0); // Uppercase for new words.
            ch = upper;

            if (new_word) {
                change.setCharAt(i, upper);
            } else {
                change.setCharAt(i, lower);
            }

            new_word = false;

            // Cast to (int) is required for extended characters (SB)
            if (!Character.isLetterOrDigit((int) ch) && ch != '_') {
                new_word = true;
            }
        }

        return change.toString();
    }

    /**
     * Create a valid filename using a name We remove all special characters, spaces, etc.
     *
     * @param name
     *          The name to use as a base for the filename
     * @return a valid filename
     */
    public static final String createFilename(String name) {
        StringBuffer filename = new StringBuffer();
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (Character.isUnicodeIdentifierPart(c)) {
                filename.append(c);
            } else if (Character.isWhitespace(c)) {
                filename.append('_');
            }
        }
        return filename.toString().toLowerCase();
    }

    public static final String createFilename(String directory, String name, String extension) {
        if (directory.endsWith(Const.FILE_SEPARATOR)) {
            return directory + createFilename(name) + extension;
        } else {
            return directory + Const.FILE_SEPARATOR + createFilename(name) + extension;
        }
    }

    public static final String createName(String filename) {
        if (Const.isEmpty(filename)) {
            return filename;
        }

        String pureFilename = filenameOnly(filename);
        if (pureFilename.endsWith(".ktr") || pureFilename.endsWith(".kjb") || pureFilename.endsWith(".xml")) {
            pureFilename = pureFilename.substring(0, pureFilename.length() - 4);
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < pureFilename.length(); i++) {
            char c = pureFilename.charAt(i);
            if (Character.isUnicodeIdentifierPart(c)) {
                sb.append(c);
            } else if (Character.isWhitespace(c)) {
                sb.append(' ');
            } else if (c == '-') {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * <p>
     * Returns the pure filename of a filename with full path. E.g. if passed parameter is
     * <code>/opt/tomcat/logs/catalina.out</code> this method returns <code>catalina.out</code>. The method works with the
     * Environment variable <i>System.getProperty("file.separator")</i>, so on linux/Unix it will check for the last
     * occurrence of a frontslash, on windows for the last occurrence of a backslash.
     * </p>
     *
     * <p>
     * To make this OS independent, the method could check for the last occurrence of a frontslash and backslash and use
     * the higher value of both. Should work, since these characters aren't allowed in filenames on neither OS types (or
     * said differently: Neither linux nor windows can carry frontslashes OR backslashes in filenames). Just a suggestion
     * of an improvement ...
     * </p>
     *
     * @param sFullPath
     * @return
     */
    public static String filenameOnly(String sFullPath) {
        if (Const.isEmpty(sFullPath)) {
            return sFullPath;
        }

        int idx = sFullPath.lastIndexOf(FILE_SEPARATOR);
        if (idx != -1) {
            return sFullPath.substring(idx + 1);
        } else {
            idx = sFullPath.lastIndexOf('/'); // URL, VFS
            if (idx != -1) {
                return sFullPath.substring(idx + 1);
            } else {
                return sFullPath;
            }
        }

    }

    /**
     * Returning the internationalized tips of the days. They get created once on first request.
     *
     * @return
     */
    public static String[] getTips() {
        if (tips == null) {
            int tipsOfDayCount = toInt(BaseMessages.getString(PKG, "Const.TipOfDay.Count"), 0);
            tips = new String[tipsOfDayCount];
            for (int i = 1; i <= tipsOfDayCount; i++) {
                tips[i - 1] = BaseMessages.getString(PKG, "Const.TipOfDay" + Integer.toString(i));
            }
        }

        return tips;
    }

    /**
     * Returning the localized date conversion formats. They get created once on first request.
     *
     * @return
     */
    public static String[] getDateFormats() {
        if (dateFormats == null) {
            int dateFormatsCount = toInt(BaseMessages.getString(PKG, "Const.DateFormat.Count"), 0);
            dateFormats = new String[dateFormatsCount];
            for (int i = 1; i <= dateFormatsCount; i++) {
                dateFormats[i - 1] = BaseMessages.getString(PKG, "Const.DateFormat" + Integer.toString(i));
            }
        }
        return dateFormats;
    }

    /**
     * Returning the localized number conversion formats. They get created once on first request.
     *
     * @return
     */
    public static String[] getNumberFormats() {
        if (numberFormats == null) {
            int numberFormatsCount = toInt(BaseMessages.getString(PKG, "Const.NumberFormat.Count"), 0);
            numberFormats = new String[numberFormatsCount + 1];
            numberFormats[0] = DEFAULT_NUMBER_FORMAT;
            for (int i = 1; i <= numberFormatsCount; i++) {
                numberFormats[i] = BaseMessages.getString(PKG, "Const.NumberFormat" + Integer.toString(i));
            }
        }
        return numberFormats;
    }

    /**
     * @return An array of all default conversion formats, to be used in dialogs etc.
     */
    public static String[] getConversionFormats() {
        String[] dats = Const.getDateFormats();
        String[] nums = Const.getNumberFormats();
        int totsize = dats.length + nums.length;
        String[] formats = new String[totsize];
        for (int x = 0; x < dats.length; x++) {
            formats[x] = dats[x];
        }
        for (int x = 0; x < nums.length; x++) {
            formats[dats.length + x] = nums[x];
        }

        return formats;
    }

    public static String[] getTransformationAndJobFilterNames() {
        if (STRING_TRANS_AND_JOB_FILTER_NAMES == null) {
            STRING_TRANS_AND_JOB_FILTER_NAMES = new String[] {
                    BaseMessages.getString(PKG, "Const.FileFilter.TransformationJob"),
                    BaseMessages.getString(PKG, "Const.FileFilter.Transformations"),
                    BaseMessages.getString(PKG, "Const.FileFilter.Jobs"),
                    BaseMessages.getString(PKG, "Const.FileFilter.XML"),
                    BaseMessages.getString(PKG, "Const.FileFilter.All") };
        }
        return STRING_TRANS_AND_JOB_FILTER_NAMES;
    }

    public static String[] getTransformationFilterNames() {
        if (STRING_TRANS_FILTER_NAMES == null) {
            STRING_TRANS_FILTER_NAMES = new String[] {
                    BaseMessages.getString(PKG, "Const.FileFilter.Transformations"),
                    BaseMessages.getString(PKG, "Const.FileFilter.XML"),
                    BaseMessages.getString(PKG, "Const.FileFilter.All") };
        }
        return STRING_TRANS_FILTER_NAMES;
    }

    public static String[] getJobFilterNames() {
        if (STRING_JOB_FILTER_NAMES == null) {
            STRING_JOB_FILTER_NAMES = new String[] { BaseMessages.getString(PKG, "Const.FileFilter.Jobs"),
                    BaseMessages.getString(PKG, "Const.FileFilter.XML"),
                    BaseMessages.getString(PKG, "Const.FileFilter.All") };
        }
        return STRING_JOB_FILTER_NAMES;
    }

    /**
     * Return the current time as nano-seconds.
     *
     * @return time as nano-seconds.
     */
    public static long nanoTime() {
        return new Date().getTime() * 1000;
    }

    /**
     * Return the input string trimmed as specified.
     *
     * @param string
     *          String to be trimmed
     * @param trimType
     *          Type of trimming
     *
     * @return Trimmed string.
     */
    public static String trimToType(String string, int trimType) {
        switch (trimType) {
        case ValueMetaInterface.TRIM_TYPE_BOTH:
            return trim(string);
        case ValueMetaInterface.TRIM_TYPE_LEFT:
            return ltrim(string);
        case ValueMetaInterface.TRIM_TYPE_RIGHT:
            return rtrim(string);
        case ValueMetaInterface.TRIM_TYPE_NONE:
        default:
            return string;
        }
    }

    /**
     * implemented to help prevent errors in matching up pluggable LAF directories and paths/files eliminating malformed
     * URLs - duplicate file separators or missing file separators.
     *
     * @param dir
     * @param file
     * @return concatenated string representing a file url
     */
    public static String safeAppendDirectory(String dir, String file) {
        boolean dirHasSeparator = ((dir.lastIndexOf(FILE_SEPARATOR)) == dir.length());
        boolean fileHasSeparator = (file.indexOf(FILE_SEPARATOR) != 0);
        if ((dirHasSeparator && !fileHasSeparator) || (!dirHasSeparator && fileHasSeparator)) {
            return dir + file;
        }
        if (dirHasSeparator && fileHasSeparator) {
            return dir + file.substring(1);
        }
        return dir + FILE_SEPARATOR + file;
    }

    /**
     * Create an array of Strings consisting of spaces. The index of a String in the array determines the number of spaces
     * in that string.
     *
     * @return array of 'space' Strings.
     */
    public static String[] getEmptyPaddedStrings() {
        if (emptyPaddedSpacesStrings == null) {
            emptyPaddedSpacesStrings = new String[250];
            for (int i = 0; i < emptyPaddedSpacesStrings.length; i++) {
                emptyPaddedSpacesStrings[i] = rightPad("", i);
            }
        }
        return emptyPaddedSpacesStrings;
    }

    /**
     * Return the percentage of free memory for this JVM.
     *
     * @return Percentage of free memory.
     */
    public static final int getPercentageFreeMemory() {
        Runtime runtime = Runtime.getRuntime();
        long maxMemory = runtime.maxMemory();
        long allocatedMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long totalFreeMemory = (freeMemory + (maxMemory - allocatedMemory));

        int percentage = (int) Math.round(100 * (double) totalFreeMemory / maxMemory);

        return percentage;
    }

    /**
     * Return non digits only.
     *
     * @return non digits in a string.
     */

    public static String removeDigits(String input) {
        if (Const.isEmpty(input)) {
            return null;
        }
        StringBuffer digitsOnly = new StringBuffer();
        char c;
        for (int i = 0; i < input.length(); i++) {
            c = input.charAt(i);
            if (!Character.isDigit(c)) {
                digitsOnly.append(c);
            }
        }
        return digitsOnly.toString();
    }

    /**
     * Return digits only.
     *
     * @return digits in a string.
     */
    public static String getDigitsOnly(String input) {
        if (Const.isEmpty(input)) {
            return null;
        }
        StringBuffer digitsOnly = new StringBuffer();
        char c;
        for (int i = 0; i < input.length(); i++) {
            c = input.charAt(i);
            if (Character.isDigit(c)) {
                digitsOnly.append(c);
            }
        }
        return digitsOnly.toString();
    }

    /**
     * Remove time from a date.
     *
     * @return a date without hour.
     */
    public static Date removeTimeFromDate(Date input) {
        if (input == null) {
            return null;
        }
        // Get an instance of the Calendar.
        Calendar calendar = Calendar.getInstance();

        // Make sure the calendar will not perform automatic correction.
        calendar.setLenient(false);

        // Set the time of the calendar to the given date.
        calendar.setTime(input);

        // Remove the hours, minutes, seconds and milliseconds.
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        // Return the date again.
        return calendar.getTime();
    }

    /**
     * Escape XML content. i.e. replace characters with &values;
     *
     * @param content
     *          content
     * @return escaped content
     */
    public static String escapeXML(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return StringEscapeUtils.escapeXml(content);
    }

    /**
     * Escape HTML content. i.e. replace characters with &values;
     *
     * @param content
     *          content
     * @return escaped content
     */
    public static String escapeHtml(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return StringEscapeUtils.escapeHtml(content);
    }

    /**
     * UnEscape HTML content. i.e. replace characters with &values;
     *
     * @param content
     *          content
     * @return unescaped content
     */
    public static String unEscapeHtml(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return StringEscapeUtils.unescapeHtml(content);
    }

    /**
     * UnEscape XML content. i.e. replace characters with &values;
     *
     * @param content
     *          content
     * @return unescaped content
     */
    public static String unEscapeXml(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return StringEscapeUtils.unescapeXml(content);
    }

    /**
     * Escape SQL content. i.e. replace characters with &values;
     *
     * @param content
     *          content
     * @return escaped content
     */
    public static String escapeSQL(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return StringEscapeUtils.escapeSql(content);
    }

    /**
     * Remove CR / LF from String
     *
     * @param in
     *          input
     * @return cleaned string
     */
    public static String removeCRLF(String in) {
        char[] inArray = in.toCharArray();
        StringBuffer out = new StringBuffer(inArray.length);
        for (int i = 0; i < inArray.length; i++) {
            char c = inArray[i];
            if (!(c == '\n' || c == '\r')) {
                out.append(c);
            }
        }
        return out.toString();
    }

    /**
     * Remove CR / LF from String
     *
     * @param in
     *          input
     * @return cleaned string
     */
    public static String removeCR(String in) {
        char[] inArray = in.toCharArray();
        StringBuffer out = new StringBuffer(inArray.length);
        for (int i = 0; i < inArray.length; i++) {
            char c = inArray[i];
            if (c != '\n') {
                out.append(c);
            }
        }
        return out.toString();
    } // removeCR

    /**
     * Remove CR / LF from String
     *
     * @param in
     *          input
     * @return cleaned string
     */
    public static String removeLF(String in) {
        char[] inArray = in.toCharArray();
        StringBuffer out = new StringBuffer(inArray.length);
        for (int i = 0; i < inArray.length; i++) {
            char c = inArray[i];
            if (c != '\r') {
                out.append(c);
            }
        }
        return out.toString();
    } // removeCRLF

    /**
     * Remove Horizontan Tab from String
     *
     * @param in
     *          input
     * @return cleaned string
     */
    public static String removeTAB(String in) {
        char[] inArray = in.toCharArray();
        StringBuffer out = new StringBuffer(inArray.length);
        for (int i = 0; i < inArray.length; i++) {
            char c = inArray[i];
            if (c != '\t') {
                out.append(c);
            }
        }
        return out.toString();
    }

    /**
     * Add time to an input date
     *
     * @param input
     *          the date
     * @param time
     *          the time to add (in string)
     * @param DateFormat
     *          the time format
     * @return date = input + time
     */
    public static Date addTimeToDate(Date input, String time, String DateFormat) throws Exception {
        if (isEmpty(time)) {
            return input;
        }
        if (input == null) {
            return null;
        }
        String dateformatString = NVL(DateFormat, "HH:mm:ss");
        int t = decodeTime(time, dateformatString);
        Date d = new Date(input.getTime() + t);
        return d;
    }

    // Decodes a time value in specified date format and returns it as milliseconds since midnight.
    public static int decodeTime(String s, String DateFormat) throws Exception {
        SimpleDateFormat f = new SimpleDateFormat(DateFormat);
        TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
        f.setTimeZone(utcTimeZone);
        f.setLenient(false);
        ParsePosition p = new ParsePosition(0);
        Date d = f.parse(s, p);
        if (d == null) {
            throw new Exception("Invalid time value " + DateFormat + ": \"" + s + "\".");
        }
        return (int) d.getTime();
    }

    /**
     * Get the number of occurances of searchFor in string.
     *
     * @param string
     *          String to be searched
     * @param searchFor
     *          to be counted string
     * @return number of occurances
     */
    public static int getOccurenceString(String string, String searchFor) {
        if (string == null || string.length() == 0) {
            return 0;
        }
        int counter = 0;
        int len = searchFor.length();
        if (len > 0) {
            int start = string.indexOf(searchFor);
            while (start != -1) {
                start = string.indexOf(searchFor, start + len);
            }
        }
        return counter;
    }

    public static String[] GetAvailableFontNames() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Font[] fonts = ge.getAllFonts();
        String[] FontName = new String[fonts.length];
        for (int i = 0; i < fonts.length; i++) {
            FontName[i] = fonts[i].getFontName();
        }
        return FontName;
    }

    public static String getKettlePropertiesFileHeader() {
        StringBuilder out = new StringBuilder();

        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line01",
                BuildVersion.getInstance().getVersion()) + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line02") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line03") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line04") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line05") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line06") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line07") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line08") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line09") + CR);
        out.append(BaseMessages.getString(PKG, "Props.Kettle.Properties.Sample.Line10") + CR);

        return out.toString();
    }

    /**
     * Mask XML content. i.e. protect with CDATA;
     *
     * @param content
     *          content
     * @return protected content
     */
    public static String protectXMLCDATA(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return "<![CDATA[" + content + "]]>";
    }

    /**
     * Get the number of occurances of searchFor in string.
     *
     * @param string
     *          String to be searched
     * @param searchFor
     *          to be counted string
     * @return number of occurances
     */
    public static int getOcuranceString(String string, String searchFor) {
        if (string == null || string.length() == 0) {
            return 0;
        }
        Pattern p = Pattern.compile(searchFor);
        Matcher m = p.matcher(string);
        int count = 0;
        while (m.find()) {
            ++count;
        }
        return count;
    }

    /**
     * Mask XML content. i.e. replace characters with &values;
     *
     * @param content
     *          content
     * @return masked content
     */
    public static String escapeXml(String content) {
        if (isEmpty(content)) {
            return content;
        }
        return StringEscapeUtils.escapeXml(content);
    }

    public static String Lpad(String valueToPad, String filler, int size) {
        if (size == 0) {
            return valueToPad;
        }
        while (valueToPad.length() < size) {
            valueToPad = filler + valueToPad;
        }
        return valueToPad;
    }

    public static String Rpad(String valueToPad, String filler, int size) {
        if (size == 0) {
            return valueToPad;
        }
        while (valueToPad.length() < size) {
            valueToPad = valueToPad + filler;
        }
        return valueToPad;
    }

    public static boolean classIsOrExtends(Class<?> clazz, Class<?> superClass) {
        if (clazz.equals(Object.class)) {
            return false;
        }
        if (clazz.equals(superClass)) {
            return true;
        }
        return classIsOrExtends(clazz.getSuperclass(), superClass);
    }
}