com.algollabs.rgx.java Source code

Java tutorial

Introduction

Here is the source code for com.algollabs.rgx.java

Source

/**
 * This file is part of rgx.
 *
 * rgx is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * rgx 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with rgx. If not, see <http://www.gnu.org/licenses/>.
 */

package com.algollabs;

import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.Collections;
import java.util.Map.Entry;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.Status;
import org.simpleframework.http.core.Container;
import org.simpleframework.http.core.ContainerServer;
import org.simpleframework.transport.Server;
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Parser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.HelpFormatter;

public class rgx implements Container {

    /**
     * Indicates a request containing an unsupported attributes.
     *
     * @see App#handle for API request structure
     */
    public class UnrecognizedAttributeException extends Exception {
        public UnrecognizedAttributeException(String attribute) {
            super(String.format("Unrecognized attribute: '%s'", attribute));
        }
    };

    public void handle(Request request, Response response) {
        PrintStream body = null;
        Boolean supported = false;

        try {
            body = response.getPrintStream();
        } catch (IOException e) {
            response.setStatus(Status.INTERNAL_SERVER_ERROR);
            try {
                response.close();
            } catch (IOException sube) {
                sube.printStackTrace();
                return;
            }
        }

        response.setValue("Content-Type", "application/json");

        // Route the request
        if (request.getMethod().equals("GET") && request.getTarget().contains("flags")) {
            supported = true;
            this.showSupportedFlags(request, response, body);
        }

        if (request.getMethod().equals("POST") && // A Construct hit?
        // We accept JSON only
                request.getContentType().toString().contains("application/json")) {

            supported = true;
            this.testExpression(request, response, body);
        }

        if (!supported) {
            response.setStatus(Status.NOT_IMPLEMENTED);
            try {
                response.close();
            } catch (IOException e) {
            }
        }
        ;
    }

    private void showSupportedFlags(Request request, Response response, PrintStream stream) {
        stream.println(supported_flags_.serialize());
        stream.close();
    }

    /**
     * Handles an API Construct request.
     *
     * Request JSON structure:
     * {
     *   "pattern": "json_encoded_regex",
     *   "subject": "string",
     *   "flags": ""
     * }
     *
     */
    private void testExpression(Request request, Response response, PrintStream stream) {
        try {
            String ptrn = null;
            String subj = null;
            String flags = null;

            // parse the required attributes: pattern, subject, and flags

            JsonElement jsonElement = parser_.parse(request.getContent());
            JsonObject jsonObject = jsonElement.getAsJsonObject();

            for (Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                String k = entry.getKey();
                JsonElement v = entry.getValue();

                if (k.equals("pattern")) {
                    ptrn = gson_.fromJson(v, String.class);
                } else if (k.equals("subject")) {
                    subj = gson_.fromJson(v, String.class);
                } else if (k.equals("flags")) {
                    flags = gson_.fromJson(v, String.class);
                } else {
                    throw new UnrecognizedAttributeException(k);
                }
            }

            // have a Construct test the expression
            stream.println((new Construct(ptrn, subj, flags, true)).test());
            response.setStatus(Status.OK);
        } catch (UnrecognizedAttributeException e) {
            e.printStackTrace();

            response.setStatus(Status.BAD_REQUEST);
            stream.println(e.getMessage());

        } catch (Exception e) {
            e.printStackTrace();

            response.setStatus(Status.INTERNAL_SERVER_ERROR);
            stream.println(e.getMessage());
        }

        stream.close();
    }

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

        // create the command line parser
        CommandLineParser parser = new GnuParser();

        // create the Options
        options.addOption("h", "help", false, "print this help message");
        options.addOption("d", "daemonize", false, "run rgx:Java as a daemon");
        options.addOption(OptionBuilder.withLongOpt("pattern")
                .withDescription("Java regular expression raw pattern").hasArg().withArgName("PATTERN").create());

        options.addOption(OptionBuilder.withLongOpt("subject")
                .withDescription("subject to test the pattern against").hasArg().withArgName("SUBJECT").create());

        options.addOption(
                OptionBuilder.withLongOpt("interface").withDescription("the interface to bind to when daemonized")
                        .hasArg().withArgName("INTERFACE").create());

        options.addOption(OptionBuilder.withLongOpt("port").withDescription("the port to bind to when daemonized")
                .hasArg().withArgName("PORT").create());

        try {
            // parse the command line arguments
            cli = parser.parse(options, args);
        } catch (ParseException exp) {
            System.out.println("Unexpected exception:" + exp.getMessage());
            return;
        }

        if (cli.hasOption("help")) {
            printHelp();
            return;
        }

        if (cli.hasOption("daemonize")) {
            String port = "9400";
            String host = "0.0.0.0";

            if (cli.hasOption("port")) {
                port = cli.getOptionValue("port");
            }
            if (cli.hasOption("interface")) {
                host = cli.getOptionValue("interface");
            }

            Container container = new rgx();
            Server server = new ContainerServer(container);
            Connection connection = new SocketConnection(server);
            SocketAddress address = new InetSocketAddress(host, Integer.parseInt(port));

            connection.connect(address);

            System.out.println("Accepting requests on " + host + ":" + port);
        } else {
            String ptrn = null;
            String subj = null;
            String flags = null;

            if (!cli.hasOption("pattern") || !cli.hasOption("subject")) {
                printHelp();
                return;
            }

            ptrn = cli.getOptionValue("pattern");
            subj = cli.getOptionValue("subject");

            if (cli.hasOption("flags"))
                flags = cli.getOptionValue("flags");

            System.out.println(new Construct(ptrn, subj, flags, false).test());
        }
    }

    private static void printHelp() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("rgx", options);
    }

    private static final JsonParser parser_ = new JsonParser();
    private static final Gson gson_ = new Gson();

    private static Options options = new Options();;

    /**
     * Expose supported Pattern compilation flags.
     */
    private static final Map<String, Integer> FLAGS;
    static {
        Map<String, Integer> flagsMap = new HashMap<String, Integer>();

        flagsMap.put("DOTALL", Pattern.DOTALL);
        flagsMap.put("CASE_INSENSITIVE", Pattern.CASE_INSENSITIVE);
        flagsMap.put("COMMENTS", Pattern.COMMENTS);
        flagsMap.put("MULTILINE", Pattern.MULTILINE);
        flagsMap.put("LITERAL", Pattern.LITERAL);
        flagsMap.put("UNICODE_CASE", Pattern.UNICODE_CASE);
        flagsMap.put("UNICODE_CHAR", Pattern.UNICODE_CHARACTER_CLASS);
        flagsMap.put("UNIX_LINES", Pattern.UNIX_LINES);
        flagsMap.put("CANON_EQ", Pattern.CANON_EQ);

        FLAGS = Collections.unmodifiableMap(flagsMap);
    }

    /**
     * An implementation of the rgx Dialect Construct specification.
     *
     * Constructs take a regular expression pattern, compile it, test it against
     * a subject, and provide a JSON-encoded result.
     *
     * @see https://www.pagehub.org/amireh/rgx/spec
     */
    private static class Construct {

        public Construct(String in_ptrn, String in_subj, String in_flags) {
            raw_ptrn_ = in_ptrn;
            subj_ = in_subj;
            flags_ = in_flags;
        }

        public Construct(String in_ptrn, String in_subj, String in_flags, Boolean unescape) {
            raw_ptrn_ = in_ptrn.replace("\\\\", "\\");
            subj_ = in_subj;
            flags_ = in_flags;
        }

        /**
         * Tests the supplied pattern against the supplied subject.
         *
         * @return JSON-encoded result
         * @see ConstructRC
         */
        public String test() {
            String out = null;
            Matcher m = null;

            if ((ptrn_ = compile()) == null) {
                return new ConstructBadPatternRC().serialize(this);
            }

            System.out.println("Testing " + ptrn_.pattern() + " on " + subj_);

            m = ptrn_.matcher(subj_).useAnchoringBounds(false).useTransparentBounds(false);

            if (m.find(0)) {
                int nr_groups = m.groupCount();

                offset_ = new int[] { m.start(0), m.end(0) };

                // System.out.println("Match boundaries: " + offset_[0] + "," + offset_[1]);
                // System.out.println("# of captures: " + nr_groups);

                for (int i = 1; i <= nr_groups; ++i) {
                    captures_.add(new Capture(m.start(i), m.group(i)));
                    // System.out.println("Capture @" + m.start(i) + " => " + m.group(i) );
                }

                out = new ConstructMatchRC().serialize(this);
            } else {
                out = new ConstructNoMatchRC().serialize(this);
            }

            return out;
        }

        /**
         * Compiles the supplied pattern with the supplied compilation flags.
         *
         * @return null if the pattern compilation fails, see bad_ptrn_message_ for
         * the failure cause
         *
         * @see FLAGS
         */
        private Pattern compile() {
            try {
                int flags = 0;
                if (flags_ != null) {
                    for (String flag_identifier : FLAGS.keySet()) {
                        if (flags_.contains(flag_identifier)) {
                            System.out.println("Using flag: " + flag_identifier);
                            flags = flags | FLAGS.get(flag_identifier);
                        }
                    }
                }

                // System.out.println(String.format("Compiling %s", raw_ptrn_));

                return Pattern.compile(raw_ptrn_, flags);
            } catch (Exception e) {
                e.printStackTrace();

                System.out.println(String.format("Bad pattern '%s': %s", raw_ptrn_, e.getMessage()));
                bad_ptrn_message_ = e.getMessage();
            }

            return null;
        }

        /**
         * Represents a captured group in a Construct's pattern test.
         *
         * Capture instances point to the starting position of their match, and
         * the content of the match. The length of the capture can be denoted
         * by those two variables and therefore is not included.
         */
        private class Capture {
            Capture(int in_position, String in_string) {
                string = in_string;
                position = in_position;
            }

            private int position;
            private String string;
        };

        /**
         * An interface for serializing Constructs into JSON based on Construct#test
         * output.
         */
        public interface ConstructRC {
            /**
             * Return a JSON-encoded rgx Dialect Construct response.
             */
            public abstract String serialize(Construct c);
        };

        /**
         * A matching construct test.
         *
         * JSON structure:
         * {
         *   "status": "RC_MATCH",
         *   "offset": [ MATCH_BEGIN, MATCH_END ],
         *   "captures": [{
         *     "position": CAPTURE_OFFSET_BEGIN,
         *     "string":   "CAPTURED_STRING"
         *   }]
         * }
         *
         * The "captures" key will be an empty array in case the pattern has
         * no group captures.
         */
        private class ConstructMatchRC implements ConstructRC {
            public String serialize(Construct c) {
                offset = c.offset_;
                captures = c.captures_;

                return gson_.toJson(this);
            }

            private final String status = RC_MATCH;
            private int[] offset;
            private ArrayList<Capture> captures;
        };

        /**
         * Pattern does not match the supplied subject.
         *
         * JSON structure:
         * {
         *   "status": "RC_NOMATCH"
         * }
         */
        private class ConstructNoMatchRC implements ConstructRC {
            public String serialize(Construct c) {
                return gson_.toJson(this);
            }

            private final String status = RC_NOMATCH;
        };

        /**
         * A bad supplied pattern.
         *
         * JSON structure:
         * {
         *   "status": "RC_BADPATTERN",
         *   "error": "MESSAGE"
         * }
         */
        public static class ConstructBadPatternRC implements ConstructRC {
            public String serialize(Construct c) {
                error = c.bad_ptrn_message_;
                return gson_.toJson(this);
            }

            private final String status = RC_BADPATTERN;
            private String error;
        };

        public int[] offset_ = new int[] {};
        public ArrayList<Capture> captures_ = new ArrayList<Capture>();

        /** Will contain the pattern compilation failure message if one occurs */
        public String bad_ptrn_message_;

        private static final String RC_MATCH = "RC_MATCH";
        private static final String RC_NOMATCH = "RC_NOMATCH";
        private static final String RC_BADPATTERN = "RC_BADPATTERN";

        private Pattern ptrn_;
        private String raw_ptrn_;
        private String subj_;
        private String flags_;
    };

    /** class Construct */

    private class SupportedFlags {
        SupportedFlags() {
            flags.put("CASE_INSENSITIVE", "Enables case-insensitive matching.");
            flags.put("DOTALL", "Enables dotall mode.");
            flags.put("COMMENTS", "Permits whitespace and comments in pattern.");
            flags.put("MULTILINE", "Enables multiline mode.");
            flags.put("LITERAL", "Enables literal parsing of the pattern.");
            flags.put("UNICODE_CASE", "Enables Unicode-aware case folding.");
            flags.put("UNICODE_CHAR",
                    "Enables the Unicode version of Predefined character classes and POSIX character classes.");
            flags.put("UNIX_LINES", "Enables Unix lines mode.");
            flags.put("CANON_EQ", "Enables canonical equivalence.");
        }

        public String serialize() {
            return gson_.toJson(this);
        }

        private Map<String, String> flags = new HashMap<String, String>();
    }

    private final SupportedFlags supported_flags_ = new SupportedFlags();
}