com.google.caja.plugin.Config.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caja.plugin.Config.java

Source

// Copyright (C) 2007 Google Inc.
//
// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
//
// 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 com.google.caja.plugin;

import com.google.caja.config.ConfigUtil;
import com.google.caja.config.WhiteList;
import com.google.caja.lang.css.CssSchema;
import com.google.caja.lang.html.HtmlSchema;
import com.google.caja.lexer.InputSource;
import com.google.caja.lexer.ParseException;
import com.google.caja.reporting.BuildInfo;
import com.google.caja.reporting.MessageType;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.util.Join;
import com.google.caja.util.Lists;
import com.google.caja.util.Pair;
import com.google.caja.util.Sets;
import com.google.caja.util.Strings;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;

/**
 * Flag processing for main methods.
 *
 * @author mikesamuel@gmail.com
 */
public final class Config {
    private final Options options = new Options();

    private final Option INPUT = defineOption("i", "input",
            "Input URI containing HTML (with optional " + "script and style blocks)", true);

    // This output HTML is not standalone, and requires a container to load the
    // supporting JS and set up a virtual document root.
    private final Option OUTPUT_HTML = defineOption("h", "output_html",
            "Output file path for translated HTML (defaults to input with " + "\".out.html\")", true);

    private final Option OUTPUT_JS = defineOption("j", "output_js",
            "Output file path for translated JS (defaults to input with \".out.js\")", true);

    private final Option OUTPUT_BASE = defineOption("o", "out",
            "Path to which the appropriate extension is added to form output files.", true);

    private final Option CSS_PROPERTY_WHITELIST = defineOption("cps", "css_prop_schema",
            "A file: or resource: URI of the CSS Property Whitelist to use.", true);

    private final Option HTML_ATTRIBUTE_WHITELIST = defineOption("has", "html_attrib_schema",
            "A file: or resource: URI of the HTML attribute Whitelist to use.", true);

    private final Option HTML_ELEMENT_WHITELIST = defineOption("hps", "html_property_schema",
            "A file: or resource: URI of the HTML element Whitelist to use.", true);

    private final Option BASE_URI = defineOption("base_uri",
            "The URI relative to which URIs in the inputs are resolved.", true);

    private final Option FETCHER_BASE = defineOption("fetcher_base",
            "An ancestor of all files which the URI fetcher is allowed to resolve.", true);

    private final Option F_URI = defineOption("fu", "f_uri", "A URI which the URI fetcher is allowed to fetch.",
            true);
    // TODO(mikesamuel): remove once JS uri policy is okay
    private final Option L_URI = defineOption("lu", "l_uri", "A URI which the URI policy is allowed to link to.",
            true);

    private final Option SERVICE_PORT = defineOption("port", "The port on which cajoling service is run.", true);

    private final Option ID_CLASS = defineOption("c", "id_class", "The module ID if it is statically known", true);

    private final Option DEBUG_MODE = defineBooleanOption("g", "debug",
            "Set to add debugging info to cajoled output.");

    private final Option RENDERER = defineOption("r", "renderer",
            "The output renderer " + Strings.toLowerCase(Arrays.toString(SourceRenderMode.values())) + "", true);

    private final Option PIPELINE_PRECONDITIONS = defineOption("ppc", "preconds",
            "Space separated properties as described in help text.", true);

    private final Option PIPELINE_GOALS = defineOption("pg", "goals",
            "Space separated properties as described in help text.", true);

    public enum SourceRenderMode {
        MINIFY, PRETTY, DEBUGGER,;
    }

    private final Class<?> mainClass;
    private final PrintWriter stderr;
    private final String usageText;
    private List<URI> inputUris;
    private File outputBase;
    private File outputJsFile;
    private File outputHtmlFile;
    private URI cssPropertyWhitelistUri;
    private URI htmlAttributeWhitelistUri;
    private URI htmlElementWhitelistUri;
    private URI baseUri;
    private File fetcherBase;
    private SourceRenderMode renderer;
    private Set<String> fUris;
    private Set<String> lUris;
    private int servicePort;
    private String idClass;
    private Planner.PlanState negGoals = Planner.EMPTY;
    private Planner.PlanState posGoals = Planner.EMPTY;
    private Planner.PlanState negPreconds = Planner.EMPTY;
    private Planner.PlanState posPreconds = Planner.EMPTY;

    public Config(Class<?> mainClass, PrintStream stderr, String usageText) {
        this(mainClass, new PrintWriter(stderr), usageText);
    }

    public Config(Class<?> mainClass, PrintWriter stderr, String usageText) {
        this.mainClass = mainClass;
        this.stderr = stderr;
        this.usageText = usageText;
    }

    public Collection<URI> getInputUris() {
        return inputUris;
    }

    public File getOutputJsFile() {
        return outputJsFile;
    }

    public File getOutputHtmlFile() {
        return outputHtmlFile;
    }

    public File getOutputBase() {
        return outputBase;
    }

    public int getServicePort() {
        return servicePort;
    }

    public URI getCssPropertyWhitelistUri() {
        return cssPropertyWhitelistUri;
    }

    public URI getHtmlAttributeWhitelistUri() {
        return htmlAttributeWhitelistUri;
    }

    public URI getHtmlElementWhitelistUri() {
        return htmlElementWhitelistUri;
    }

    public URI getBaseUri() {
        return baseUri;
    }

    public File getFetcherBase() {
        return fetcherBase;
    }

    public CssSchema getCssSchema(MessageQueue mq) {
        return new CssSchema(whitelist(cssPropertyWhitelistUri, mq),
                whitelist(URI.create("resource:///com/google/caja/lang/css/css-extensions-fns.json"), mq));
    }

    public HtmlSchema getHtmlSchema(MessageQueue mq) {
        return new HtmlSchema(whitelist(htmlElementWhitelistUri, mq), whitelist(htmlAttributeWhitelistUri, mq));
    }

    public String getIdClass() {
        return idClass;
    }

    public Set<String> getFetchableUris() {
        return fUris;
    }

    public Set<String> getLinkableUris() {
        return lUris;
    }

    public SourceRenderMode renderer() {
        return renderer;
    }

    public Planner.PlanState goals(Planner.PlanState ps) {
        return ps.without(negGoals).with(posGoals);
    }

    public Planner.PlanState preconditions(Planner.PlanState ps) {
        return ps.without(negPreconds).with(posPreconds);
    }

    public boolean processArguments(String[] argv) {
        try {
            CommandLine cl;
            try {
                cl = new BasicParser().parse(options, argv, false);
            } catch (org.apache.commons.cli.ParseException e) {
                usage(e.getMessage(), stderr);
                return false;
            }

            inputUris = Lists.newArrayList();
            for (String input : getOptionValues(cl, INPUT)) {
                URI inputUri;
                try {
                    if (input.indexOf(':') >= 0) {
                        inputUri = new URI(input);
                    } else {
                        File inputFile = new File(input);

                        if (!inputFile.exists()) {
                            usage("File \"" + input + "\" does not exist", stderr);
                            return false;
                        }
                        if (!inputFile.canRead() || inputFile.isDirectory()) {
                            usage("File \"" + input + "\" is not a regular file", stderr);
                            return false;
                        }

                        inputUri = inputFile.getAbsoluteFile().toURI();
                    }
                } catch (URISyntaxException ex) {
                    usage("Input \"" + input + "\" is not a valid URI", stderr);
                    return false;
                }

                inputUris.add(inputUri);
            }
            if (inputUris.isEmpty()) {
                usage("Option \"--" + INPUT.getLongOpt() + "\" missing", stderr);
                return false;
            }

            if (cl.getOptionValue(OUTPUT_BASE.getOpt()) != null) {
                outputBase = new File(cl.getOptionValue(OUTPUT_BASE.getOpt()));

                outputJsFile = substituteExtension(outputBase, ".out.js");
                outputHtmlFile = substituteExtension(outputBase, ".out.html");

                if (cl.getOptionValue(OUTPUT_JS.getOpt()) != null) {
                    usage("Can't specify both --out and --output_js", stderr);
                    return false;
                }
                if (cl.getOptionValue(OUTPUT_HTML.getOpt()) != null) {
                    usage("Can't specify both --out and --output_html", stderr);
                    return false;
                }
            } else {
                outputJsFile = cl.getOptionValue(OUTPUT_JS.getOpt()) == null ? null
                        : new File(cl.getOptionValue(OUTPUT_JS.getOpt()));

                outputHtmlFile = cl.getOptionValue(OUTPUT_HTML.getOpt()) == null ? null
                        : new File(cl.getOptionValue(OUTPUT_HTML.getOpt()));

                if (outputJsFile == null && outputHtmlFile == null) {
                    usage("Please specify js output via " + OUTPUT_JS.getLongOpt() + " &| html output via "
                            + OUTPUT_HTML.getLongOpt(), stderr);
                    return false;
                }
            }

            try {
                cssPropertyWhitelistUri = new URI(cl.getOptionValue(CSS_PROPERTY_WHITELIST.getOpt(),
                        "resource:///com/google/caja/lang/css/css-extensions.json"));
                htmlAttributeWhitelistUri = new URI(cl.getOptionValue(HTML_ATTRIBUTE_WHITELIST.getOpt(),
                        "resource:///com/google/caja/lang/html" + "/html4-attributes-extensions.json"));
                htmlElementWhitelistUri = new URI(cl.getOptionValue(HTML_ELEMENT_WHITELIST.getOpt(),
                        "resource:///com/google/caja/lang/html" + "/html4-elements-extensions.json"));

                if (cl.getOptionValue(BASE_URI.getOpt()) != null) {
                    baseUri = new URI(cl.getOptionValue(BASE_URI.getOpt()));
                } else {
                    baseUri = inputUris.get(0);
                }
            } catch (URISyntaxException ex) {
                stderr.println("Invalid whitelist URI: " + ex.getInput() + "\n    " + ex.getReason());
                return false;
            }

            if (cl.getOptionValue(FETCHER_BASE.getOpt()) != null) {
                fetcherBase = new File(cl.getOptionValue(FETCHER_BASE.getOpt()));
            } else if (Strings.equalsIgnoreCase(baseUri.getScheme(), "file")) {
                fetcherBase = new File(baseUri).getParentFile();
            }

            idClass = cl.getOptionValue(ID_CLASS.getOpt(), null);

            String servicePortString;
            try {
                servicePortString = cl.getOptionValue(SERVICE_PORT.getOpt(), "8887");
                servicePort = Integer.parseInt(servicePortString);
            } catch (NumberFormatException e) {
                stderr.println("Invalid service port: " + SERVICE_PORT.getOpt() + "\n    " + e.getMessage());
                return false;
            }

            fUris = Sets.immutableSet(getOptionValues(cl, F_URI));
            lUris = Sets.immutableSet(getOptionValues(cl, L_URI));

            String renderString = cl.getOptionValue(RENDERER.getOpt());
            if (renderString != null) {
                renderer = SourceRenderMode.valueOf(renderString.toUpperCase());
                if (renderer == null) {
                    stderr.println("Invalid renderer: " + renderString);
                    return false;
                }
            } else {
                renderer = SourceRenderMode.PRETTY;
            }

            boolean debugMode = cl.hasOption(DEBUG_MODE.getOpt());
            boolean onlyJsEmitted = cl.hasOption(OUTPUT_JS.getOpt()) && !cl.hasOption(OUTPUT_HTML.getOpt());
            if (debugMode) {
                negGoals = negGoals.with(PipelineMaker.ONE_CAJOLED_MODULE);
                posGoals = posGoals.with(PipelineMaker.ONE_CAJOLED_MODULE_DEBUG);
            }
            if (onlyJsEmitted) {
                negGoals = negGoals.with(PipelineMaker.HTML_SAFE_STATIC);
            }

            String preconds = cl.getOptionValue(PIPELINE_PRECONDITIONS.getOpt());
            if (preconds != null) {
                Pair<Planner.PlanState, Planner.PlanState> deltas = planDeltas(preconds);
                negPreconds = negPreconds.with(deltas.a);
                posPreconds = posPreconds.with(deltas.b);
            }

            String goals = cl.getOptionValue(PIPELINE_GOALS.getOpt());
            if (goals != null) {
                Pair<Planner.PlanState, Planner.PlanState> deltas = planDeltas(goals);
                negGoals = negGoals.with(deltas.a);
                posGoals = posGoals.with(deltas.b);
            }

            return true;
        } finally {
            stderr.flush();
        }
    }

    private Pair<Planner.PlanState, Planner.PlanState> planDeltas(String props) {
        props = props.trim();
        List<String> neg = Lists.newArrayList();
        List<String> pos = Lists.newArrayList();
        for (String part : props.split("\\s+")) {
            if (!"".equals(part)) {
                if (part.startsWith("-")) {
                    neg.add(part.substring(1));
                } else {
                    pos.add(part.substring(part.startsWith("+") ? 1 : 0));
                }
            }
        }
        try {
            return Pair.pair(PipelineMaker.planState(Join.join("", neg)),
                    PipelineMaker.planState(Join.join("", pos)));
        } catch (IllegalArgumentException ex) {
            stderr.println("Bad prop in " + props + " : " + ex.getMessage());
            return Pair.pair(Planner.EMPTY, Planner.EMPTY);
        }
    }

    public void usage(String msg, PrintWriter out) {
        out.println(BuildInfo.getInstance().getBuildInfo());
        out.println();
        if (msg != null && !"".equals(msg)) {
            out.println(msg);
            out.println();
        }
        new HelpFormatter().printHelp(out, HelpFormatter.DEFAULT_WIDTH,
                (mainClass.getSimpleName() + " --input <in.html> [--output_js <out.js> | --out <out>]"), "\n",
                options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, "\n" + usageText, false);
        out.println();
        int maxPlanStateWidth = 0;
        for (Pair<String, String> doc : PipelineMaker.getPreconditionDocumentation()) {
            maxPlanStateWidth = Math.max(maxPlanStateWidth, doc.a.length());
        }
        for (Pair<String, String> doc : PipelineMaker.getGoalDocumentation()) {
            maxPlanStateWidth = Math.max(maxPlanStateWidth, doc.a.length());
        }
        String fmtStr = "%" + maxPlanStateWidth + "s | %s";
        out.println("Preconditions (default to " + PipelineMaker.DEFAULT_PRECONDS + ")");
        for (Pair<String, String> doc : PipelineMaker.getPreconditionDocumentation()) {
            out.println(String.format(fmtStr, doc.a, doc.b));
        }
        out.println();
        out.println("Goals (default to " + PipelineMaker.DEFAULT_GOALS + ")");
        for (Pair<String, String> doc : PipelineMaker.getGoalDocumentation()) {
            out.println(String.format(fmtStr, doc.a, doc.b));
        }
    }

    private static File substituteExtension(File file, String extension) {
        String fileName = file.getName();
        int lastDot = fileName.lastIndexOf('.');
        if (lastDot < 0) {
            lastDot = fileName.length();
        }
        return new File(file.getParentFile(), fileName.substring(0, lastDot) + extension);
    }

    private static WhiteList whitelist(URI uri, MessageQueue mq) {
        InputSource src = new InputSource(uri);
        try {
            return ConfigUtil.loadWhiteListFromJson(uri, ConfigUtil.RESOURCE_RESOLVER, mq);
        } catch (IOException ex) {
            mq.addMessage(MessageType.IO_ERROR, src);
        } catch (ParseException ex) {
            ex.toMessageQueue(mq);
        }
        // Return a Null instance if unable to load.
        return new WhiteList() {
            public Set<String> allowedItems() {
                return Collections.<String>emptySet();
            }

            public Map<String, TypeDefinition> typeDefinitions() {
                return Collections.<String, TypeDefinition>emptyMap();
            }
        };
    }

    private Option defineOption(String shortFlag, String longFlag, String help, boolean optional) {
        Option opt = new Option(shortFlag, longFlag, /* hasArg: */ true, help);
        opt.setOptionalArg(optional);
        options.addOption(opt);
        return opt;
    }

    private Option defineBooleanOption(String shortFlag, String longFlag, String help) {
        Option opt = new Option(shortFlag, longFlag, false, help);
        opt.setOptionalArg(true);
        options.addOption(opt);
        return opt;
    }

    private Option defineOption(String longFlag, String help, boolean optional) {
        return defineOption(longFlag, longFlag, help, optional);
    }

    private static String[] getOptionValues(CommandLine cl, Option opt) {
        String[] values = cl.getOptionValues(opt.getOpt());
        return values != null ? values : new String[0];
    }

    public static void main(String[] argv) {
        Config config = new Config(Config.class, System.err, "Does some stuff.");
        System.err.println(config.processArguments(argv));
    }
}