de.rrze.idmone.utils.jidgen.IdGenerator.java Source code

Java tutorial

Introduction

Here is the source code for de.rrze.idmone.utils.jidgen.IdGenerator.java

Source

/*
 * jidgen, developed as a part of the IDMOne project at RRZE.
 * Copyright 2008, RRZE, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors. This
 * product includes software developed by the Apache Software Foundation
 * http://www.apache.org/
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package de.rrze.idmone.utils.jidgen;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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

import de.rrze.idmone.utils.jidgen.cli.IdGenOptions;
import de.rrze.idmone.utils.jidgen.filter.BlacklistFilter;
import de.rrze.idmone.utils.jidgen.filter.FilterChain;
import de.rrze.idmone.utils.jidgen.filter.PasswdFilter;
import de.rrze.idmone.utils.jidgen.filter.ShellCmdFilter;
import de.rrze.idmone.utils.jidgen.template.Template;

/**
 * class IdGenerator
 * 
 * 
 * 
 * 
 * <p>
 *       <b>Used external packages</b>
 *       <br />
 *       For further infos about the cli package see <a href="http://commons.apache.org/cli/">
 *       org.apache.commons.cli</a>.
 *       <br />
 *       For further infos about the logging package see 
 *       <a href="http://commons.apache.org/logging/commons-logging-1.1/index.html">
 *       org.apache.commons.logging</a>
 * </p>
 *  
 * @author unrza249
 * @author unrz205
 */
public class IdGenerator {
    /**
     *  The class logger
     */
    private static final Log logger = LogFactory.getLog(IdGenerator.class);

    /**
     * The options manager for IdGen 
     */
    private IdGenOptions options;

    /**
     * The option string before parsing
     */
    private String[] cliArgs;

    /**
     * Flag that indicates whether or not an update of the
     * options object is needed.
     * <b>Only used internally!</b>
     */
    private boolean updateOptions = true;

    /**
     * A filter chain for the IdGenerator<br />
     * This chain contains all filters that should be applied to
     * the generated ids.
     */
    private FilterChain filterChain;

    /**
     * Default constructor of the IdGenerator
     */
    public IdGenerator() {
        logger.trace("Invoked default constructor.");
        /*      
         // uncomment this for using an internally pre-defined configuration
         // together with the log4j logging system 
         try {
           // configure the log4j logging system from a external configuration file
           PropertyConfigurator.configure(this.getClass().getResource("/log4j.properties"));
        }
        catch (Exception e) {
           // if that doesn't work use this default configuration
           Properties props = new Properties();
           props.put("log4j.rootLogger", "WARN, A1");
           props.put("log4j.appender.A1", "org.apache.log4j.ConsoleAppender");
           props.put("log4j.appender.A1.layout", "org.apache.log4j.PatternLayout");
           props.put("log4j.appender.A1.layout.ConversionPattern", "%-4r [%t] %-5p %c %x - %m%n");
           PropertyConfigurator.configure(props);
        }
         */

        // create options definition for CLI/external library usage
        this.options = buildOptions();

        // create an empty filter chain
        this.filterChain = new FilterChain();
    }

    public IdGenerator(String[] args) {
        this();
        this.setCLIArgs(args);
        this.init();
    }

    public IdGenerator(String args) {
        this();
        this.setCLIArgs(args);
        this.init();
    }

    /**
     * Entry point of the program (CLI)
     * 
     * @param args
     *            the program arguments
     */
    public static void main(String[] args) {
        logger.info(Messages.getString("IdGenerator.WELCOME"));

        // create an instance of the IdGenerator and start the generation process
        IdGenerator generator = new IdGenerator();

        // pass on the CLI options array
        generator.setCLIArgs(args);

        // initialize the generator options manually
        // so we can print the usage on error
        if (!generator.init()) {
            generator.printUsage();
            System.exit(150);
        }

        /*
         * PROCESS CLI ONLY OPTIONS
         */

        // check for -h option or call with no options at all
        if (generator.options.hasOptionValue("h") || generator.options.getNum() == 0) {
            generator.printUsage();
            System.exit(0);
        }

        // check for -hh option
        if (generator.options.hasOptionValue("hh")) {
            generator.printHelp();
            System.exit(0);
        }

        // set number of ids
        if (generator.options.hasOptionValue("N")) {
            Globals.NUM_IDs = Integer.parseInt(generator.options.getOptionValue("N"));
            logger.trace("Set number of ids to generate to " + Globals.NUM_IDs + ".");
        }

        // enable column output
        if (generator.options.hasOptionValue("C")) {
            Globals.ENABLE_COLUMN_OUTPUT = true;
            logger.trace("Enable column output...");
        }

        // set terminal width
        if (generator.options.hasOptionValue("W")) {
            Globals.TERM_WIDTH = Integer.parseInt(generator.options.getOptionValue("W"));
            logger.trace("Set terminal width to " + Globals.TERM_WIDTH + ".");
        }

        /*
         * START WORKING
         */

        // generate ids
        List<String> ids = generator.generateIDs(Globals.NUM_IDs);

        // output the generated ids
        if (ids != null && !ids.isEmpty()) {
            logger.info(Messages.getString("IdGenerator.ID"));
            if (Globals.ENABLE_COLUMN_OUTPUT) {
                generator.printColumns(ids);
            } else {
                generator.print(ids);
            }
        }
    }

    /**
     * Init the IdGenerator object<br />
     * This got a seperate method, so that it can
     * be called more flexibly and also from the main
     * method for CLI usage.
     * 
     * @return true if no errors occurred, false otherwise 
     */
    private boolean init() {
        logger.trace("Init called.");
        // at first: update the options data if needed
        if (this.updateOptions) {
            this.updateOptions = false;

            if (!this.parseOptions(this.cliArgs)) {
                logger.error(Messages.getString("IdGenerator.ERROR_OPTIONS_UPDATE") + " "
                        + Arrays.toString(this.cliArgs));
                return false;
            }
        }

        logger.trace("Processing CLI arguments...");

        /*
         * FLAGS
         */

        /*
         * DATA
         */

        /*
         * FILTERS
         */
        // blacklist filter
        if (this.options.hasOptionValue("B")) {
            logger.trace("Enable blacklist filter...");
            BlacklistFilter bl = new BlacklistFilter();

            if (this.options.hasOptionValue("Bf")) {
                Globals.BLACKLIST_FILE = this.options.getOptionValue("Bf");
                logger.trace("Using ALTERNATE blacklist file (" + Globals.BLACKLIST_FILE + ").");
            } else {
                logger.trace("Using DEFAULT blacklist file (" + Globals.BLACKLIST_FILE + ").");
            }

            // read blacklist from file
            File file = new File(Globals.BLACKLIST_FILE);
            while (bl.addToBlacklist(file.getLine())) {
                // just loop
            }
            file.close();

            this.filterChain.addFilter(bl);
        }

        // passwd filter
        if (this.options.hasOptionValue("P")) {
            logger.trace("Enable passwd filter...");
            PasswdFilter passwd = new PasswdFilter();

            if (this.options.hasOptionValue("Pf")) {
                Globals.PASSWD_FILE = this.options.getOptionValue("Pf");
                logger.trace("Using ALTERNATE passwd file (" + Globals.PASSWD_FILE + ").");
            } else {
                logger.trace("Using DEFAULT passwd file (" + Globals.PASSWD_FILE + ").");
            }

            passwd.setFile(Globals.PASSWD_FILE);

            this.filterChain.addFilter(passwd);
        }

        // shellcmd filter
        if (this.options.hasOptionValue("S")) {
            logger.trace("Enable shellcmd filter...");
            ShellCmdFilter shellCmd = new ShellCmdFilter();

            if (this.options.hasOptionValue("Sf")) {
                Globals.SHELLCMD = this.options.getOptionValue("Sf");
                logger.trace("Using shell command (" + Globals.SHELLCMD + ").");
            } else {
                logger.trace("Using DEFAULT shell command (" + Globals.SHELLCMD + ").");
            }

            shellCmd.setCmd(Globals.SHELLCMD);

            this.filterChain.addFilter(shellCmd);
        }

        return true;
    }

    /**
     * Converts the given string into an array
     * and calls parseOptions(String[]).
     * 
     * @param args
     *          argument string
     * @return   true on success, false otherwise
     */
    private boolean parseOptions(String args) {
        String[] argsArr = args.split(" ");
        return this.parseOptions(argsArr);
    }

    /**
     * Fills the data array inside the options object
     * with the arguments specified in the array.
     *  
     * @param args
     *          the argument array
     * @return true on success, false otherwise
     */
    private boolean parseOptions(String[] args) {
        // parse the command line options and
        // fill the data array
        try {
            logger.trace("Parsing cliArgs...");
            this.options.parse(args);
        } catch (ParseException e) {
            logger.debug(e.toString());
            return false;
        } catch (NumberFormatException e) {
            logger.debug(e.toString());
            return false;
        }

        return true;
    }

    /**
     * Converts the given string into an array
     * and calls setCLIArgs(String[]).
     * 
     * @param args
     *          the argument string
     */
    public void setCLIArgs(String args) {
        logger.trace("Converting argument string to array...");
        String[] argsArr = args.split(" ");
        this.setCLIArgs(argsArr);
    }

    /**
     * Sets the stored argument array for
     * later parsing.<br />
     * An update of the data array can be 
     * done explicitly by calling the
     * updateOptions() method or automatically
     * on the next call to generateIds(int). 
     * 
     * @param args
     *          the new argument array
     */
    public void setCLIArgs(String[] args) {
        this.updateOptions = true;
        this.cliArgs = args;
        logger.trace("Set cliArgs to " + Arrays.toString(this.cliArgs));
    }

    /**
     * Updates everything that needs to be updated
     * to be up-to-date again.
     * <em>This also includes re-reading the blacklist
     * file.</em>
     * 
     * @return true on success, false otherwise
     */
    public boolean update() {
        this.filterChain.clear();
        return this.init();
    }

    /**
     * This method tries to generate the given number of ids. 
     * The method returns an empty list if it does 
     * not manage to create any suitable id within the <em>MAX_ATTEMPTS</em>
     * or null if an error occurs.
     * 
     * @param num
     *          target number of ids to generate
     * @return a suitable id list, an empty list if such could not be
     *         generated or null on error
     */
    public List<String> generateIDs(int num) {
        if (this.updateOptions) {
            this.update();
        }

        ArrayList<String> ids = new ArrayList<String>();

        logger.info(Messages.getString("IdGenerator.START_GENERATION") + num);

        Template template = new Template(this.options.getData());

        int i = 0;
        while (template.hasAlternatives() && (ids.size() < num)) {
            if (i++ == Globals.MAX_ATTEMPTS) {
                logger.fatal(
                        Messages.getString("IdGenerator.MAX_ATTEMPTS_REACHED") + " (" + Globals.MAX_ATTEMPTS + ")");
                System.exit(152);
            }
            String idCandidate = null;
            idCandidate = template.buildString();
            logger.trace(Messages.getString("IdGenerator.TRACE_ID_CANDIDATE") + " " + idCandidate);

            // apply the filter chain to the generated id
            // add to list if we got a valid, unique id 
            if ((this.filterChain.apply(idCandidate) != null) && (!ids.contains(idCandidate))) {
                ids.add(idCandidate);
            } else {
                // log some info about the failed attempt 
                logger.trace(Messages.getString("IdGenerator.TRACE_ATTEMPT_GENERATE") + " " + idCandidate);
            }
        }

        logger.debug(Messages.getString("IdGenerator.NUMBER_OF_ITERATIONS") + i);

        if (ids.size() < num) {
            logger.warn(Messages.getString("IdGenerator.FAILED_TO_REACH_TARGET_NUM") + ids.size());
        }

        if (ids.size() == 0) {
            logger.fatal(Messages.getString("IdGenerator.NO_ALTERNATIVES_LEFT"));
        }

        return ids;
    }

    /**
     * Prints ids into columns with a predefined terminal width(to
     * System.out). The number of columns is calculated from the terminal width.
     * 
     * @param ids
     *            a list of ids to be printed
     */
    public void printColumns(List<String> ids) {
        int idLength = ids.get(0).length();
        int numberOfColumns = Globals.TERM_WIDTH / (idLength + 1);
        if (numberOfColumns == 0)
            numberOfColumns = 1;

        logger.debug(Messages.getString("IdGenerator.N_SEPARATOR"));

        int i;
        int column = 0;
        for (i = 0; i < ids.size(); i++) {
            column++;
            String id = (String) ids.get(i);
            if (((column % numberOfColumns) == 0)) {
                System.out.print(id + Messages.getString("IdGenerator.NEW_LINE"));
            } else {
                System.out.print(id + ' ');
            }
        }
        if ((column % numberOfColumns) != 0) {
            System.out.print(Messages.getString("IdGenerator.NEW_LINE"));
        }
        logger.debug(Messages.getString("IdGenerator.N_SEPARATOR"));
    }

    /**
     * Prints a list of ids to a terminal(System.out)
     * 
     * @param ids
     *            a list of ids to be printed
     */
    public void print(List<String> ids) {
        logger.debug(Messages.getString("IdGenerator.N_SEPARATOR"));

        for (int i = 0; i < ids.size(); i++) {
            String id = (String) ids.get(i);
            System.out.print(id + Messages.getString("IdGenerator.NEW_LINE"));
        }
        logger.debug(Messages.getString("IdGenerator.N_SEPARATOR"));
    }

    /**
     * Prints the usage info
     * and the available CLI options
     */
    public void printUsage() {
        System.out.println(options.getHelp(false));
    }

    /**
     * Prints a very verbose help page
     * with more detailed info
     */
    public void printHelp() {
        System.out.println(options.getHelp(true));
    }

    //   TODO find are more elegant way to specify options globally
    /**
     * Initializes the CLI (Command Line Interface) options of the IdGenerator.
     * 
     * @return the CLI options
     */
    private IdGenOptions buildOptions() {
        IdGenOptions opts = new IdGenOptions();

        logger.trace("Building CLI options...");

        // number of ids
        opts.add("W", "terminal-width", Messages.getString("IIdGenCommandLineOptions.CL_TERMINAL_WIDTH_DESC")
                + " (Default: " + Globals.DEFAULT_TERM_WIDTH + ")", 1, "number", ' ');

        // number of ids
        opts.add("N", "number-ids", Messages.getString("IIdGenCommandLineOptions.CL_NUMBER_IDS_DESC"), 1, "number",
                ' ');

        // print in columns flag
        opts.add("C", "print-in-columns", Messages.getString("IIdGenCommandLineOptions.CL_PRINT_IN_COLUMNS_DESC"));

        // shellcmd filter command
        opts.add("Sf", "shellcmd-command", Messages.getString("IIdGenCommandLineOptions.CL_SHELLCMD_COMMAND_DESC"),
                1, "command", ' ');

        // shellcmd filter
        opts.add("S", "enable-shellcmd-filter", Messages.getString("IIdGenCommandLineOptions.CL_SHELLCMD_DESC")
                + " (Default: " + Globals.DEFAULT_SHELLCMD + ")");

        // passwd filter file
        opts.add("Pf", "passwd-file", Messages.getString("IIdGenCommandLineOptions.CL_PASSWD_FILE_DESC"), 1, "file",
                ' ');

        // passwd filter
        opts.add("P", "enable-passwd-filter", Messages.getString("IIdGenCommandLineOptions.CL_PASSWD_DESC")
                + " (Default: " + Globals.DEFAULT_PASSWD_FILE + ")");

        // blacklist filter file
        opts.add("Bf", "blacklist-file", Messages.getString("IIdGenCommandLineOptions.CL_BLACKLIST_FILE_DESC"), 1,
                "file", ' ');

        // blacklist filter
        opts.add("B", "enable-blacklist-filter", Messages.getString("IIdGenCommandLineOptions.CL_BLACKLIST_DESC")
                + " (Default: " + Globals.DEFAULT_BLACKLIST_FILE + ")");

        // create all "T[a-z]" options as invisible and a dummy option for them
        for (char currentChar = 'a'; currentChar < 'z'; currentChar++) {
            opts.addInvisible("T" + currentChar, "template-variable-" + currentChar, 1, "data", ' ');
        }
        opts.addDummy("T[a-z]", "template-variable-[a-z]",
                Messages.getString("IIdGenCommandLineOptions.CL_TEMPLATE_VARIABLE_DESC"), 1, "data");

        // id template string
        opts.add("T", "template", Messages.getString("IIdGenCommandLineOptions.CL_TEMPLATE_DESC"), 1, "template",
                ' ');

        // display usage (short help)
        opts.add("h", "help", Messages.getString("IIdGenCommandLineOptions.CL_HELP"));

        // display help page (long help)
        opts.add("hh", "help-page", Messages.getString("IIdGenCommandLineOptions.CL_HELP_PAGE"));

        return opts;
    }

    /**
     * Set an option with it's value
     * 
     * @param opt
     *          the short option parameter to be set
     * @param value
     *          the value to be associated with the parameter
     */
    public void setOption(String opt, String value) {
        updateOptions = false;
        this.options.setOptionValue(opt, value);
    }
}