All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.util.MessageFileProcessor Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.util;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintStream;

/**
 * This class is responsible for generating a Java enumeration source class
 * that can be referenced by throwers of NoSQLRuntimeException which need
 * standard error messages generated.  To affect changes in the generated Java
 * enumeration, follow the steps below;
 *
 * 1. Add a new message to the file
 *    kv/kvstore/resources/msgs/messages.properties.
 * 2. Run the ant target gen-messages this will produce the file
 *    kv/kvstore/src/oracle/kv/util/ErrorMessage.java.
 * 3. Pass the new enum that was generated (and any other message
 *    parameters) to the constructor of NoSQLRuntimeException.
 * 4. Check in your changes to messages.properties.
 */
public class MessageFileProcessor {
    public static final String MESSAGES_FILE_BASE_NAME = "messages";
    public static final String MESSAGES_FILE_SUFFIX = "properties";
    public static final String MESSAGES_FILE_NAME =
        MESSAGES_FILE_BASE_NAME + "." + MESSAGES_FILE_SUFFIX;

    private static final String ARG_CODELINE_BASEDIR = "-d";
    private static final String COMMENT_DELIMITER = "/";
    private static final String MESSAGE_LINE_DELIMETER = ",";
    private static final String LINE_SEP = "\n";
    private static final String FILE_SEP = System.getProperty("file.separator");
    private static final String OVERRIDE_TAG = "@Override";

    private static final String GEN_CODE_PKG_NAME = "oracle.kv.util";
    private static final String GEN_CODE_CLASS_NAME = "ErrorMessage";
    private static final String GENERATOR_CLASS_NAME = "MessageFileProcessor";
    private static final String ERR_CODE_ENUM_PREFIX = "NOSQL_";

    private static final String GEN_CODE_GET_VALUE_DECALARE =
        "public abstract int getValue();";
    private static final String GEN_CODE_GET_VALUE_METHOD =
        "public int getValue()";
    private static final String GEN_CODE_GET_ENUM_METHOD =
        "public static ErrorMessage getEnum(int value)";

    private static final String PATH_TO_MESSAGE_FILE = FILE_SEP +
        "resources" + FILE_SEP + "msgs" + FILE_SEP + MESSAGES_FILE_NAME;
    private static final String PATH_TO_GENERATED_JAVA_CLASS = FILE_SEP +
        "src" + FILE_SEP + "oracle" + FILE_SEP + "kv" + FILE_SEP +
        "util" + FILE_SEP + GEN_CODE_CLASS_NAME + ".java";
    private static final String PATH_TO_GENERATOR_JAVA_CLASS = FILE_SEP +
        "src" + FILE_SEP + "oracle" + FILE_SEP + "kv" + FILE_SEP +
        "util" + FILE_SEP + GENERATOR_CLASS_NAME + ".java";

    private static final String WARNING_COMMENT =
        "/*-\n" +
        " * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.\n" +
        " *\n" +
        " * This file was distributed by Oracle as part of a version of Oracle NoSQL\n" +
        " * Database made available at:\n" +
        " *\n" +
        " * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html\n" +
        " *\n" +
        " * Please see the LICENSE file included in the top-level directory of the\n" +
        " * appropriate version of Oracle NoSQL Database for a copy of the license and\n" +
        " * additional information.\n" +
        " */\n\n" +
        "//---------------------------------------------------------------------------\n" +
        "//--------         DO NOT EDIT THIS FILE DIRECTLY                 -----------\n" +
        "//--------         See kv.util.MessageFileProcessor.java          -----------\n" +
        "//---------------------------------------------------------------------------\n";

    private static String codelineBaseDir = null;

    public static void main(String args[]) {
        int i = 0;
        while (i < args.length) {
            if (args[i].equals(ARG_CODELINE_BASEDIR)) {
                if (args.length <= i) {
                    usageAndExit();
                }
                codelineBaseDir = args[i + 1];
                i += 2;
            } else {
                usageAndExit();
            }
            i++;
        }

        if (codelineBaseDir == null) {
            usageAndExit();
        }

        try {
            genJavaEnumFromMsgFile(codelineBaseDir + PATH_TO_MESSAGE_FILE,
                                   codelineBaseDir +
                                   PATH_TO_GENERATED_JAVA_CLASS,
                                   codelineBaseDir +
                                   PATH_TO_GENERATOR_JAVA_CLASS);
        } catch (IOException E) {
            System.out.println(E.toString());
            E.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * Generate Java source by parsing the messages file.
     *
     * @param msgFilePath A fully qualified path to the error messages file.
     *
     * @param generatedEnumSrcFilePath A fully qualified path to the Java
     * source file to be generated.  This file will be overwritten if it
     * already exists.
     *
     * @param msgFileProcessorFilePath this class source file path.
     *
     * @throws IOException on errors
     */
    private static void
        genJavaEnumFromMsgFile(final String msgFilePath,
                               final String generatedEnumSrcFilePath,
                               final String msgFileProcessorFilePath)
        throws IOException {

        /*
         * If source file has not changed since target was modified, do
         * nothing.
         */
        if (new File(generatedEnumSrcFilePath).lastModified() >
            new File(msgFilePath).lastModified() &&
            new File(generatedEnumSrcFilePath).lastModified() >
            new File(msgFileProcessorFilePath).lastModified()) {
            return;
        }

        /* Generate header of target Java source file. */
        final PrintStream ps = new PrintStream(generatedEnumSrcFilePath);
        ps.println(WARNING_COMMENT);
        ps.println("package " + GEN_CODE_PKG_NAME + ";" + LINE_SEP);
        ps.println("public enum " + GEN_CODE_CLASS_NAME + " {" + LINE_SEP);

        final LineNumberReader reader =
            new LineNumberReader(new FileReader(msgFilePath));
        try {
            String currLine = null;
            boolean firstLine = true;
            while ((currLine = reader.readLine()) != null)  {
                final String key =
                    getKey(currLine.trim(), reader.getLineNumber());
                if (key == null)  {
                    continue;
                }

                if (!firstLine) {
                    ps.println(",");
                } else {
                    firstLine = false;
                }
                /* Output message in a comment for easier development. */
                ps.println("\t// " +
                           getMessageForKey(key, currLine,
                                            reader.getLineNumber()));
                ps.print("\t" + ERR_CODE_ENUM_PREFIX + key + "() {" + LINE_SEP);
                ps.print(genGetValueMethodBlock("\t\t", key));
                ps.print("\t}");
            }
            ps.println(";" + LINE_SEP);
            ps.println("\t" + GEN_CODE_GET_VALUE_DECALARE);
            ps.println();
            ps.println(genGetEnumMethodBlock("\t"));
            ps.println("}");
            ps.flush();
            ps.close();
            System.out.println
                ("Successfully wrote " + generatedEnumSrcFilePath);
        } finally {
            reader.close();
        }
    }

    private static String genGetValueMethodBlock(String leftIndent,
                                                 String key) {
        return leftIndent + OVERRIDE_TAG + LINE_SEP +
               leftIndent + GEN_CODE_GET_VALUE_METHOD + " {" + LINE_SEP +
               leftIndent + "\treturn " + key + ";" + LINE_SEP +
               leftIndent + "}" + LINE_SEP;
    }

    private static String genGetEnumMethodBlock(String leftIndent) {
        return leftIndent + GEN_CODE_GET_ENUM_METHOD + " {" + LINE_SEP +
               leftIndent + "\tfor (ErrorMessage msg : values()) {" + LINE_SEP +
               leftIndent + "\t\tif (msg.getValue() == value) {" + LINE_SEP +
               leftIndent + "\t\t\treturn msg;" + LINE_SEP +
               leftIndent + "\t\t}" + LINE_SEP +
               leftIndent + "\t}" + LINE_SEP +
               leftIndent + "\tthrow new IllegalArgumentException(\"unknow value: \" + value);" + LINE_SEP +
               leftIndent + "}" + LINE_SEP;
    }

    /**
     * Retrieve the key from the message value from a line in the message file.
     *
     * @param currLine A line of text from the message file
     *
     * @param lineNumber The line number corresponding to the supplied currLine
     * parameter (used for exception messages).
     *
     * @return A string that is the key value from the message file.  If a
     * comment on the current line is encountered then this method will return
     * null.
     */
    public static String getKey(final String currLine, final int lineNumber) {

        final String[] tokens =  parseLine(currLine, lineNumber);
        return tokens != null ? tokens[0] : null;
    }

    /**
     * Retrieves the message from the message file that corresponds to the key
     *
     * @param requestedKey  The key to look up
     * @param ln Reader corresponding to the message file to scan
     * @return  The message associated with the supplied key or NULL if no
     *          message for the key is found
     * @throws IOException on error
     */
    public static String getMessageForKey(final String requestedKey,
                                          final LineNumberReader ln)
        throws IOException {

        String currLine = null;
        while ((currLine = ln.readLine()) != null)  {
            if ((currLine.length() == 0) ||
                (currLine.startsWith(COMMENT_DELIMITER)))   {
                continue;
            }

            final String msg = getMessageForKey(requestedKey, currLine.trim(),
                                                ln.getLineNumber());
            if (msg != null) {
                return msg;
            }
        }

        return null;
    }

    /**
     * Retrieves the message from the current line in the messages file that
     * corresponds to the key
     *
     * @param requestedKey The key to look up
     *
     * @param currLine The contents of a line from the messages file
     *
     * @param lineNumber The line number corresponding to currLine (used for
     * exceptions).
     *
     * @return The message associated with the supplied key or NULL if no
     * message for the key is found
     */
    private static String getMessageForKey(final String requestedKey,
                                           final String currLine,
                                           final int lineNumber) {

        final String[] pair = parseLine(currLine, lineNumber);
        if (pair[0].equals(requestedKey)) {
            return pair[1];
        }

        return null;
    }

    /**
     * Returns the tokens that have been parsed from a line of text from the
     * message file
     * @param currLine A line of text from the messages file
     * @param lineNumber The line number in the file corresponding to the
     *                   supplied line of text.  Used for exception messages.
     * @return An array of tokens in parse order otherwise NULL if the line is
     *         actually a comment line
     * @throws Exception on error
     */
    private static String[] parseLine(final String currLine,
                                      final int lineNumber)
        throws RuntimeException {

        if ((currLine.length() == 0) ||
            (currLine.startsWith(COMMENT_DELIMITER)))   {
            return null;
        }

        /**
         * The standard Oracle message file has exactly three tokens on
         * each non-commented line integer1, integer2, string
         * Where
         *    integer1 is the message ID
         *    integer2 is reserved and always 0
         *    string is the actual error message itself
         */
        final String[] msgTokens = currLine.split(MESSAGE_LINE_DELIMETER, 3);
        if (msgTokens.length < 3 ||
            msgTokens[0] == null ||
            msgTokens[0].length() == 0 ||
            msgTokens[2] == null ||
            msgTokens[2].length() == 0)  {
            throw new RuntimeException
                ("Unexpected message format at line " + lineNumber);
        }

        try {
            Integer.parseInt(msgTokens[0]);
        } catch (NumberFormatException e) {
            throw new RuntimeException
                ("Expected to find an integer as the first token on line " +
                 lineNumber + " but found '" + msgTokens[0] + "'");
        }

        final String[] ret = new String[2];
        ret[0] = msgTokens[0];

        /* Remove double quotes. */
        final String message = msgTokens[2].trim();
        if (!message.startsWith("\"")) {
            throw new RuntimeException
                ("Expected message on line " + lineNumber +
                 " to start with \" but found '" +
                 message.substring(0, 1) + "' instead.");
        }

        if (!message.endsWith("\"")) {
            throw new RuntimeException
                ("Expected message on line " + lineNumber +
                 " to end with \" but found '" +
                 message.substring(message.length() - 1) +
                 "' instead.");
        }

        ret[1] = message.substring(1, message.length() - 1);
        return ret;
    }

    private static void usageAndExit() {
        System.out.println("Usage: ");
        System.out.println("\t" + MessageFileProcessor.class.getName() + " " +
                           ARG_CODELINE_BASEDIR + " path_to_root_of_codeline");
        System.exit(-1);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy