org.jahia.configuration.configurators.PropertiesManager.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.configuration.configurators.PropertiesManager.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2018 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 */
package org.jahia.configuration.configurators;

import java.io.*;
import java.util.*;

import org.apache.commons.io.IOUtils;
import org.codehaus.plexus.util.StringUtils;

/**
 * desc:  This class provides to you a *super interface* for properties.
 * It allows you to create a new properties file, set properties, remove
 * properties, get properties, get properties from an existing properties
 * object or from a flat file, get a complete properties object, and you can
 * store the new properties object where you want on the filesystem. The store
 * method keep your base properties file design (not like the store() method
 * from java properties object)!
 * <p/>
 *
 * @author Alexandre Kraft
 * @version 1.0
 */
public class PropertiesManager {
    private Map<String, Object> properties = new LinkedHashMap<String, Object>();
    private Set<String> modifiedProperties = new HashSet<String>();
    private boolean loadedFromInputStream = false;
    private Set<String> removedPropertyNames = new HashSet<String>();
    private boolean unmodifiedCommentingActivated = false;
    private String additionalPropertiesMessage = "The following properties were added.";
    private Map<String, PropertyLayout> propertyLayouts = new LinkedHashMap<String, PropertyLayout>();
    boolean additionalMessageAdded = false;
    private boolean replaceTabsWithSpaces = true;
    private boolean sanitizeValue = true;

    public class PropertyLayout {
        List<String> comments = new ArrayList<String>();
        String name;
        String nameLine;
        String separator = "=";
        List<String> valueLines = new ArrayList<String>();

        public PropertyLayout() {
        }

        public List<String> getComments() {
            return comments;
        }

        public void setComments(List<String> comments) {
            this.comments = comments;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getValue() {
            StringBuilder valueBuilder = new StringBuilder();
            for (String valueLine : valueLines) {
                valueBuilder.append(StringUtils.stripStart(unescapeNonASCII(valueLine), null));
            }
            return valueBuilder.toString();
        }

        public String getNameLine() {
            return nameLine;
        }

        public void setNameLine(String nameLine) {
            this.nameLine = nameLine;
        }

        public String getSeparator() {
            return separator;
        }

        public void setSeparator(String separator) {
            this.separator = separator;
        }

        public List<String> getValueLines() {
            return valueLines;
        }

        public void setValueLines(List<String> valueLines) {
            this.valueLines = valueLines;
        }
    }

    /**
     * Default constructor.
     *
     */
    public PropertiesManager() {
        // do nothing :o)
    } // end constructor

    /**
     * Construct a properties manager from an existing input stream that references a properties file.
     * @param sourcePropertiesInputStream
     * @throws IOException
     */
    public PropertiesManager(InputStream sourcePropertiesInputStream) throws IOException {
        this.loadProperties(sourcePropertiesInputStream);
    } // end constructor

    /**
     * Default constructor.
     *
     * @param properties The properties object used to define base properties.
     */
    public PropertiesManager(Properties properties) {
        this.properties.clear();
        for (String propertyName : properties.stringPropertyNames()) {
            setProperty(propertyName, properties.getProperty(propertyName));
        }
    } // end constructor

    /**
     * Load a complete properties file in memory by its filename.
     *
     * @param sourcePropertiesInputStream
     */
    private void loadProperties(InputStream sourcePropertiesInputStream) throws IOException {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(sourcePropertiesInputStream, "ISO-8859-1"));

        // parse the file...
        String currentLine = null;
        boolean inMultilineValue = false;
        PropertyLayout currentPropertyLayout = new PropertyLayout();
        while ((currentLine = reader.readLine()) != null) {
            int valueSeparatorPos = -1;
            if (!inMultilineValue) {
                valueSeparatorPos = getValueSeparatorPos(currentLine);
            }
            if (inMultilineValue && currentLine.trim().equals("")) {
                if (currentPropertyLayout.getValueLines().size() > 0) {
                    currentPropertyLayout.getValueLines().add(currentLine);
                    properties.put(currentPropertyLayout.getName(), currentPropertyLayout.getValue());
                    propertyLayouts.put(currentPropertyLayout.getName(), currentPropertyLayout);
                    currentPropertyLayout = new PropertyLayout();
                }
                inMultilineValue = false;
                continue;
            }
            if (currentLine.trim().equals("") || currentLine.trim().startsWith("#")
                    || currentLine.trim().startsWith("!")) {
                // empty natural line or comment line, we just skip it
                currentPropertyLayout.getComments().add(currentLine);
                continue;
            }
            if (valueSeparatorPos >= 0) {
                if (currentPropertyLayout.getNameLine() != null) {
                    if (currentPropertyLayout.getValueLines().size() > 0) {
                        properties.put(currentPropertyLayout.getName(), currentPropertyLayout.getValue());
                        propertyLayouts.put(currentPropertyLayout.getName(), currentPropertyLayout);
                        currentPropertyLayout = new PropertyLayout();
                    }
                }
                currentPropertyLayout.setNameLine(unescapeNonASCII(currentLine.substring(0, valueSeparatorPos)));
                currentPropertyLayout.setSeparator(currentLine.substring(valueSeparatorPos, valueSeparatorPos + 1));
                currentPropertyLayout.setName(currentPropertyLayout.getNameLine().trim());
                String lastPropertyLine = unescapeNonASCII(currentLine.substring(valueSeparatorPos + 1));
                if (lastPropertyLine == null) {
                    lastPropertyLine = "";
                }
                if (lastPropertyLine.trim().endsWith("\\")) {
                    inMultilineValue = true;
                    lastPropertyLine = StringUtils.stripEnd(lastPropertyLine, "\\");
                    currentPropertyLayout.getValueLines().add(lastPropertyLine);
                } else {
                    currentPropertyLayout.getValueLines().add(lastPropertyLine);
                    properties.put(currentPropertyLayout.getName(), currentPropertyLayout.getValue());
                    propertyLayouts.put(currentPropertyLayout.getName(), currentPropertyLayout);
                    inMultilineValue = false;
                    currentPropertyLayout = new PropertyLayout();
                }
            } else {
                if (inMultilineValue) {
                    if (currentLine.trim().endsWith("\\")) {
                        // multi-value is continuing
                        currentLine = unescapeNonASCII(StringUtils.stripEnd(currentLine, "\\"));
                        currentPropertyLayout.getValueLines().add(currentLine);
                    } else {
                        // multi-value is (probably) stopped
                        currentPropertyLayout.getValueLines().add(unescapeNonASCII(currentLine));
                        properties.put(currentPropertyLayout.getName(), currentPropertyLayout.getValue());
                        propertyLayouts.put(currentPropertyLayout.getName(), currentPropertyLayout);
                        inMultilineValue = false;
                        currentPropertyLayout = new PropertyLayout();
                    }
                } else {
                    currentPropertyLayout.setNameLine(unescapeNonASCII(currentLine));
                    currentPropertyLayout.setName(currentPropertyLayout.getNameLine().trim());
                    properties.put(currentPropertyLayout.getName(), "");
                    propertyLayouts.put(currentPropertyLayout.getName(), currentPropertyLayout);
                    inMultilineValue = false;
                    currentPropertyLayout = new PropertyLayout();
                }
            }
        }

        IOUtils.closeQuietly(reader);
        loadedFromInputStream = true;
    }

    /**
     * Get a property value by its name.
     *
     * @param propertyName The property name to get its value.
     * @return Returns a String containing the value of the property name.
     */
    public String getProperty(String propertyName) {
        return toString(properties.get(propertyName));
    } // end getProperty

    public Object getRawProperty(String propertyName) {
        return properties.get(propertyName);
    }

    /**
     * Set a property value by its name.
     *
     * @param propertyName The property name to set.
     * @param propvalue    The property value to set.
     */
    public void setProperty(String propertyName, String propvalue) {
        String oldValue = (String) properties.put(propertyName, propvalue);
        if (!StringUtils.equals(oldValue, propvalue)) {
            // set "modified" flag only if the value was effectively changed
            modifiedProperties.add(propertyName);
        }
        PropertyLayout propertyLayout = propertyLayouts.get(propertyName);
        if (propertyLayout == null) {
            propertyLayout = new PropertyLayout();
            propertyLayout.setName(propertyName);
            propertyLayout.setNameLine(propertyName);
            if (getAdditionalPropertiesMessage() != null && !additionalMessageAdded) {
                propertyLayout.getComments().add("# " + getAdditionalPropertiesMessage());
                additionalMessageAdded = true;
            }
        }
        propertyLayout.getValueLines().clear();
        propertyLayout.getValueLines().add(" " + propvalue);
        propertyLayouts.put(propertyName, propertyLayout);
    } // end setProperty

    public void setProperty(String propertyName, Object[] propertyValues) {
        Object oldValue = properties.put(propertyName, propertyValues);
        String oldValueString = toString(oldValue);
        String newValueString = toString(propertyValues);
        if (!StringUtils.equals(oldValueString, newValueString)) {
            // set "modified" flag only if the value was effectively changed
            modifiedProperties.add(propertyName);
        }
        PropertyLayout propertyLayout = propertyLayouts.get(propertyName);
        if (propertyLayout == null) {
            propertyLayout = new PropertyLayout();
            propertyLayout.setName(propertyName);
            propertyLayout.setNameLine(propertyName);
            if (getAdditionalPropertiesMessage() != null && !additionalMessageAdded) {
                propertyLayout.getComments().add("# " + getAdditionalPropertiesMessage());
                additionalMessageAdded = true;
            }
        }
        propertyLayout.getValueLines().clear();
        int i = 0;
        for (Object propertyValue : propertyValues) {
            if (i == 0) {
                propertyLayout.getValueLines().add(" " + propertyValue.toString());
            } else {
                propertyLayout.getValueLines().add("  " + propertyValue.toString());
            }
        }
        propertyLayouts.put(propertyName, propertyLayout);
    }

    /**
     * Remove a property by its name.
     *
     * @param propertyName The property name to remove.
     * @author Alexandre Kraft
     */
    public void removeProperty(String propertyName) {
        properties.remove(propertyName);
        propertyLayouts.remove(propertyName);
        removedPropertyNames.add(propertyName);

    } // end removeProperty

    /**
     * Store new properties and values in the properties file.
     * If the file where you want to write doesn't exists, the file is created.
     *
     * @param sourcePropertiesInputStream The source input stream used to load the properties from, or null if it
     * doesn't exist.
     * @param destPropertiesFilePath   The filesystem path where the file is saved.
     * @author Alexandre Kraft
     * @author Khue N'Guyen
     */
    public void storeProperties(InputStream sourcePropertiesInputStream, String destPropertiesFilePath)
            throws IOException {
        if (loadedFromInputStream && sourcePropertiesInputStream == null) {
            throw new UnsupportedOperationException(
                    "If loaded from an input stream, it must be provided when storing the file");
        }
        List<String> outputLineList = new ArrayList<String>();

        File propertiesFileObject = new File(destPropertiesFilePath);
        File propertiesFileFolder = propertiesFileObject.getParentFile();

        // check if the destination folder exists and create it if needed...
        if (!propertiesFileFolder.exists()) {
            propertiesFileFolder.mkdirs();
            propertiesFileFolder = null;
        }

        if (loadedFromInputStream) {
            for (Map.Entry<String, PropertyLayout> propertyLayoutEntry : propertyLayouts.entrySet()) {
                PropertyLayout value = propertyLayoutEntry.getValue();
                for (String comment : value.getComments()) {
                    outputLineList.add(replaceTabsWithSpaces(comment));
                }
                int i = 0;
                List<String> valueLines = value.getValueLines();
                int size = valueLines.size();
                if (size == 0) {
                    outputLineList.add(value.getNameLine());
                }
                for (String valueLine : valueLines) {
                    StringBuilder lineBuilder = new StringBuilder();
                    if (unmodifiedCommentingActivated
                            && !modifiedProperties.contains(propertyLayoutEntry.getKey())) {
                        lineBuilder.append("#");
                    }
                    if (i == 0) {
                        lineBuilder.append(escapeNonASCII(value.getNameLine(), true));
                        lineBuilder.append(value.getSeparator());
                    }
                    lineBuilder.append(escapeNonASCII(i == 0 ? sanitizeValue(valueLine) : valueLine, false));
                    if (size > 1 && i < size - 1) {
                        lineBuilder.append("\\");
                    }
                    outputLineList.add(replaceTabsWithSpaces(lineBuilder.toString()));
                    i++;
                }
            }

            // write the file...
            writeTheFile(destPropertiesFilePath, outputLineList);
        } else {
            FileOutputStream outputStream = new FileOutputStream(destPropertiesFilePath);
            getPropertiesObject().store(outputStream, "This file has been written by Jahia.");
            outputStream.close();
        }
    }

    public void outputProperty(List<String> outputLineList, String currentLine, Set<String> remainingPropertyNames,
            String currentPropertyName, int equalPosition) {
        if (remainingPropertyNames.contains(currentPropertyName)) {
            Object propValue = properties.get(currentPropertyName);
            remainingPropertyNames.remove(currentPropertyName);
            StringBuilder lineBuilder = new StringBuilder();
            if (modifiedProperties.contains(currentPropertyName)) {
                lineBuilder.append(escapeNonASCII(currentLine.substring(0, equalPosition + 1), true));
                lineBuilder.append(" ");
                lineBuilder.append(escapeValue(currentPropertyName, propValue));
            } else {
                if (unmodifiedCommentingActivated) {
                    // this property was not modified, we comment it out
                    lineBuilder.append("#");
                    lineBuilder.append(escapeNonASCII(currentLine.substring(0, equalPosition + 1), true));
                    lineBuilder.append(" ");
                    lineBuilder.append(escapeValue(currentPropertyName, propValue));
                } else {
                    // property was not modified and commenting is not activated, we use the original line(s)
                    lineBuilder.append(currentLine);
                }
            }
            outputLineList.add(lineBuilder.toString());
        } else if (removedPropertyNames.contains(currentPropertyName)) {
            // the property must be removed from the file, we do not add it to the output.
        }
    }

    /**
     * Write the file composed by the storeProperties() method, using
     *
     * @param propertiesFilePath The filesystem path where the file is saved.
     * @param bufferList         List containing all the string lines of the new file.
     * @author Alexandre Kraft
     */
    private void writeTheFile(String propertiesFilePath, List<String> bufferList) {

        File thisFile = null;
        FileWriter fileWriter = null;
        StringBuffer outputBuffer = null;

        try {
            thisFile = new File(propertiesFilePath);
            fileWriter = new FileWriter(thisFile);
            outputBuffer = new StringBuffer();

            for (int i = 0; i < bufferList.size(); i++) {
                outputBuffer.append((String) bufferList.get(i));
                outputBuffer.append("\n");
            }

            fileWriter.write(outputBuffer.toString());
        } catch (java.io.IOException ioe) {
        } finally {
            try {
                fileWriter.close();
            } catch (java.io.IOException ioe2) {
            }
            fileWriter = null;
            thisFile = null;
        }
    } // end writeTheFile

    /**
     * Get the properties object for the instance of this class.
     *
     * @return The properties object for the instance of this class.
     * @author Alexandre Kraft
     */
    public Properties getPropertiesObject() {
        Properties propertiesObject = new Properties();
        for (Map.Entry<String, Object> propertiesEntry : properties.entrySet()) {
            propertiesObject.put(propertiesEntry.getKey(), toString(propertiesEntry.getValue()));
        }
        return propertiesObject;
    } // end getPropertiesObject

    public boolean isUnmodifiedCommentingActivated() {
        return unmodifiedCommentingActivated;
    }

    /**
     * If activated, non-modified properties will be commented out when saving the modifications
     * @param unmodifiedCommentingActivated
     */
    public void setUnmodifiedCommentingActivated(boolean unmodifiedCommentingActivated) {
        this.unmodifiedCommentingActivated = unmodifiedCommentingActivated;
    }

    public String getAdditionalPropertiesMessage() {
        return additionalPropertiesMessage;
    }

    /**
     * The message to use in a comment before the list of properties that were not present in the
     * original properties file and that were added dynamically. Set this message to null to avoid
     * the generation of this message.
     * @param additionalPropertiesMessage
     */
    public void setAdditionalPropertiesMessage(String additionalPropertiesMessage) {
        this.additionalPropertiesMessage = additionalPropertiesMessage;
    }

    private String toString(Object propertyValue) {
        if (propertyValue == null) {
            return null;
        }
        if (propertyValue instanceof String) {
            return (String) propertyValue;
        }
        if (propertyValue instanceof Object[]) {
            Object[] propertyValues = (Object[]) propertyValue;
            StringBuilder stringValues = new StringBuilder();
            int counter = 0;
            for (Object objectValue : propertyValues) {
                stringValues.append(objectValue.toString());
                if (counter < propertyValues.length - 1) {
                    stringValues.append(",");
                }
                counter++;
            }
            return stringValues.toString();
        } else {
            return propertyValue.toString();
        }
    }

    /**
     * Converts unicodes to encoded &#92;uxxxx and escapes
     * special characters with a preceding slash
     * 
     * @see Properties
     */
    private String escapeValue(String propertyName, Object propertyValue) {
        if (propertyValue == null) {
            return null;
        }
        if (propertyValue instanceof String) {
            String theString = (String) propertyValue;
            if (theString == null || theString.length() == 0 || !theString.contains("\\")) {
                return escapeNonASCII(theString, false);
            }
            return escapeNonASCII(StringUtils.replace(theString, "\\", "\\\\"), false);
        } else if (propertyValue instanceof Object[]) {
            StringBuilder valueString = new StringBuilder();
            Object[] objectValues = (Object[]) propertyValue;
            List<String> stringValues = new ArrayList<String>();
            long totalValueLength = 0;
            for (Object objectValue : objectValues) {
                String stringValue = objectValue.toString();
                totalValueLength += stringValue.length();
                stringValues.add(stringValue);
            }
            boolean multiLineSplit = false;
            if (totalValueLength > 50 || (propertyLayouts.get(propertyName) != null
                    && propertyLayouts.get(propertyName).getValueLines().size() > 1)) {
                multiLineSplit = true;
            }
            int counter = 0;
            for (String stringValue : stringValues) {
                valueString.append(stringValue);
                if (counter < stringValues.size() - 1) {
                    if (multiLineSplit) {
                        valueString.append(",\\\n    ");
                    } else {
                        valueString.append(",");
                    }
                }
                counter++;
            }
            return escapeNonASCII(valueString.toString(), false);
        } else {
            return escapeNonASCII(propertyValue.toString(), false);
        }
    }

    /**
     * @param input
     * @return
     */
    private String escapeNonASCII(String input, boolean encodeSeparators) {
        if (input == null || input.length() == 0 || input.trim().length() == 0) {
            return input;
        }
        StringBuilder output = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            char curChar = input.charAt(i);
            if ((int) curChar > 127) {
                // must escape using unicode
                output.append("\\u");
                output.append(String.format("%04X", (int) curChar));
            } else if ((int) curChar < 32) {
                switch (curChar) {
                case '\b':
                    output.append("\\b");
                    break;
                case '\n':
                    output.append("\\n");
                    break;
                case '\t':
                    // output.append("\\t");
                    output.append(curChar);
                    break;
                case '\f':
                    output.append("\\f");
                    break;
                case '\r':
                    output.append("\\r");
                    break;
                default:
                    output.append("\\u");
                    output.append(String.format("%04X", (int) curChar));
                }
            } else {
                switch (curChar) {
                case '"':
                    output.append('\\');
                    output.append('"');
                    break;
                case '\\':
                    output.append('\\');
                    output.append('\\');
                    break;
                case ':':
                case '=':
                    if (encodeSeparators) {
                        output.append("\\");
                        output.append(curChar);
                    } else {
                        output.append(curChar);
                    }
                    break;
                default:
                    output.append(curChar);
                    break;
                }
            }
        }
        return output.toString();
    }

    private String unescapeNonASCII(String input) {
        if (input == null || input.length() == 0 || input.trim().length() == 0) {
            return input;
        }
        StringBuilder output = new StringBuilder();
        boolean processingSlash = false;
        boolean processingUnicode = false;
        StringBuilder unicodeCharacters = new StringBuilder(4);
        for (int i = 0; i < input.length(); i++) {
            char ch = input.charAt(i);
            if (processingUnicode) {
                unicodeCharacters.append(ch);
                if (unicodeCharacters.length() == 4) {
                    try {
                        int value = Integer.decode("0x" + unicodeCharacters.toString());
                        output.append((char) value);
                        unicodeCharacters = new StringBuilder(4);
                        processingUnicode = false;
                        processingSlash = false;
                    } catch (NumberFormatException nfe) {
                        throw new IllegalArgumentException(
                                "Invalid unicode : " + unicodeCharacters + " in string : " + input, nfe);
                    }
                }
                continue;
            }

            if (processingSlash) {
                // handle an escaped value
                processingSlash = false;
                switch (ch) {
                case '\\':
                    output.append('\\');
                    break;
                case '\'':
                    output.append('\'');
                    break;
                case '\"':
                    output.append('"');
                    break;
                case 'r':
                    output.append('\r');
                    break;
                case 'f':
                    output.append('\f');
                    break;
                case 't':
                    output.append('\t');
                    break;
                case 'n':
                    output.append('\n');
                    break;
                case 'b':
                    output.append('\b');
                    break;
                case 'u':
                    processingUnicode = true;
                    break;
                default:
                    output.append(ch);
                    break;
                }
            } else if (ch == '\\') {
                processingSlash = true;
            } else {
                output.append(ch);
            }
        }

        if (processingSlash) {
            // then we're in the weird case of a \ at the end of the
            // string, let's output it anyway.
            output.append('\\');
        }

        return output.toString();
    }

    private int getValueSeparatorPos(String input) {
        if (input == null || input.length() == 0) {
            return -1;
        }
        int seperatorPos = getFirstSeperatorPos(input, "=");
        if (seperatorPos > -1) {
            return seperatorPos;
        }
        seperatorPos = getFirstSeperatorPos(input, ":");
        if (seperatorPos > -1) {
            return seperatorPos;
        }
        // now let's check if we have the case of a whitespace seperating the key from the value.
        int position = 0;
        while (Character.isWhitespace(input.charAt(position)) && !(input.charAt(position) == '\n')
                && !(input.charAt(position) == '\r') && position < input.length() - 1) {
            position++;
        }
        if (position == input.length() - 1) {
            // no starting whitespace found, the whole string is the key
            return -1;
        }
        while (!Character.isWhitespace(input.charAt(position)) && position < input.length() - 1) {
            position++;
        }
        if (position == input.length() - 1) {
            // no whitespace found after key
            return -1;
        }
        return position;
    }

    private int getFirstSeperatorPos(String input, String seperator) {
        int startPosition = 0;
        int seperatorPos = -1;
        while ((seperatorPos = input.indexOf(seperator, startPosition)) >= 0
                && startPosition < input.length() - 1) {
            if (seperatorPos > 0 && input.charAt(seperatorPos - 1) == '\\') {
                // this equals is escaped
                startPosition = seperatorPos + 1;
            } else {
                // we found the first proper equals sign
                if (seperatorPos >= 0) {
                    return seperatorPos;
                }
            }
        }
        return -1;
    }

    public void setReplaceTabsWithSpaces(boolean replaceTabsWithSpaces) {
        this.replaceTabsWithSpaces = replaceTabsWithSpaces;
    }

    public boolean isReplaceTabsWithSpaces() {
        return replaceTabsWithSpaces;
    }

    private String replaceTabsWithSpaces(String source) {
        return source != null && isReplaceTabsWithSpaces() ? StringUtils.replace(source, "\t", "    ") : source;
    }

    public boolean isSanitizeValue() {
        return sanitizeValue;
    }

    public void setSanitizeValue(boolean sanitizeValue) {
        this.sanitizeValue = sanitizeValue;
    }

    /**
     * Trims all the leading whitespaces from the value and just inserts one single space.
     * 
     * @param source
     *            the value to be sanitized
     * @return the sanitized value
     */
    private String sanitizeValue(String source) {
        return source != null && isSanitizeValue() ? (' ' + StringUtils.stripStart(source, null)) : source;
    }
}