Java tutorial
/* * (c) 2005 David B. Bracewell * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.davidbracewell; import com.davidbracewell.cli.CommandLine; import com.davidbracewell.cli.CommandLineOption; import com.davidbracewell.cli.CommandLineParser; import com.davidbracewell.config.Config; import com.davidbracewell.conversion.Val; import com.davidbracewell.io.Resources; import com.davidbracewell.logging.Logger; import com.davidbracewell.reflection.Reflect; import com.davidbracewell.tuple.Pair; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Maps; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.Collection; import java.util.Map; /** * <p>Abstract base class for an application. The Application class takes care of boilerplate command line and config * methodology. It looks for fields on the implementing class that have a <code>CommandLine</code> annotation and uses * these to configure a command line parser. Calling the <code>run(String[] args)</code> method will parse the * command line, set the values of the associated fields and initialize the configuration. Non-named arguments are * stored in a private array and can be accessed through the <code>getOtherArguments</code> method. An * <code>Description</code> annotation can be added to a class extending Application to provide a short description of * what the program does for use when displaying help.</p> * <p> Under the hood an Application is a thread with a final run method that calls an abstract method named * <code>programLogic</code>.</p> * * @author David B. Bracewell */ public abstract class Application extends Thread { private static final Logger log = Logger.getLogger(Application.class); /** * The name of the application */ public final String applicationName; private String[] nonNamedArguments; private String packageName; /** * Instantiates a new Application. * * @param applicationName the application name */ protected Application(String applicationName) { this(applicationName, null); } /** * Instantiates a new Application. * * @param applicationName the application name * @param packageName the package name to use for the application, which is important for loading the correct * configuration. */ protected Application(String applicationName, String packageName) { Preconditions.checkArgument(!Strings.isNullOrEmpty(applicationName)); this.applicationName = applicationName; this.packageName = packageName; } private Val getDefaultValue(CommandLine command) { return Strings.isNullOrEmpty(command.defaultValue()) ? null : new Val(command.defaultValue()); } /** * Get other arguments. * * @return Other arguments on the command line not specified by the annotations. */ public final String[] getOtherArguments() { return nonNamedArguments; } @Override public final void run() { try { programLogic(); } catch (Exception e) { log.severe(e); System.exit(-1); } } /** * Child classes override this method adding their program logic. * * @throws Exception Something abnormal happened. */ protected abstract void programLogic() throws Exception; /** * Runs the application. * * @param args the command line arguments */ public final void run(String[] args) { if (args == null) { args = new String[0]; } try { Reflect reflect = Reflect.onObject(this).allowPrivilegedAccess(); CommandLineParser parser = new CommandLineParser(); //Build out the command lines using the annotations on the application Map<CommandLineOption, Pair<Field, CommandLine>> fieldCommandLine = Maps.newHashMap(); for (Field field : reflect.getFields()) { CommandLine command = field.getAnnotation(CommandLine.class); if (command != null) { String spec; if (command.specification().isEmpty()) { if (field.getType().isInstance(Boolean.class) || field.getType().isInstance(boolean.class)) { spec = "--" + field.getName(); } else { spec = "--" + field.getName() + "=ARG" + (command.optional() ? "" : "+"); } } else { spec = command.specification(); } fieldCommandLine.put(parser.addOption(spec, command.description(), command.aliases()), Pair.of(field, command)); } } //Parse the command line nonNamedArguments = Config.initialize(applicationName, args, parser); if (packageName != null) { Config.loadConfig(Resources.fromClasspath(packageName.replace(".", "/") + "/default.conf")); } //Now we set the field values based on what we found on the command line for (Map.Entry<CommandLineOption, Pair<Field, CommandLine>> entry : fieldCommandLine.entrySet()) { CommandLineOption option = entry.getKey(); CommandLine annotation = entry.getValue().getValue(); Val value = option.wasSeen(parser) ? option.getValue(parser) : getDefaultValue(annotation); Field field = entry.getValue().getKey(); if (value != null) { Class<?> clazz = field.getType(); if (Collection.class.isAssignableFrom(clazz)) { Class<?> genericType = (Class<?>) ((ParameterizedType) field.getGenericType()) .getActualTypeArguments()[0]; reflect.set(field.getName(), value.asCollection(clazz, genericType)); } else if (Map.class.isAssignableFrom(clazz)) { Class<?> keyType = (Class<?>) ((ParameterizedType) field.getGenericType()) .getActualTypeArguments()[0]; Class<?> valueType = (Class<?>) ((ParameterizedType) field.getGenericType()) .getActualTypeArguments()[1]; reflect.set(field.getName(), value.asMap(clazz, keyType, valueType)); } else { reflect.set(field.getName(), value); } } } if (CommandLineOption.HELP.wasSeen(parser)) { String equals = Strings.repeat("=", 40) + "\n"; String description = equals + applicationName + "\n"; if (this.getClass().isAnnotationPresent(Description.class)) { description += Strings.repeat("-", 40) + "\n" + this.getClass().getAnnotation(Description.class).value() + "\n"; } description += equals; parser.showHelp(description); System.exit(0); } run(); } catch (Exception e) { log.severe(e); System.exit(-1); } } /** * The interface Description. */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface Description { /** * Value string. * * @return the string */ String value() default ""; }//END OF Requires }//END OF Application