net.unicon.academus.spell.SpellCheckerServlet.java Source code

Java tutorial

Introduction

Here is the source code for net.unicon.academus.spell.SpellCheckerServlet.java

Source

/*
 * Copyright (C) 2007 Unicon, Inc.
 *
 * 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 2
 * 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 distribution.  It is also available here:
 * http://www.fsf.org/licensing/licenses/gpl.html
 *
 * As a special exception to the terms and conditions of version
 * 2 of the GPL, you may redistribute this Program in connection
 * with Free/Libre and Open Source Software ("FLOSS") applications
 * as described in the GPL FLOSS exception.  You should have received
 * a copy of the text describing the FLOSS exception along with this
 * distribution.
 */

package net.unicon.academus.spell;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintWriter;

import java.net.URLDecoder;

import java.util.ArrayList;
import java.util.Iterator;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

public class SpellCheckerServlet extends HttpServlet {
    private static Log log = LogFactory.getLog(SpellCheckerServlet.class);

    /** Encoding for URL decoding/encoding. Not passed to aspell. */
    private static final String ENCODING = "UTF-8";

    private String lang = "en_US";
    private String aspell_loc = "aspell";
    private String known_good = "A";
    private boolean hasHTMLFilter = false;

    private String header_resource = "/rendering/fragments/spell/header.html";
    private String footer_resource = "/rendering/fragments/spell/footer.html";

    private ServletContext ctx = null;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        ctx = config.getServletContext();

        lang = getParam(config, "lang", lang);
        aspell_loc = getParam(config, "aspell", aspell_loc);
        known_good = getParam(config, "known-good", known_good);

        try {
            hasHTMLFilter = checkHTMLFilter();
        } catch (Exception ex) {
            log.warn("Unable to verify Aspell HTML Filter support", ex);
            hasHTMLFilter = false;
        }

        header_resource = getParam(config, "header", header_resource);
        footer_resource = getParam(config, "footer", footer_resource);

        log.info("SpellCheckerServlet initialized.");
    }

    private static String getParam(ServletConfig config, String name, String def) {
        String ret = config.getInitParameter(name);

        if (ret == null)
            ret = def;

        return ret;
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException {
        doPost(req, res);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException {
        res.setContentType("text/html");
        PrintWriter out = null;
        String[] chk = req.getParameterValues("textinputs");

        if (log.isDebugEnabled()) {
            StringBuffer buf = new StringBuffer();
            for (int i = 0; i < chk.length; i++)
                buf.append('"').append(chk[i]).append("\"; ");
            log.debug("Spell check request: " + buf.toString());
        }

        try {
            out = res.getWriter();

            printHeader(out);
            performSpellCheck(out, chk);
            printFooter(out);

            out.close();
        } catch (Exception ex) {
            throw new ServletException(ex);
        }
    }

    private boolean checkHTMLFilter() throws Exception {
        boolean rslt = false;

        String[] cmdline = new String[3];
        cmdline[0] = aspell_loc;
        cmdline[1] = "dump";
        cmdline[2] = "filters";

        Process p = Runtime.getRuntime().exec(cmdline);
        BufferedReader pin = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line = pin.readLine();
        while (!rslt && line != null) {
            if (line.startsWith("html"))
                rslt = true;

            line = pin.readLine();
        }
        pin.close();
        p.destroy();

        log.info("Aspell HTML Filter support: " + rslt);
        return rslt;
    }

    /**
     * Perform the actual spellcheck operation using the aspell process.
     */
    private synchronized void performSpellCheck(PrintWriter out, String[] chk) throws Exception {
        ArrayList cmdline = new ArrayList();
        cmdline.add(aspell_loc);
        cmdline.add("-a");
        cmdline.add("--lang=" + lang);
        if (hasHTMLFilter)
            cmdline.add("--mode=html");

        if (log.isDebugEnabled()) {
            StringBuffer cmd = new StringBuffer();
            Iterator it = cmdline.iterator();
            while (it.hasNext()) {
                cmd.append(it.next()).append(' ');
            }
            log.debug("Running aspell command: " + cmd.toString());
        }

        Process p = Runtime.getRuntime().exec((String[]) cmdline.toArray(new String[0]));
        PrintWriter pout = new PrintWriter(p.getOutputStream());

        // Start the reader thread. This is threaded so that we do not run into
        // problems with the input stream buffer running out prior to
        // processing the results.
        Thread inThread = new InReader(p.getInputStream(), chk, out);
        inThread.start();

        for (int i = 0; i < chk.length; i++) {
            // Decode the input string if necessary.
            chk[i] = URLDecoder.decode(chk[i], ENCODING);

            // Force a line containing '*' to signal the input switch
            pout.println("%"); // Exit terse mode
            pout.println("^" + known_good); // Emit a known-good word
            pout.println("!"); // Enter terse mode

            String[] lines = chk[i].split("\n");
            for (int k = 0; k < lines.length; k++) {
                pout.println(lines[k]);
            }

            pout.flush();
        }

        // Close the input stream to signal completion to aspell
        pout.flush();
        pout.close();

        // Wait for input reader thread to finish.
        inThread.join();

        // Kill the aspell process
        p.destroy();

        log.debug("SpellCheckerServlet completed processing with aspell.");
    }

    // This thread performs the input stream reading from aspell. This is
    // necessary so that the input stream buffer does not overflow prior to
    // reading the results. The size of the buffer is operating system
    // dependent, and as such cannot be depended on.
    private class InReader extends Thread {
        private InputStream in;
        private String[] chk;
        private PrintWriter out;

        public InReader(InputStream in, String[] chk, PrintWriter out) {
            this.in = in;
            this.chk = chk;
            this.out = out;
        }

        public void run() {
            try {
                StringBuffer buf = new StringBuffer();
                StringBuffer errorBuf = new StringBuffer();

                printInputs(buf, chk);

                BufferedReader pin = new BufferedReader(new InputStreamReader(in));

                String line = pin.readLine();
                int inputIdx = -1;
                int badWordIdx = 0;
                boolean error = false;

                log.debug("InReader beginning processing.");

                if (line == null)
                    log.warn("Read null as first line!");

                while (line != null) {
                    if (line.length() > 0) {
                        if (log.isDebugEnabled())
                            log.debug("Read line: " + line);
                        char t = line.charAt(0);

                        if (t == '&' || t == '#') {
                            // if '&', then not in dictionary but has suggestions
                            // if '#', then not in dictionary and no suggestions
                            String[] tmp = line.split(" ", 5);
                            printWord(buf, tmp[1], inputIdx, badWordIdx);

                            String[] suggs = null;
                            if (tmp.length == 5) {
                                suggs = tmp[4].split(", ");
                            } else
                                suggs = new String[0];
                            printSuggestions(buf, inputIdx, badWordIdx, suggs);

                            badWordIdx++;

                        } else if (t == '*') {
                            // if '*', then it is a delimiter between text inputs

                            inputIdx++;
                            printInputDecl(buf, inputIdx);
                            badWordIdx = 0;
                        } else if (t == '@') {
                            // Version... Ignore.
                        } else {
                            log.warn("Aspell warning: " + line);
                            error = true;
                            errorBuf.append(line).append("\\n");
                        }
                    } else if (log.isDebugEnabled()) {
                        log.debug("Read empty line");
                    }

                    line = pin.readLine();
                }

                if (error) {
                    printError(out, errorBuf.toString());
                } else {
                    out.print(buf.toString());
                }
                out.flush();

                pin.close();

                log.debug("InReader completed processing.");

            } catch (Exception ex) {
                throw new RuntimeException("Failed to perform spell check: InReader", ex);
            }
        }
    }

    private void printError(PrintWriter out, String error) throws IOException {
        out.print("error = 'Error executing `");
        out.print(aspell_loc);
        out.print("`: \\n");
        out.print(escapeQuotes(error));
        out.println("';");
        log.error("SpellCheckerServlet: Error occurred during execution of `" + aspell_loc + "`: " + error);
    }

    private void printInputs(StringBuffer buf, String[] inpt) {
        for (int i = 0; i < inpt.length; i++) {
            buf.append("textinputs[").append(i).append("] = decodeURIComponent('").append(escapeQuotes(inpt[i]))
                    .append("');\n");
        }
    }

    private void printInputDecl(StringBuffer buf, int tidx) {
        buf.append("words[").append(tidx).append("] = [];\n");
        buf.append("suggs[").append(tidx).append("] = [];\n");
    }

    private void printWord(StringBuffer buf, String word, int tidx, int widx) {
        buf.append("words[").append(tidx).append("][").append(widx).append("] = '").append(escapeQuotes(word))
                .append("';\n");
    }

    private void printSuggestions(StringBuffer buf, int tidx, int widx, String[] suggs) {
        buf.append("suggs[").append(tidx).append("][").append(widx).append("] = [");
        for (int i = 0; i < suggs.length; i++) {
            buf.append("'").append(escapeQuotes(suggs[i])).append("'");
            if (i + 1 < suggs.length)
                buf.append(", ");
        }
        buf.append("];\n");
    }

    private String escapeQuotes(String str) {
        String rslt = str;

        rslt = rslt.replaceAll("\\\\", "\\\\\\\\");
        rslt = rslt.replaceAll("\r", "");
        rslt = rslt.replaceAll("\n", "\\\\n");
        rslt = rslt.replaceAll("'", "\\\\'");

        return rslt;
    }

    private void printResource(PrintWriter out, String resource) throws IOException {
        InputStreamReader is = new InputStreamReader(ctx.getResourceAsStream(resource));

        char[] buf = new char[4096];
        int r = 0;
        while ((r = is.read(buf, 0, buf.length)) != -1) {
            out.write(buf, 0, r);
        }
        is.close();
        out.flush();
    }

    private void printHeader(PrintWriter out) throws IOException {
        printResource(out, header_resource);
    }

    private void printFooter(PrintWriter out) throws IOException {
        printResource(out, footer_resource);
    }
}