ca.hec.commons.utils.MergePropertiesUtils.java Source code

Java tutorial

Introduction

Here is the source code for ca.hec.commons.utils.MergePropertiesUtils.java

Source

/******************************************************************************
 * $Id: $
 ******************************************************************************
 *
 * Copyright (c) 2013 The Sakai Foundation, The Sakai Quebec Team.
 *
 * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
 *
 * 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 ca.hec.commons.utils;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * Utility to merge properties during a sakai migration.
 * Example:
 * 
 *    > mergePropertiesHec pathNewPropertiesFile pathUpdatedValuesOldPropertiesFile pathOriginalValuesOldPropertiesFile
 * 
 * For migration from Sakai 2.8.1 to Sakai 2.9.1, the path would be:
 * 
 * - pathNewPropertiesFile : path to SAKAI 2.9.1 properties file
 * - pathUpdatedValuesOldPropertiesFile : path to SAKAI 2.8.1 MODIF properties file
 * - pathOriginalValuesOldPropertiesFile : path to SAKAI 2.8.1 properties file
 * 
 * 
 * The algorithm is :
 * 
 * - Properties in 'new' properties file are updated with value of the 'UpdatedValuesOld' properties file
 * - Properties that are present in 'UpdatedValuesOld' properties file but not in the other files are added at the end of the 'new' file: they are some personalizations we made during Sakai 2.8.1
 *        
 * The utility will send output to stdout, which you can copy and save in a properties file.
 *
 *
 *  NOte: Currently, the continuation lines (lines ending with \) 
 *        in 'new' are appended into a single line.*/
public class MergePropertiesUtils {

    public static void main(String[] args) throws Exception {

        String pathNewPropertiesFile = args[0]; //file path for Sakai 2.9
        String pathUpdatedValuesOldPropertiesFile = args[1]; //file path for Sakai 2.8 MODIF
        String pathOriginalValuesOldPropertiesFile = args[2]; //file path for Sakai 2.8 
        String pathEnValuesPropertiesFile = args[3]; //english file in SAKAI 2.9

        File newPropertiesFile = new File(pathNewPropertiesFile); //Sakai 2.9
        File updatedValuesOldPropertiesFile = new File(pathUpdatedValuesOldPropertiesFile); //Sakai 2.8 MODIF
        File originalValuesOldPropertiesFilesPath = new File(pathOriginalValuesOldPropertiesFile); //Sakai 2.8
        File enValuesPropertiesFile = new File(pathEnValuesPropertiesFile); //SAKAI2.9

        Properties newProps = load(newPropertiesFile); //Sakai 2.9
        Properties updatedProps = load(updatedValuesOldPropertiesFile); //Sakai 2.8 MODIF
        Properties originalProps = load(originalValuesOldPropertiesFilesPath); //Sakai 2.8 
        Properties enProps = load(enValuesPropertiesFile); //SAKAI 2.9

        importNewProps(enProps, newProps);

        /**
         * Output new properties (2.9.1) with updated values from updatedProps (2.8.1 MODIF)
         */
        merge(newPropertiesFile, updatedProps);

        /**
         * Output old properties (2.8.1 MODIF) that are not present in 2.8.1 or 2.9.1 (personalizations)
         */
        printNewProps(updatedValuesOldPropertiesFile, newProps, originalProps);

    }

    /**
     * Merge newProps to mainProps.
        
     * NOTE: The idea is that we want to write out the properties in exactly the same order, for future comparison purposes.
     * 
     * @param newPropertiesFile
     * @param newProps
     * @return nb of properties from main props file.
     */
    public static void merge(File newPropertiesFile, Properties updatedProps) throws Exception {
        /**
         * 1) Read line by line of the new PropertiesFile (Sakai 2.9.1)
         *  For each line: extract property
         *  check if we have a match one in the propToMerge 
         *    - if no, then rewrite the same line
         *    - if yes: - if same value, rewrite the same line 
         *              - if different value, rewrite the prop with the new value
         *              - For both cases, delete the key from newProperties.
         *              
         *  2) At the end, write the remaining list of propToMerge at
         *     the end of mainProp
         */

        try {

            int nbPropsInMain = 0;
            int nbPropsChanged = 0;
            int nbPropsSimilar = 0;
            int nbPropsNotInNew = 0;

            // Open the file that is the first
            // command line parameter
            FileInputStream fstream = new FileInputStream(newPropertiesFile);
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            LineWithContinuation lineIn;

            // Read File Line By Line
            while ((lineIn = LineWithContinuation.readLineWithContinuation(br)) != null) {

                KeyValue keyValue = extractKeyValue(lineIn.getFullLine());

                // May be a comment line, or blank line, or not a line containing key & property pair (we expect "key = value" line).
                // Simply echo the line back.
                if (keyValue == null) {
                    System.out.println(lineIn.getFullLine());
                    continue;
                }

                nbPropsInMain++;

                String key = keyValue.key;
                //System.out.println(key);

                String newValue = updatedProps.getProperty(key);
                String valueEscaped = unescapeJava(keyValue.value);

                if (newValue != null) {
                    if (!newValue.equals(valueEscaped)) {
                        String newLine = composeNewPropLine(key, StringEscapeUtils.escapeJava(newValue));
                        System.out.println(newLine);
                        nbPropsChanged++;
                    } else {
                        System.out.println(lineIn.getLineWithReturn());
                        nbPropsSimilar++;
                    }

                    // remove the key from newProps because it is used
                    updatedProps.remove(key);
                } else {
                    System.out.println(lineIn.getLineWithReturn());
                    nbPropsNotInNew++;
                }
            }

            // Close the input stream
            in.close();

            System.out.println("\n\n### " + nbPropsInMain + " properties in SAKAI 11 (" + nbPropsChanged
                    + " changed, " + nbPropsSimilar + " props with same value in both versions, " + nbPropsNotInNew
                    + " not in 2.9.1)");

        } catch (Exception e) {// Catch exception if any
            System.err.println("Error: " + e.getMessage());
            throw e;
        }
    }

    /**
     * Class for reading a line, taking care of the continuation symbol '\' at the end of the line.
     */
    private static class LineWithContinuation {
        private String fullLine; // the line fully rendered.
        private List<String> linesAsIs; // the original lines unchanged

        public LineWithContinuation(String fullLine, List<String> linesAsIs) {
            this.fullLine = fullLine;
            this.linesAsIs = linesAsIs;
        }

        public static LineWithContinuation readLineWithContinuation(BufferedReader br) throws IOException {
            StringBuffer lineAppended = new StringBuffer();
            List<String> linesAsIs = new ArrayList<String>();
            String line;

            while ((line = br.readLine()) != null) {
                boolean isComment = line.startsWith("#");
                boolean isContinued = !isComment && line.endsWith("\\"); // if comment, there is no continuation
                String lineClean = line.substring(0, isContinued ? line.length() - 1 : line.length());
                lineAppended.append(lineClean);
                linesAsIs.add(line);

                if (!isContinued) {
                    break;
                }
            }

            return line == null ? null : new LineWithContinuation(lineAppended.toString(), linesAsIs);
        }

        public String getLineWithReturn() {
            StringBuffer buf = new StringBuffer();
            for (int i = 0; i < linesAsIs.size(); i++) {
                boolean last = (i == linesAsIs.size() - 1);
                buf.append(linesAsIs.get(i) + (last ? "" : "\n"));
            }

            return buf.toString();
        }

        public String getFullLine() {
            return fullLine;
        }
    }

    private static void importNewProps(Properties enProperties, Properties frCAProperties) {
        Set<Object> enKeys = enProperties.keySet();
        System.out.println("#----Start importing-----");
        for (Object key : enKeys) {
            if (!frCAProperties.containsKey(key)) {
                System.out.println(key + "=---TO TRANSLATE ----" + enProperties.getProperty((String) key));
            }

        }

        System.out.println();
        System.out.println();
        System.out.println();
    }

    /**
     * Print the newprops.
     * Properties that are 'used' already are commented out.
     * 
     * NOTE: The idea is that we want to write out the properties in exactly the same order, for future comparison purposes.
     * 
     * @param newProps
     * @param remainingNewProps New properties that are left, i.e. remaining ones.
     */

    private static void printNewProps(File updatedValuesOldPropertiesFile, Properties newProps,
            Properties originalProps) {
        /**
         * Read new props line by line.
         * For each line, extract the prop key
         *   if it is not in the remaining, then it is used. So we write the line, but commented out
         *   otherwise, write the line as is.
         */

        System.out.println(
                "#########################################################################################");
        System.out.println(
                "####################### Personalizations  from 2.9.1-source-modif #######################");

        try {
            // Open the file that contains Sakai 2.8.1 modif personalizations
            FileInputStream fstream = new FileInputStream(updatedValuesOldPropertiesFile);
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine;

            // Read File Line By Line
            while ((strLine = br.readLine()) != null) {

                KeyValue keyValue = extractKeyValue(strLine);

                //if the line does not have the pattern key = value, we skip it 
                if (keyValue == null) {
                    continue;
                }

                String key = keyValue.key;

                boolean isNotUsedInNewVersion = (newProps.get(key) == null);
                boolean isNotUsedInOldVersion = (originalProps.get(key) == null);

                // Write out line only if property is a personalized property:it is not used in new version and was not used in old version
                if (isNotUsedInNewVersion && isNotUsedInOldVersion) {
                    System.out.println(strLine);
                }
            }
            // Close the input stream
            in.close();

        } catch (Exception e) {// Catch exception if any
            System.err.println("Error: " + e.getMessage());
        }
    }

    /**
     * Compose a line for a property, like 'prop1 = the small dog'.
     * @param key
     * @param value
     * @return
     */
    private static String composeNewPropLine(String key, String value) {
        return key + " = " + value;
    }

    private static String unescapeJava(String str) {
        return StringEscapeUtils.unescapeJava(str);
    }

    /**
     * @param strLine 
     * @return the key extracted. Example returns 'prop1' for input 'prop1 = the small dog'
     *    Null is returned if the line is empty, or is a comment line (i.e. starts with a #)
     */
    private static KeyValue extractKeyValue(String strLine) {
        String line = strLine.trim();

        if (line.startsWith("#")) {
            return null;
        }

        int indexOfEqual = line.indexOf('=');

        if (indexOfEqual < 0) {
            return null;
        }

        String key = line.substring(0, indexOfEqual);
        String value = line.substring(indexOfEqual + 1);

        return new KeyValue(key.trim(), StringUtils.stripStart(value, null)); // NOTE: Note that value is NOT trimmed to be similar to Properties.load().
        //return new KeyValue(key.trim(), value.trim()); // NOTE: Note that value is NOT trimmed to be similar to Properties.load().
    }

    /**
     * **********
     * Small KeyValue class.
     * **********
     */
    private static class KeyValue {
        public String key;
        public String value;

        public KeyValue(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    /**
     * Load a Properties File
     * 
     * @param propsFile
     * @return Properties
     * @throws IOException
     */
    public static Properties load(File propsFile) throws IOException {
        Properties props = new Properties();
        FileInputStream fis = new FileInputStream(propsFile);
        props.load(fis);
        fis.close();
        return props;
    }

}