Java tutorial
/** * Copyright 2011-2017 Asakusa Framework Team. * * 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.asakusafw.lang.inspection.cli; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.text.MessageFormat; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Properties; import java.util.TreeMap; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.asakusafw.lang.inspection.InspectionNode; import com.asakusafw.lang.inspection.InspectionNodeRepository; import com.asakusafw.lang.inspection.json.JsonInspectionNodeRepository; import com.asakusafw.lang.inspection.processor.DetailProcessor; import com.asakusafw.lang.inspection.processor.DotProcessor; import com.asakusafw.lang.inspection.processor.InspectionNodeProcessor; import com.asakusafw.lang.inspection.processor.ListProcessor; import com.asakusafw.lang.inspection.processor.StoreProcessor; /** * Processes inspection object. */ public final class Cli { static final Logger LOG = LoggerFactory.getLogger(Cli.class); private static final InspectionNodeProcessor DEFAULT_PROCESSOR = new DetailProcessor(); private static final Map<String, InspectionNodeProcessor> BUILTIN_PROCESSORS; static { Map<String, InspectionNodeProcessor> map = new LinkedHashMap<>(); map.put("ls", new ListProcessor()); //$NON-NLS-1$ map.put("list", new ListProcessor()); //$NON-NLS-1$ map.put("txt", new DetailProcessor()); //$NON-NLS-1$ map.put("text", new DetailProcessor()); //$NON-NLS-1$ map.put("json", new StoreProcessor()); //$NON-NLS-1$ map.put("dot", new DotProcessor()); //$NON-NLS-1$ BUILTIN_PROCESSORS = map; } private static final String PATH_SEPARATOR = "/"; //$NON-NLS-1$ private Cli() { return; } /** * The program entry. * @param args application arguments * @throws Exception if failed */ public static void main(String[] args) throws Exception { int status = execute(args); if (status != 0) { System.exit(status); } } /** * The program entry. * @param args application arguments * @return the exit code */ public static int execute(String... args) { Configuration configuration; try { configuration = parse(args); } catch (Exception e) { LOG.error(MessageFormat.format("error occurred while analyzing arguments: {0}", Arrays.toString(args)), e); HelpFormatter formatter = new HelpFormatter(); formatter.setWidth(Integer.MAX_VALUE); formatter.printHelp(MessageFormat.format("java -classpath ... {0}", //$NON-NLS-1$ Cli.class.getName()), new Opts().options, true); return 1; } try { process(configuration); } catch (Exception e) { LOG.error(MessageFormat.format("error occurred while processing inspection object: {0}", Arrays.toString(args)), e); return 1; } return 0; } static Configuration parse(String... args) throws ParseException { LOG.debug("analyzing command line arguments: {}", Arrays.toString(args)); //$NON-NLS-1$ Opts opts = new Opts(); CommandLineParser parser = new BasicParser(); CommandLine cmd = parser.parse(opts.options, args); Configuration results = new Configuration(); results.input = parseFile(cmd, opts.input, true); results.output = parseFile(cmd, opts.output, false); results.path = parseOpt(cmd, opts.path, false); results.format = parseOpt(cmd, opts.format, false); results.properties = parseProperties(cmd, opts.properties); return results; } private static File parseFile(CommandLine cmd, Option opt, boolean mandatory) { String value = parseOpt(cmd, opt, mandatory); if (value == null) { return null; } return new File(value); } private static Map<String, String> parseProperties(CommandLine cmd, Option option) { Properties properties = cmd.getOptionProperties(option.getLongOpt()); Map<String, String> results = new TreeMap<>(); for (Map.Entry<Object, Object> entry : properties.entrySet()) { results.put((String) entry.getKey(), (String) entry.getValue()); } return results; } private static String parseOpt(CommandLine cmd, Option opt, boolean mandatory) { String value = cmd.getOptionValue(opt.getLongOpt()); if (value != null) { value = value.trim(); if (value.isEmpty()) { value = null; } } LOG.debug("--{}: {}", opt.getLongOpt(), value); //$NON-NLS-1$ if (value == null) { if (mandatory) { throw new IllegalArgumentException( MessageFormat.format("option \"--{0}\" is mandatory", opt.getLongOpt())); } return null; } return value; } static void process(Configuration configuration) throws IOException, ReflectiveOperationException { InspectionNodeProcessor processor = loadProcessor(configuration.format); InspectionNodeProcessor.Context context = new InspectionNodeProcessor.Context(configuration.properties); InspectionNode node = loadInput(configuration.input, configuration.repository, configuration.path); if (configuration.output == null) { processor.process(context, node, configuration.defaultOutput); } else { try (OutputStream output = openOutput(configuration.output)) { processor.process(context, node, output); } } } private static InspectionNodeProcessor loadProcessor(String format) throws ReflectiveOperationException { if (format == null) { return DEFAULT_PROCESSOR; } InspectionNodeProcessor builtin = BUILTIN_PROCESSORS.get(format); if (builtin != null) { return builtin; } LOG.debug("loading processor: {}", format); //$NON-NLS-1$ return Class.forName(format).asSubclass(InspectionNodeProcessor.class).newInstance(); } private static InspectionNode loadInput(File file, InspectionNodeRepository repository, String path) throws IOException { InspectionNode node = loadInput(file, repository); if (path != null) { return find(node, path); } return node; } private static InspectionNode loadInput(File file, InspectionNodeRepository repository) throws IOException { LOG.debug("loading file: {}", file); //$NON-NLS-1$ try (InputStream input = new FileInputStream(file)) { return repository.load(input); } } private static OutputStream openOutput(File file) throws IOException { File parent = file.getAbsoluteFile().getParentFile(); if (parent.mkdirs() == false && parent.isDirectory() == false) { throw new IOException(MessageFormat.format("failed to create output: {0}", file)); } return new FileOutputStream(file); } private static InspectionNode find(InspectionNode node, String path) { InspectionNode current = node; StringBuilder history = new StringBuilder(); for (String segment : path.split(PATH_SEPARATOR)) { if (segment.isEmpty()) { continue; } if (history.length() >= 1) { history.append(PATH_SEPARATOR); } history.append(segment); InspectionNode next = current.getElements().get(segment); if (next == null) { throw new NoSuchElementException(history.toString()); } current = next; } return current; } private static class Opts { final Option input = required("input", 1) //$NON-NLS-1$ .withDescription("input inspection file").withArgumentDescription("/path/to/input-file.json"); //$NON-NLS-2$ final Option path = optional("path", 1) //$NON-NLS-1$ .withDescription("inspection path (default: root)").withArgumentDescription("path/to/target-node"); //$NON-NLS-2$ final Option output = optional("output", 1) //$NON-NLS-1$ .withDescription("output file (default: stdout)").withArgumentDescription("/path/to/output-file"); //$NON-NLS-2$ final Option format = optional("format", 1) //$NON-NLS-1$ .withDescription("output format (default: txt)") .withArgumentDescription("ls|txt|dot|json|class-name"); //$NON-NLS-1$ final Option properties = properties("P", "property") //$NON-NLS-1$ //$NON-NLS-2$ .withValueSeparator('=').withDescription("format property").withArgumentDescription("key=value"); //$NON-NLS-2$ final Options options = new Options(); Opts() { for (Field field : Opts.class.getDeclaredFields()) { if (Option.class.isAssignableFrom(field.getType()) == false) { continue; } try { Option option = (Option) field.get(this); options.addOption(option); } catch (Exception e) { throw new IllegalStateException(e); } } } private static RichOption optional(String name, int arguments) { return new RichOption(null, name, arguments, false); } private static RichOption required(String name, int arguments) { return new RichOption(null, name, arguments, true); } private static RichOption properties(String shortName, String longName) { RichOption option = new RichOption(shortName, longName, 2, false); return option; } } static class Configuration { File input; File output; String path; String format; Map<String, String> properties; InspectionNodeRepository repository = new JsonInspectionNodeRepository(); OutputStream defaultOutput = System.out; } }