All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.util.ApplicationConfig Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
/*
 * #%L
 * Nuiton Utils
 * 
 * $Id: ApplicationConfig.java 2513 2013-02-26 07:22:43Z tchemit $
 * $HeadURL: http://svn.nuiton.org/svn/nuiton-utils/tags/nuiton-utils-2.6.10/nuiton-utils/src/main/java/org/nuiton/util/ApplicationConfig.java $
 * %%
 * Copyright (C) 2004 - 2010 CodeLutin, Chatellier Eric
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */


package org.nuiton.util;

import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.swing.KeyStroke;
import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.collections.EnumerationUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.util.converter.ConverterUtil;

import static org.nuiton.i18n.I18n._;

/**
 * Application configuration.
 * 

*

A finir...

*
    *
  • Ajout d'annotations sur les methodes * pour preciser plus de chose pour les options (pattern, min/max, alias, * description, ...) *
  • Trouver un moyen de document les options et actions pour automatiquement * generer l'aide en ligne. Pour eviter de devoir maintenir une methode * dans lequel est ecrit l'aide en plus des options. *
  • Prise en compte du flag {@link #useOnlyAliases} *
  • Vu qu'en java on ne peut pas pointer une methode mais seulement une classe * il y a un bout des actions qui sont des chaines (nom de la methode). Il faudrait * faire un plugin maven qui check que l'action existe bien durant la compilation. * Il est simple de le faire a l'execution mais c trop tard :( *
  • Ajouter de la documentation pour {@link #getOptionAsList(String)} *
*

*

Bonnes pratiques

*

* Vous devez créer une factory pour créer les instances d'{@link ApplicationConfig} qui contiendra par exemple une méthode : *

*

 *
 *   public static ApplicationConfig getConfig(
 *           Properties props, String configFilename, String ... args) {
 *
 *       ApplicationConfig conf = new ApplicationConfig(
 *               MyAppConfigOption.class, MyAppConfigAction.class,
 *               props, configFilename);
 *
 *       try {
 *           conf.parse(args);
 *       } catch (ArgumentsParserException eee) {
 *           if (log.isErrorEnabled()) {
 *               log.error("Can't load app configuration", eee);
 *           }
 *       }
 *       return conf;
 *   }
 *
 * 
*

*

    *
  • MyAppConfigOption doit étendre {@link OptionDef} et décrir la configuration de l'application. *
  • MyAppConfigAction doit étendre {@link ActionDef} et décrir la liste des options * et de leur alias disponible pour l'application. *
*

*

Lecture des fichiers de configuration

*

* La lecture des fichiers de configuration se fait durant l'appel de la methode * {@link #parse(String...)} en utilisant la valeur de qui doit être définit * dans les options avec pour clef {@link ApplicationConfig#CONFIG_FILE_NAME} pour * trouver les fichiers (voir Les options de configuration pour l'ordre de * chargement des fichiers) *

*

La sauvegarde

* La sauvegarde des options se fait via une des trois methodes disponibles : *
    *
  • {@link #save(File, boolean, String...)} sauve les données dans le fichier demandé *
  • {@link #saveForSystem(String...)} sauvegarde les donnees dans /etc *
  • {@link #saveForUser(String...)} sauvegarde les donnees dans $HOME *
*

* Lors de l'utilisation de la methode {@link #saveForSystem(String...)} ou * {@link #saveForUser(String...)} seules les options lues dans un fichier ou modifiées par * programmation ({@link #setOption(String, String)} seront sauvegardées. Par exemple les * options passees sur la ligne de commande ne seront pas sauvees. *

*

Les options de configuration

*

* Cette classe permet de lire les fichiers de configuration, utiliser les * variable d'environnement et de parser la ligne de commande. L'ordre de prise * en compte des informations trouvées est le suivant (le premier le plus * important) : *

    *
  • options ajoutees par programmation: {@link #setOption(String, String)}
  • *
  • ligne de commande
  • *
  • variable d'environnement de la JVM: java -Dkey=value
  • *
  • variable d'environnement; export key=value
  • *
  • fichier de configuration du repertoire courant: $user.dir/filename
  • *
  • fichier de configuration du repertoire home de l'utilisateur: $user.home/.filename
  • *
  • fichier de configuration du repertoire /etc: /etc/filename
  • *
  • fichier de configuration trouve dans le classpath: $CLASSPATH/filename
  • *
  • options ajoutees par programmation: {@link #defaults}.put(key, value)
  • *
*

*

* Les options sur la ligne de commande sont de la forme: *

 * --option key value
 * --monOption key value1 value2
 * 
*

*

    *
  • --option key value: est la syntaxe par defaut *
  • --monOption key value1 value2: est la syntaxe si vous avez ajouter une * methode setMonOption(key, value1, value2) sur votre classe de configuration * qui herite de {@link ApplicationConfig}. Dans ce cas vous pouvez mettre les * arguments que vous souhaitez du moment qu'ils soient convertibles de la * representation String vers le type que vous avez mis. *
*

*

Les actions

*

* Les actions ne peuvent etre que sur la ligne de commande. Elles sont de la * forme: *

 * --le.package.LaClass#laMethode arg1 arg2 arg3 ... argN
 * 
*

* Une action est donc defini par le chemin complet vers la methode qui traitera * l'action. Cette methode peut-etre une methode static ou non. Si la methode * n'est pas static lors de l'instanciation de l'objet on essaie de passer en * parametre du constructeur la classe de configuration utilisee pour permettre * a l'action d'avoir a sa disposition les options de configuration. Si aucun * constructeur avec comme seul parametre une classe heritant de * {@link ApplicationConfig} n'existe alors le constructeur par defaut est * utilise (il doit etre accessible). Toutes methodes d'actions faisant * parties d'un meme objet utiliseront la meme instance de cette objet lors * de leur execution. *

* Si la methode utilise les arguments variants alors tous les arguments * jusqu'au prochain -- ou la fin de la ligne de commande sont utilises. Sinon * Le nombre exact d'argument necessaire a la methode sont utilises. *

* Les arguments sont automatiquement converti dans le bon type reclame par la * methode. *

* Si l'on veut des arguments optionnels le seul moyen actuellement est * d'utiliser une methode avec des arguments variants *

* Les actions ne sont pas execute mais seulement parsees. Pour les executer * il faut utiliser la méthode {@link #doAction(int)} qui prend en argument un numero * de 'step' ou {@link #doAllAction()} qui fait les actions dans l'ordre de leur step. * Par defaut toutes les actions sont de niveau 0 et sont executee * dans l'ordre d'apparition sur la ligne de commande. Si l'on souhaite * distinguer les actions il est possible d'utiliser l'annotation * {@link ApplicationConfig.Action.Step} sur la methode qui fera l'action en * precisant une autre valeur que 0. *

 * doAction(0);
 * ... do something ...
 * doAction(1);
 * 
* dans cette exemple on fait un traitement entre l'execution des actions * de niveau 0 et les actions de niveau 1. *

*

Les arguments non parsées

* Tout ce qui n'est pas option ou action est considere comme non parse et peut * etre recupere par la methode {@link #getUnparsed}. Si l'on souhaite forcer * la fin du parsing de la ligne de commande il est possible de mettre --. * Par exemple: *
 * monProg "mon arg" --option k1 v1 -- --option k2 v2 -- autre
 * 
* Dans cet exemple seule la premiere option sera considere comme une option. * On retrouvera dans {@code unparsed}: "mon arg", "--option", "k2", "v2", "--", * "autre" *

*

Les alias

* On voit qu'aussi bien pour les actions que pour les options, le nom de la * methode doit etre utilise. Pour eviter ceci il est possible de definir * des alias ce qui permet de creer des options courtes par exemple. Pour cela, * on utilise la methode {@link #addAlias(String, String...)}. *
 * addAlias("-v", "--option", "verbose", "true");
 * addAlias("-o", "--option", "outputfile");
 * addAlias("-i", "--mon.package.MaClass#MaMethode", "import");
 * 
* En faite avant le parsing de la ligne de commande tous les alias trouves sont * automatiquement remplacer par leur correspondance. Il est donc possible * d'utiliser ce mecanisme pour autre chose par exemple: *
 * addAlias("cl", "Code Lutin");
 * addAlias("bp", "Benjamin POUSSIN);
 * 
* Dans le premier exemple on simplifie une option de flags l'option -v n'attend * donc plus d'argument. Dans le second exemple on simplifie une option qui * attend encore un argment de type File. Enfin dans le troisieme exemple * on simplifie la syntaxe d'une action et on force le premier argument de * l'action a etre "import". *

*

Conversion de type

* Pour la conversion de type nous utilisons common-beans. Les types supportes * sont: *
    *
  • les primitif (byte, short, int, long, float, double, char, boolean) *
  • {@link String} *
  • {@link File} *
  • {@link URL} *
  • {@link Class} *
  • Sql{@link Date} *
  • Sql{@link Time} *
  • Sql{@link Timestamp} *
  • les tableaux d'un type primitif ou {@link String}. Chaque element doit * etre separe par une virgule. *
*

* Pour suporter d'autre type, il vous suffit d'enregistrer de nouveau * converter dans commons-beans. *

*

Les substitutions de variable

* {@link ApplicationConfig} supporte les substition de variables de la forme * ${xxx} où {@code xxx} est une autre variable de la configuration. *

* Exemple (dans un fichier de configuration): *

 * firstname = John
 * lastname = Doe
 * fullname = ${firstname} ${lastname}
 * 
* getOption("fullname") retournera "John Doe". * * @author poussin * @version $Revision: 2513 $ *

* Last update $Date: 2013-02-26 08:22:43 +0100 (Tue, 26 Feb 2013) $ by * @since 0.30 * @deprecated since 2.6.10 (replaced by org.nuiton.util.config.ApplicationConfig * in nuiton-config module), will be removed in version 2.7.1. */ @Deprecated public class ApplicationConfig { /** Logger. */ static private Log log = LogFactory.getLog(ApplicationConfig.class); /** Used to know what is separator between class and method on command line. */ private static final String CLASS_METHOD_SEPARATOR = "#"; public static final String LIST_SEPARATOR = ","; /** Configuration file key option. */ public static final String CONFIG_FILE_NAME = "config.file"; /** Configuration encoding key option. */ public static final String CONFIG_ENCODING = "config.encoding"; /** Permet d'associer un nom de contexte pour prefixer les options {@link #CONFIG_PATH} et {@link #CONFIG_FILE_NAME}. */ public static final String APP_NAME = "app.name"; /** * Property name of {@link #adjusting} internal state. * * @since 1.3 */ public static final String ADJUSTING_PROPERTY = "adjusting"; /** * Configuration directory where config path in located. *

* Use default system configuration if nothing is defined: *

    *
  • Linux : /etc/xxx.properties *
  • Windows : C:\\Windows\\System32\\xxx.properties *
  • Mac OS : /etc/ *
*/ public static final String CONFIG_PATH = "config.path"; /** System os name. (windows, linux, max os x) */ protected String osName; /** TODO */ protected boolean useOnlyAliases; /** * file $user.home/.[filename] *

* Not used anymore. Just used for migration issue. * * @deprecated since 1.2.1, and can't be removed (for migration purpose) */ @Deprecated protected String userPath = getUserHome() + File.separator + "."; /** vrai si on est en train de parser les options de la ligne de commande. */ protected boolean inParseOptionPhase; /** TODO */ protected Properties defaults = new Properties(); /** TODO */ protected Properties classpath = new Properties(defaults); /** TODO */ protected Properties etcfile = new Properties(classpath); /** TODO */ protected Properties homefile = new Properties(etcfile); /** TODO */ protected Properties curfile = new Properties(homefile); /** TODO */ protected Properties env = new Properties(curfile){ /** * Environnement variables can't contains dot (bash, csh, ...). Dots are * replaced by underscore (_) to find property if property is not find * with dot */ @Override public synchronized Object get(Object key) { Object result = super.get(key); if (result == null && key instanceof String) { String skey = (String)key; skey = skey.replace(".", "_"); result = super.get(skey); } return result; } /** * override to use get(key) and not super.get(key) as in initial implementation :( */ @Override public String getProperty(String key) { Object oval = get(key); String sval = (oval instanceof String) ? (String)oval : null; return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; } }; /** TODO */ protected Properties jvm = new Properties(env); /** TODO */ protected Properties line = new Properties(jvm); /** TODO */ protected Properties options = new Properties(line); /** TODO */ protected Map> cacheOption = new HashMap>(); /** TODO */ protected Map, Object> cacheAction = new HashMap, Object>(); /** contient apres l'appel de parse, la liste des arguments non utilises */ protected List unparsed = new ArrayList(); /** TODO */ protected Map> aliases = new HashMap>(); /** TODO */ protected Map> actions = new HashMap>(); /** * Internal state to manage with masse operations on option and control * listeners. *

* for example, if you want to save options, using javaBeans technology, * can add a listener to save each time the property is modified. *

* Says now you have an algorithm to set new values in configuration using * setters but you do NOt want to save each time, add in your saving action * a test to detect if model is adjusting. * * @see #saveUserAction * @since 1.3 */ private boolean adjusting; /** suport of config modification. */ protected PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** permet de conserver des objets associe avec ce ApplicationConfig */ transient protected Map context = new HashMap(); /** * Init ApplicationConfig with current simple class name as config file. *

* Also init converters. * * @see ConverterUtil#initConverters() */ public ApplicationConfig() { this(null, null); // setEncoding("UTF-8"); // setConfigFileName(getClass().getSimpleName()); // setEncoding("utf-8"); // // // init extra-converters // ConverterUtil.initConverters(); // // // get system os name // osName = System.getProperty("os.name"); } /** * Create configuration for a particular configuration filename * * @param configFilename name of config to use */ public ApplicationConfig(String configFilename) { this(null, configFilename); } /** * Init ApplicationConfig with current simple class name as config file * and use Properties parameter as defaults *

* Also init converters. * * @param defaults properties * @see ConverterUtil#initConverters() */ public ApplicationConfig(Properties defaults) { this(defaults, null); } /** * All in one, this constructor allow to pass all necessary argument to * initialise ApplicationConfig and parse command line * * @param defaults properties that override default value of optionClass, can be null * @param configFilename override default config filename, can be null * @since 2.4.8 */ public ApplicationConfig(Properties defaults, String configFilename) { init(defaults, configFilename); } /** * On separt l'init du corps du constructeur, car les sous classes ne doivent * pas l'executer. * * @param defaults * @param configFilename * @since 2.4.9 */ protected void init(Properties defaults, String configFilename) { if (defaults != null) { // iterate with Properties method and not with Hashtable method to // prevent missed value with chained Properties object for (String key : defaults.stringPropertyNames()) { setDefaultOption(key, defaults.getProperty(key)); } } setEncoding("UTF-8"); if (configFilename == null) { setConfigFileName(getClass().getSimpleName()); } else { setDefaultOption(CONFIG_FILE_NAME, configFilename); } // init extra-converters ConverterUtil.initConverters(); // get system os name osName = System.getProperty("os.name"); } /** * All in one, this constructor allow to pass all necessary argument to * initialise ApplicationConfig and parse command line * * @param optionClass class that describe option, can be null * @param actionClass class that describe action, can be null * @param defaults properties that override default value of optionClass, can be null * @param configFilename override default config filename, can be null * @deprecated since 2.4.8, prefer use {@link #ApplicationConfig(Properties, String)} */ @Deprecated public ApplicationConfig( Class optionClass, Class actionClass, Properties defaults, String configFilename) { this(defaults, configFilename); if (optionClass != null) { loadDefaultOptions(optionClass); } if (actionClass != null) { loadActions(actionClass); } } /** * Get user home directory (system property {@code user.home}). * * @return user home directory */ public static String getUserHome() { String result = System.getProperty("user.home"); return result; } /** * Get user name (system property {@code user.name}). * * @return user name */ public String getUsername() { String result = getOption("user.name"); return result; } /** * Get os name (system property {@code os.name}). * * @return os name * @since 2.6.6 */ public String getOsName() { String result = getOption("os.name"); return result; } /** * Get os arch (system property {@code os.arch}). * * @return os arch * @since 2.6.6 */ public String getOsArch() { String result = getOption("os.arch"); return result; } /** * Load default options of enum pass in param (enum must extend {@link OptionDef}) * * @param optionClass to load * @param type of enum extend {@link OptionDef} * @deprecated since 2.4.8, prefer use now {@link #loadDefaultOptions(OptionDef[])} */ @Deprecated public void loadDefaultOptions(Class optionClass) { loadDefaultOptions(optionClass.getEnumConstants()); } /** * Load default given options. * * @param options options to load * @param type of enum extend {@link OptionDef} * @since 2.4.8 */ public void loadDefaultOptions(O[] options) { // load default option (included configuration file name : important) for (OptionDef o : options) { if (o.getDefaultValue() != null) { setDefaultOption(o.getKey(), o.getDefaultValue()); } } } /** * Load actions of enum pass in param (enum must extend {@link ActionDef}) * * @param actionClass to load * @param type of enum extend {@link ActionDef} * @deprecated since 2.4.8, prefer use now {@link #loadActions(ActionDef[])} */ @Deprecated public void loadActions(Class actionClass) { loadActions(actionClass.getEnumConstants()); } /** * Load given actions. * * @param actions actions to load * @param type of enum extend {@link ActionDef} * @since 2.4.8 */ public void loadActions(A[] actions) { // load actions for (A a : actions) { for (String alias : a.getAliases()) { addActionAlias(alias, a.getAction()); } } } /** * Used to put default configuration option in config option. Those options * are used as fallback value. * * @param key default property key * @param value default property value */ public void setDefaultOption(String key, String value) { defaults.setProperty(key, value); } /** * Save configuration, in specified file. * * @param file file where config will be writen * @param forceAll if true save all config option * (with defaults, classpath, env, command line) * @param excludeKeys optional list of keys to exclude from * @throws IOException if IO pb */ public void save(File file, boolean forceAll, String... excludeKeys) throws IOException { // store sorted in file Properties prop = new SortedProperties(); if (forceAll) { prop.putAll(defaults); prop.putAll(classpath); } prop.putAll(etcfile); prop.putAll(homefile); prop.putAll(curfile); if (forceAll) { prop.putAll(jvm); prop.putAll(env); prop.putAll(line); } prop.putAll(options); for (String excludeKey : excludeKeys) { prop.remove(excludeKey); } // Ano #687 : create parentFile before using it in FileWriter boolean dirCreated = FileUtil.createDirectoryIfNecessary(file.getParentFile()); if (dirCreated && log.isDebugEnabled()) { log.debug("Creation of config directory " + file.getParent()); } saveResource(file, prop, "Last saved " + new Date()); } /** * Save configuration, in system directory (/etc/) using the * {@link #getConfigFileName}. Default, env and commande line note saved. * * @param excludeKeys optional list of keys to exclude from */ public void saveForSystem(String... excludeKeys) { File file = getSystemConfigFile(); if (log.isDebugEnabled()) { log.debug("will save system configuration in " + file); } try { save(file, false, excludeKeys); } catch (IOException eee) { if (log.isWarnEnabled()) { log.warn(_("nuitonutil.error.applicationconfig.save", file), eee); } } } /** * Save configuration, in user home directory using the * {@link #getConfigFileName}. Default, env and commande line note saved * * @param excludeKeys optional list of keys to exclude from */ public void saveForUser(String... excludeKeys) { File file = getUserConfigFile(); if (log.isDebugEnabled()) { log.debug("will save user configuration in " + file); } try { save(file, false, excludeKeys); } catch (IOException eee) { if (log.isWarnEnabled()) { log.warn(_("nuitonutil.error.applicationconfig.save", file), eee); } } } /** * Clean the user configuration file (The one in user home) and save it * in user config file. * * All options with an empty value will be removed from this file. * * Moreover, like {@link #saveForUser(String...)} the given * {@code excludeKeys} will never be saved. * * This method can be useful when migrating some configuration from a * version to another one with deprecated options (otherwise they will stay * for ever in the configuration file with an empty value which is not * acceptable). * * Important note: Using this method can have some strange * side effects, since it could then allow to reuse default configurations * from other level (default, env, jvm,...). Use with care only! * * @param excludeKeys optional list of key to not treat in cleaning process, * nor save in user user config file. * @since 2.6.6 */ public void cleanUserConfig(String... excludeKeys) { Set keys = new HashSet(homefile.stringPropertyNames()); List toExclude = Arrays.asList(excludeKeys); for (String key : keys) { if (!toExclude.contains(key)) { String property = homefile.getProperty(key); if (StringUtils.isBlank(property)) { if (log.isInfoEnabled()) { log.info("Remove blank property: " + key); } homefile.remove(key); } } } // can now save cleaned user config saveForUser(excludeKeys); } /** * Obtain the system config file location. * * @return the system config file location */ public File getSystemConfigFile() { File file = new File(getConfigPath(), getConfigFileName()); return file; } /** * Obtain the user config file location. * * @return the user config file location */ public File getUserConfigFile() { return new File(getUserConfigDirectory(), getConfigFileName()); } /** * Return list of unparsed command line argument * * @return list of unparsed arguments */ public List getUnparsed() { return unparsed; } /** * Add action to list of action to do. * * @param action action to add, can be null. */ public void addAction(Action action) { if (action != null) { Integer step = action.step; List list = actions.get(step); if (list == null) { list = new LinkedList(); actions.put(step, list); } list.add(action); } } /** * Return ordered action step number. * example: 0,1,5,6 * * @return ordered action step number * @since 2.4 */ public List getActionStep() { List result = new ArrayList(actions.keySet()); Collections.sort(result); return result; } /** * Do all action in specified order step (first 0). * * @throws IllegalAccessException if action invocation failed * @throws IllegalArgumentException if action invocation failed * @throws InvocationTargetException if action invocation failed * @throws InstantiationException if action invocation failed * @see Action.Step * @since 2.4 */ public void doAllAction() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { for (int step : getActionStep()) { doAction(step); } } /** * Do action in specified step. * * @param step do action only defined in this step * @throws IllegalAccessException if action invocation failed * @throws IllegalArgumentException if action invocation failed * @throws InvocationTargetException if action invocation failed * @throws InstantiationException if action invocation failed * @see Action.Step */ public void doAction(int step) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { List list = actions.get(step); if (list != null) { for (Action a : list) { a.doAction(); } } } public void setUseOnlyAliases(boolean useOnlyAliases) { this.useOnlyAliases = useOnlyAliases; } public boolean isUseOnlyAliases() { return useOnlyAliases; } /** * Get the encoding used to read/write resources. *

* This value is stored as an option using the * {@link #getEncodingOption()} key. * * @return the encoding used to read/write resources. * @since 2.3 */ public String getEncoding() { return getOption(getEncodingOption()); } /** * Set the new encoding option. * * @param encoding the new value of the option encoding * @since 2.3 */ public void setEncoding(String encoding) { setDefaultOption(getEncodingOption(), encoding); } /** * All argument in aliases as key is substitued by target. * * @param alias alias string as '-v' * @param target substitution as '--option verbose true' */ public void addAlias(String alias, String... target) { aliases.put(alias, Arrays.asList(target)); } /** * Add alias for action. This method put just -- front the actionMethod and * call {@link #addAlias(String, String...)}. * * @param alias the alias to add for the given method action * @param actionMethod must be fully qualified method path: * package.Class#method */ public void addActionAlias(String alias, String actionMethod) { addAlias(alias, "--" + actionMethod); } /** * Set name of file where options are read (in /etc, $HOME, $CURDIR) * This set used {@link #setDefaultOption(String, String)}. * * @param name file name */ public void setConfigFileName(String name) { // put in defaults, this permit user to overwrite it on commande line setDefaultOption(getConfigFileNameOption(), name); } /** * Get name of file where options are read (in /etc, $HOME, $CURDIR). * * @return name of file */ public String getConfigFileName() { String result = getOption(getConfigFileNameOption()); return result; } public boolean isAdjusting() { return adjusting; } public void setAdjusting(boolean adjusting) { boolean oldvalue = this.adjusting; this.adjusting = adjusting; firePropertyChange(ADJUSTING_PROPERTY, oldvalue, adjusting); } protected String getConfigFileNameOption() { String optionName = CONFIG_FILE_NAME; if (getOption(APP_NAME) != null) { optionName = getOption(APP_NAME) + "." + optionName; } return optionName; } /** * Obtains the key used to store the option encoding. * * @return the encoding option'key * @since 2.3 */ protected String getEncodingOption() { String optionName = CONFIG_ENCODING; if (getOption(APP_NAME) != null) { optionName = getOption(APP_NAME) + "." + optionName; } return optionName; } /** * Use appName to add a context in config.file and config.path options. *

* Ex for an application named 'pollen' : {@code config.file} option becomes * {@code pollen.config.file} and {@code config.path} becomes * {@code pollen.config.path} * * @param appName to use as application context * @since 1.2.1 */ public void setAppName(String appName) { setDefaultOption(APP_NAME, appName); } /** * Get configuration file path to use. *

* Use (in order) one of the following definition: *

* * @return path to use with endind {@link File#separator} * @since 1.2.1 */ public String getConfigPath() { // Concat appName to configPath option to specify context for // application deployment String appName = getOption(APP_NAME) != null ? getOption(APP_NAME) + "." : ""; String result = getOption(appName + CONFIG_PATH); if (result == null) { result = getSystemConfigurationPath(); } if (log.isDebugEnabled()) { log.debug("Configuration path used : " + result); } return result; } /** * Get system configuration path. *

* Currently supported: *

    *
  • Windows : C:\Windows\System32 *
  • Unix : /etc/ *
* * @return the system path * @since 1.2.1 */ protected String getSystemConfigurationPath() { String systemPath = null; // Windows if (osName.toLowerCase().contains("windows")) { // try 1 : %SystemDirectory% try { String systemDirectory = System.getenv("SystemDirectory"); if (systemDirectory != null && systemDirectory.length() > 0) { systemPath = systemDirectory; } } catch (SecurityException eee) { if (log.isErrorEnabled()) { log.error("Can't read env property", eee); } } // try 2 : %SystemRoot% if (systemPath != null) { try { String systemRoot = System.getenv("SystemRoot"); if (systemRoot != null && systemRoot.length() > 0) { systemPath = systemRoot + "\\System32"; } } catch (SecurityException eee) { if (log.isErrorEnabled()) { log.error("Can't read env property", eee); } } } else { // default value systemPath = "C:\\Windows\\System32"; } // %SystemDrive% exists too : C: } else { // All others are unix like // look for in /etc/ systemPath = File.separator + "etc" + File.separator; } if (log.isDebugEnabled()) { log.debug(systemPath); } return systemPath; } /** * Get user configuration path. *

* Currently supported: *

    *
  • Windows : ${user.home}\\Application Data\\ *
  • Max os x : ${user.home}/Library/Application Support *
  • Unix : ${user.home}/.config *
*

* Unix norm is based on freedesktop concept explained here : * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html * * @return the user configuration path * @since 1.2.1 */ public String getUserConfigDirectory() { String userPath = null; String userHome = null; try { userHome = getUserHome(); } catch (SecurityException ignore) { } if (userHome != null) { // windows if (osName.toLowerCase().contains("windows")) { try { String appDataEV = System.getenv("APPDATA"); if (appDataEV != null && appDataEV.length() > 0) { userPath = appDataEV; } } catch (SecurityException ignore) { } if (userPath == null || userPath.isEmpty()) { // ${userHome}\Application Data\ userPath = userHome + File.separator + "Application Data"; } } else if (osName.toLowerCase().contains("mac os x")) { // ${userHome}/Library/Application Support/${applicationId} userPath = userHome + File.separator + "/Library/Application Support"; } else { // ${userHome}/.config/ userPath = userHome + "/.config"; } } // what if null ? return userPath; } /** * Teste si un option existe ou non. * * @param key la clef de l'option à tester * @return {@code true} si l'option existe, {@code false} sinon. */ public boolean hasOption(String key) { // on est oblige de faire un get, car le containsKey n'est pas recursif // sur tous les properties boolean result = options.getProperty(key) != null; return result; } /** * Teste si un option existe ou non * * @param key la clef de l'option à tester * @return {@code true} si 'loption existe, {@code false} sinon. */ public boolean hasOption(OptionDef key) { boolean result = hasOption(key.getKey()); return result; } /** * ajoute un objet dans le context, la classe de l'objet est utilise comme cle * * @since 2.4.2 */ public void putObject(Object o) { putObject(o.getClass().getName(), o); } /** * ajoute un objet dans le context, 'name' est utilise comme cle * * @since 2.4.2 */ public void putObject(String name, Object o) { context.put(name, o); } /** * recupere un objet de la class, s'il n'existe pas encore, il est cree * (il faut donc que class soit instanciable *

* E peut prendre en argument du contruteur un objet de type ApplicationConfig * * @since 2.4.2 */ public E getObject(Class clazz) { E result = getObject(clazz, clazz.getName()); return result; } /** * recupere un objet ayant le nom 'name', s'il n'existe pas encore, il est * cree en utilisant la class, sinon il est simplement caster vers cette * classe. *

* E peut prendre en argument du contruteur un objet de type ApplicationConfig * * @since 2.4.2 */ public E getObject(Class clazz, String name) { E result = clazz.cast(context.get(name)); if (result == null) { result = ObjectUtil.newInstance( clazz, Collections.singleton(this), true); putObject(name, result); } return result; } /** * retourne une nouvelle instance d'un objet dont on recupere la la class * dans la configuration via la cle 'key'. Retourne null si la cle n'est pas * retrouve * * @since 2.4.2 */ public Object getOptionAsObject(String key) { Object result = null; if (hasOption(key)) { Class clazz = getOptionAsClass(key); result = ObjectUtil.newInstance( clazz, Collections.singleton(this), true); } return result; } /** * retourne une nouvelle instance d'un objet dont on recupere la la class * dans la configuration via la cle 'key' et le cast en E. Retourne null * si la cle n'est pas retrouve *

* E peut prendre en argument du contruteur un objet de type ApplicationConfig * * @since 2.4.2 */ public E getOptionAsObject(Class clazz, String key) { E result = clazz.cast(getOptionAsObject(key)); return result; } /** * retourne l'objet instancier via la classe recupere dans la configuration * via la cle 'key'. Une fois instancie, le meme objet est toujours retourne. * On null si key n'est pas retrouve. *

* La classe peut avoir un constructeur prenant un ApplicationConfig * * @since 2.4.2 */ public Object getOptionAsSingleton(String key) { Object result = context.get(key); if (result == null) { result = getOptionAsObject(key); putObject(key, result); } return result; } /** * retourne l'objet caster en 'E', instancier via la classe recupere dans la * configuration via la cle 'key'. Une fois instancie, le meme objet est * toujours retourne. On null si key n'est pas retrouve *

* La classe peut avoir un constructeur prenant un ApplicationConfig * * @since 2.4.2 */ public E getOptionAsSingleton(Class clazz, String key) { E result = clazz.cast(getOptionAsSingleton(key)); return result; } /** * Set option value. * * @param key property key * @param value property value */ public void setOption(String key, String value) { if (inParseOptionPhase) { line.setProperty(key, value); } else { options.setProperty(key, value); } } /** * get option value as string. *

* Replace inner ${xxx} value. * * @param key the option's key * @return String representation value */ public String getOption(String key) { String value = options.getProperty(key); // replace ${xxx} value = replaceRecursiveOptions(value); return value; } /** * Replace included ${xxx} suboptions by their values. * * @param option option to replace into * @return replaced option * @since 1.1.3 */ protected String replaceRecursiveOptions(String option) { // TODO do a common code with RecursiveProperties code // TODO but can't overwrite getProperty() method String result = option; if (result == null) { return null; } //Ex : result="My name is ${myName}." int pos = result.indexOf("${", 0); //Ex : pos=11 while (pos != -1) { int posEnd = result.indexOf("}", pos + 1); //Ex : posEnd=19 if (posEnd != -1) { String value = getOption(result.substring(pos + 2, posEnd)); // Ex : getProperty("myName"); if (value != null) { // Ex : value="Thimel" result = result.substring(0, pos) + value + result.substring(posEnd + 1); // Ex : result="My name is " + "Thimel" + "." pos = result.indexOf("${", pos + value.length()); // Ex : pos=-1 } else { // Ex : value=null pos = result.indexOf("${", posEnd + 1); // Ex : pos=-1 } // Ex : pos=-1 } } return result; } /** * Returns a sub config that encapsulate this ApplicationConfig. * * @param prefix prefix to put automaticaly at beginning of all key * @return sub config that encapsulate this ApplicationConfig * @since 2.4.9 */ public SubApplicationConfig getSubConfig(String prefix) { SubApplicationConfig result = new SubApplicationConfig(this, prefix); return result; } /** * Permet de recuperer l'ensemble des options commencant par une certaine * chaine. * * @param prefix debut de cle a recuperer * @return la liste des options filtrées */ public Properties getOptionStartsWith(String prefix) { Properties result = new Properties(); for (String key : options.stringPropertyNames()) { if (key.startsWith(prefix)) { result.setProperty(key, options.getProperty(key)); } } return result; } /** * Get option value from a option definition. * * @param key the definition of the option * @return the value for the given option */ public Object getOption(OptionDef key) { Object result = getOption(key.getType(), key.getKey()); return result; } /** * Get option value as typed value. * * @param type of the object wanted as return type * @param clazz type of object wanted as return type * @param key the option's key * @return typed value */ public T getOption(Class clazz, String key) { String value = getOption(key); T result = null; if (value != null) { result = (T) convertOption(clazz, key, value, false); } return result; } /** * Convert value in instance of clazz or List if asList is true *

* example: *

  • convertOption(Boolean.class, "toto", "true,true", false) => false *
  • convertOption(Boolean.class, "toto", null, false) => ? ConverterUtil dependant *
  • convertOption(Boolean.class, "toto", "true,true", true) => [true, true] *
  • convertOption(Boolean.class, "toto", null, true) => [] * * @param clazz result type expected * @param key option key * @param value value to convert * @param asList value is string that represente a list * @return the converted option in the required type */ protected Object convertOption(Class clazz, String key, String value, boolean asList) { String cacheKey = key + "-" + asList + "-" + clazz.getName(); int hash = 0; if (value != null) { hash = value.hashCode(); } CacheItem cacheItem = cacheOption.get(cacheKey); // compute value if value don't exist in cacheOption or // if it's modified since last computation if (cacheItem == null || cacheItem.hash != hash) { if (asList) { List list = new ArrayList(); if (value != null) { String[] values = StringUtil.split(value, LIST_SEPARATOR); for (String valueString : values) { // prefer use our convertert method (auto-register more converters) T v = ConverterUtil.convert(clazz, valueString); list.add(v); } } cacheItem = new CacheItem>(list, hash); } else { // prefer use our converter method (auto-register more converters) T v = ConverterUtil.convert(clazz, value); cacheItem = new CacheItem(v, hash); } // add new item to the cache cacheOption.put(cacheKey, cacheItem); } // take result in item Object result = cacheItem.item; return result; } /** * Help to convert value to list of object. If no option for this key * empty List is returned finaly * * @param key the key of searched option * @return value of option list */ public OptionList getOptionAsList(String key) { String value = getOption(key); OptionList result = new OptionList(this, key, value); return result; } /** * Get option value as {@link File}. * * @param key the option's key * @return value as file */ public File getOptionAsFile(String key) { File result = getOption(File.class, key); if (result != null) { result = result.getAbsoluteFile(); } return result; } /** * Get option value as {@link Color}. * * @param key the option's key * @return value as color */ public Color getOptionAsColor(String key) { Color color = getOption(Color.class, key); return color; } /** * Get option value as {@link Properties}, this property must be a filepath * and file must be a properties. *

    * Returned Properties is {@link RecursiveProperties}. * * @param key the option's key * @return Properties object loaded with value pointed by file * @throws IOException if exception occured on read file */ public Properties getOptionAsProperties(String key) throws IOException { File file = getOptionAsFile(key); Properties prop = new RecursiveProperties(); FileReader reader = new FileReader(file); try { prop.load(reader); } finally { reader.close(); } return prop; } /** * Get option value as {@link URL}. * * @param key the option's key * @return value as URL */ public URL getOptionAsURL(String key) { URL result = getOption(URL.class, key); return result; } /** * Get option value as {@link Class}. * * @param key the option's key * @return value as Class */ public Class getOptionAsClass(String key) { Class result = getOption(Class.class, key); return result; } /** * Get option value as {@link Date}. * * @param key the option's key * @return value as Date */ public Date getOptionAsDate(String key) { Date result = getOption(Date.class, key); return result; } /** * Get option value as {@link Time}. * * @param key the option's key * @return value as Time */ public Time getOptionAsTime(String key) { Time result = getOption(Time.class, key); return result; } /** * Get option value as {@link Timestamp}. * * @param key the option's key * @return value as Timestamp */ public Timestamp getOptionAsTimestamp(String key) { Timestamp result = getOption(Timestamp.class, key); return result; } /** * Get option value as {@code int}. * * @param key the option's key * @return value as {@code int} */ public int getOptionAsInt(String key) { Integer result = getOption(Integer.class, key); if (result == null) { // primitive value can not be null result = 0; } return result; } /** * Get option value as {@code long}. * * @param key the option's key * @return value as {@code long} */ public long getOptionAsLong(String key) { Long result = getOption(Long.class, key); if (result == null) { // primitive value can not be null result = 0L; } return result; } /** * Get option value as {@code float}. * * @param key the option's key * @return value as {@code float} * @since 2.2 */ public float getOptionAsFloat(String key) { Float result = getOption(Float.class, key); if (result == null) { // primitive value can not be null result = 0f; } return result; } /** * Get option value as {@code double}. * * @param key the option's key * @return value as {@code double} */ public double getOptionAsDouble(String key) { Double result = getOption(Double.class, key); if (result == null) { // primitive value can not be null result = 0d; } return result; } /** * Get option value as {@code boolean}. * * @param key the option's key * @return value as {@code boolean}. */ public boolean getOptionAsBoolean(String key) { Boolean result = getOption(Boolean.class, key); if (result == null) { // primitive value can not be null result = false; } return result; } /** * Get option value as {@link Locale}. * * @param key the option's key * @return value as {@link Locale}. * @since 2.0 */ public Locale getOptionAsLocale(String key) { Locale result = getOption(Locale.class, key); return result; } /** * Get option value as {@link Version}. * * @param key the option's key * @return value as {@link Version}. * @since 2.0 */ public Version getOptionAsVersion(String key) { Version result = getOption(Version.class, key); return result; } /** * Get option value as {@link KeyStroke}. * * @param key the option's key * @return value as {@link KeyStroke}. * @since 2.5.1 */ public KeyStroke getOptionAsKeyStroke(String key) { KeyStroke result = getOption(KeyStroke.class, key); return result; } /** * Get all options from configuration. * * @return Properties which contains all options */ public Properties getOptions() { return options; } /** * Set manually options when you don't want to use parse method to check * properties file configured by {@link #setConfigFileName(String)}. * * @param options Properties which contains all options to set */ public void setOptions(Properties options) { this.options = options; } /** * Get all options as flat {@link Properties} object (replace inner options). * * @return flat Properties object * @since 1.2.2 */ public Properties getFlatOptions() { return getFlatOptions(true); } /** * Get all options as flat {@link Properties} object. * * @param replaceInner if {@code true} replace imbricated options by theirs values * @return flat Properties object * @since 1.2.2 */ public Properties getFlatOptions(boolean replaceInner) { Properties props = new Properties(); for (String propertyKey : options.stringPropertyNames()) { String propertyValue; if (replaceInner) { // replace ${xxx} option propertyValue = getOption(propertyKey); } else { // do not replace ${xxx} option propertyValue = options.getProperty(propertyKey); } props.setProperty(propertyKey, propertyValue); } return props; } /** * Install the {@link #saveUserAction} on givne {@code properties}. * * @param properties properties on which insalls the saveUserAction */ protected void installSaveUserAction(String... properties) { // pass in adjusting state setAdjusting(true); try { // ajout de tous les listeners pour sauver la configuration // lors de la modification des options de la configuration for (String propertyKey : properties) { // add a listener if (log.isDebugEnabled()) { log.debug("register saveUserAction on property [" + propertyKey + ']'); } addPropertyChangeListener(propertyKey, saveUserAction); } } finally { // ok back to normal adjusting state setAdjusting(false); } } /** * Get all set method on this object or super object. * * @return map with method name without set and in lower case as key, and * method as value */ protected Map getMethods() { // looking for all methods set on ApplicationConfig Method[] allMethods = getClass().getMethods(); Map methods = new HashMap(); for (Method m : allMethods) { String methodName = m.getName(); if (methodName.startsWith("set")) { methodName = methodName.substring(3).toLowerCase(); methods.put(methodName, m); } } return methods; } /** * Take required argument for method in args. Argument used is removed from * args. If method has varArgs, we take all argument to next '--' * * @param m the method to call * @param args iterator with many argument (equals or more than necessary * @return the arguments found for the given method */ protected String[] getParams(Method m, ListIterator args) { List result = new ArrayList(); if (m.isVarArgs()) { while (args.hasNext()) { String p = args.next(); if (p.startsWith("--")) { // stop search args.previous(); break; } else { result.add(p); args.remove(); } } } else { int paramLenght = m.getParameterTypes().length; for (int i = 0; i < paramLenght; i++) { String p = args.next(); args.remove(); // remove this arg because is used now result.add(p); } } return result.toArray(new String[result.size()]); } /** * Create action from string, string must be [package.][class][#][method] * if package, class or method missing, default is used * * @param name name of the action * @param args arguments for action invocation * @return the created action * @throws ArgumentsParserException if parsing failed * @throws IllegalAccessException if could not create action * @throws IllegalArgumentException if could not create action * @throws InstantiationException if could not create action * @throws InvocationTargetException if could not create action */ protected Action createAction(String name, ListIterator args) throws ArgumentsParserException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Action result = null; List methods = ObjectUtil.getMethod(name, true); Class clazz = null; Method method = null; if (methods.size() > 0) { if (methods.size() > 1) { log.warn(String.format( "More than one method found, used the first: %s", methods)); } method = methods.get(0); clazz = method.getDeclaringClass(); } if (method != null) { // remove option from command line, because is used now args.remove(); // creation de l'object sur lequel on fera l'appel Object o = cacheAction.get(clazz); if (o == null && !Modifier.isStatic(method.getModifiers())) { try { o = ConstructorUtils.invokeConstructor(clazz, this); } catch (NoSuchMethodException eee) { log.debug(String.format( "Use default constructor, because no constructor" + " with Config parameter on class %s", clazz.getName())); o = clazz.newInstance(); } cacheAction.put(clazz, o); } // recherche du step de l'action int step = 0; Action.Step annotation = method.getAnnotation(Action.Step.class); if (annotation != null) { step = annotation.value(); } String[] params = getParams(method, args); result = new Action(step, o, method, params); } return result; } /** * Parse option and call set necessary method, read jvm, env variable, * Load configuration file and prepare Action. * * @param args argument as main(String[] args) * @return ApplicationConfig instance * @throws ArgumentsParserException if parsing failed */ public ApplicationConfig parse(String... args) throws ArgumentsParserException { if (args == null) { args = ArrayUtils.EMPTY_STRING_ARRAY; } try { Map methods = getMethods(); List arguments = new ArrayList(args.length); for (String arg : args) { if (aliases.containsKey(arg)) { arguments.addAll(aliases.get(arg)); } else { arguments.add(arg); } } // first parse option inParseOptionPhase = true; for (ListIterator i = arguments.listIterator(); i.hasNext(); ) { String arg = i.next(); if (arg.equals("--")) { // stop parsing break; } if (arg.startsWith("--")) { String optionName = arg.substring(2); if (methods.containsKey(optionName)) { i.remove(); // remove this arg because is used now Method m = methods.get(optionName); String[] params = getParams(m, i); if (log.isDebugEnabled()) { log.debug(String.format( "Set option '%s' with method '%s %s'", optionName, m, Arrays.toString(params))); } ObjectUtil.call(this, m, params); } } } inParseOptionPhase = false; // // second load options from all sources // // JVM jvm.putAll(System.getProperties()); // ENV env.putAll(System.getenv()); // classpath String filename = getConfigFileName(); Enumeration enumInClasspath = ClassLoader.getSystemClassLoader().getResources(filename); Set urlsInClasspath = new HashSet(EnumerationUtils.toList(enumInClasspath)); enumInClasspath = ApplicationConfig.class.getClassLoader().getResources(filename); urlsInClasspath.addAll(EnumerationUtils.toList(enumInClasspath)); if (log.isDebugEnabled() && urlsInClasspath.isEmpty()) { log.debug("No configuration file found in classpath : /" + filename); } /* TODO EC20100515 not sure if it's usefull :( if (urlsInClasspath.isEmpty()) { URL inClasspath = ApplicationConfig.class.getResource("/" + filename); if (inClasspath != null) { urlsInClasspath.add(inClasspath); } }*/ for (URL inClasspath : urlsInClasspath) { if (log.isInfoEnabled()) { log.info("Loading configuration file (classpath) : " + inClasspath); } loadResource(inClasspath.toURI(), classpath); } // system directory File etcConfig = getSystemConfigFile(); // File etcConfig = new File(getConfigPath(), filename); if (etcConfig.exists()) { if (log.isInfoEnabled()) { log.info("Loading configuration file (etc) : " + etcConfig); } loadResource(etcConfig.toURI(), etcfile); } else { if (log.isDebugEnabled()) { log.debug("No configuration file found in system : " + etcConfig.getAbsolutePath()); } } // user home directory File homeConfig = getUserConfigFile(); // File homeConfig = new File(getUserPath(), filename); if (log.isDebugEnabled()) { log.debug("User configuration file : " + homeConfig); } // don't use new File(String, String) File oldHomeConfig = new File(userPath + filename); // migration, if homeConfig doesn't exists and oldHomeConfig does if (!homeConfig.exists() && oldHomeConfig.exists()) { migrateUserConfigurationFile(oldHomeConfig, homeConfig); } // end of migration if (homeConfig.exists()) { if (log.isInfoEnabled()) { log.info("Loading configuration file (home) : " + homeConfig); } loadResource(homeConfig.toURI(), homefile); } else { if (log.isDebugEnabled()) { log.debug("No configuration file found in user home : " + homeConfig.getAbsolutePath()); } } // file $CURDIR/filename File config = new File(filename); if (config.exists()) { if (log.isInfoEnabled()) { log.info("Loading configuration file (curr) : " + config); } loadResource(config.toURI(), curfile); } else { if (log.isDebugEnabled()) { log.debug("No configuration file found in current" + " directory : " + config.getAbsolutePath()); } } // // third parse action and do action // for (ListIterator i = arguments.listIterator(); i.hasNext(); ) { String arg = i.next(); if (arg.equals("--")) { // stop parsing break; } if (arg.startsWith("--")) { String optionName = arg.substring(2); Action action = createAction(optionName, i); addAction(action); } } // // not used args added to unparsed // arguments.remove("--"); unparsed.addAll(arguments); } catch (Exception eee) { if (log.isErrorEnabled()) { log.error(eee); } throw new ArgumentsParserException("Can't parse argument", eee); } return this; } /** * Move old user configuration file {@code oldHomeConfig} to {@code * homeConfig}. * * @param oldHomeConfig old configuration file path * @param homeConfig new configuration file path * @throws IOException if could not move configuration file */ protected void migrateUserConfigurationFile(File oldHomeConfig, File homeConfig) throws IOException { if (log.isInfoEnabled()) { log.info(_("nuitonutil.config.moving.conf", oldHomeConfig.getPath(), homeConfig.getPath())); } boolean b = oldHomeConfig.renameTo(homeConfig); if (!b) { // could not move... String message = String.format( "could not move old configuration file %s to %s", oldHomeConfig, homeConfig ); throw new IOException(message); } } /** * Load a resources given by his {@code uri} to the given * {@code properties} argument. * * @param uri the uri to load * @param properties the properties file to load * @throws IOException if something occurs bad while loading resource * @see Properties#load(Reader) * @since 2.3 */ protected void loadResource(URI uri, Properties properties) throws IOException { InputStreamReader reader = new InputStreamReader(uri.toURL().openStream(), getEncoding()); try { properties.load(reader); } finally { reader.close(); } } /** * Save the given {@code properties} into the given {@code file} with * the given {@code comment}. * * @param file the location where to store the properties * @param properties the properties file to save * @param comment the comment to add in the saved file * @throws IOException if something occurs bad while saving resource * @see Properties#store(Writer, String) * @since 2.3 */ protected void saveResource(File file, Properties properties, String comment) throws IOException { Writer reader = new OutputStreamWriter(new FileOutputStream(file), getEncoding()); try { properties.store(reader, comment); } finally { reader.close(); } } /** For debugging. */ public void printConfig() { System.out.println("-------------------Value-------------------------"); printConfig(System.out); System.out.println("-------------------------------------------------"); } /** * Print out current configuration in specified output. * * @param output output to write config to * @since 1.1.4 */ public void printConfig(PrintStream output) { output.println("defaults " + defaults); output.println("classpath " + classpath); output.println("etcfile " + etcfile); output.println("homefile " + homefile); output.println("curfile " + curfile); output.println("env " + env); output.println("jvm " + jvm); output.println("line " + line); output.println("options " + options); } /** * Return all configuration used with value, that respect includePattern * * @param includePattern null for all value, or config key pattern (ex: "wikitty.*") * @param padding for better presentation, you can use padding to align '=' sign * @return string that represent config * @since 1.5.2 */ public String getPrintableConfig(String includePattern, int padding) { String msg = "Configuration:\n"; for (String key : getFlatOptions().stringPropertyNames()) { if (includePattern == null || "".equals(includePattern) || key.matches(includePattern)) { String value = getOption(key); msg += String.format("\t%" + padding + "s = %s\n", key, value); } } return msg; } protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { pcs.firePropertyChange(propertyName, oldValue, newValue); } public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { pcs.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { pcs.removePropertyChangeListener(propertyName, listener); } public boolean hasListeners(String propertyName) { return pcs.hasListeners(propertyName); } public PropertyChangeListener[] getPropertyChangeListeners( String propertyName) { return pcs.getPropertyChangeListeners(propertyName); } public PropertyChangeListener[] getPropertyChangeListeners() { return pcs.getPropertyChangeListeners(); } /////////////////////////////////////////////////////////////////////////// // // C L A S S E S D E C L A R A T I O N // /////////////////////////////////////////////////////////////////////////// /** * Action to save user configuration. *

    * Add it as a listener of the configuration for a given property. *

    * Note: Will not save if {@link #isAdjusting()} is {@code true}. * * @since 1.3 */ private final PropertyChangeListener saveUserAction = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (isAdjusting()) { if (log.isDebugEnabled()) { log.debug("Skip save while adjusting"); } return; } if (log.isDebugEnabled()) { log.debug("Saving configuration fired by property [" + evt.getPropertyName() + "] at " + new Date()); } saveForUser(); } }; /** * Permet de masquer un prefix. Il est possible d'avoir des valeurs par * defaut. Par exemple: *

         * monOption=toto
         * monPrefix.monOption=titi
         * 
    *

    *

  • Si on cree le subApp avec le prefix "monPrefix." et qu'on demande la valeur * de "monOption", la valeur retournee est "titi". *
  • Si on cree le subApp avec le prefix "monAutrePrefix." et qu'on demande la valeur * de "monOption", la valeur retournee est "toto" (valeur par defaut de monOption. *

    * Certaines methodes retournees ne sont pas * surchargee et ne masque pas le prefix: *

  • getOptions() * * @since 2.4.9 */ public static class SubApplicationConfig extends ApplicationConfig { protected ApplicationConfig parent; protected String prefix; public SubApplicationConfig(ApplicationConfig parent, String prefix) { this.parent = parent; this.prefix = prefix; } @Override protected void init(Properties defaults, String configFilename) { // do nothing } public ApplicationConfig getParent() { return parent; } public String getPrefix() { return prefix; } @Override public Properties getOptions() { return getParent().getOptions(); } @Override public void setDefaultOption(String key, String value) { getParent().setDefaultOption(getPrefix() + key, value); } @Override public boolean hasOption(String key) { boolean result = getOption(key) != null; return result; } @Override public void setOption(String key, String value) { getParent().setOption(getPrefix() + key, value); } /** * Surcharge pour recherche la cle avec le prefix. Si on ne la retrouve * pas, on recherche sans le prefix pour permettre d'avoir des valeurs * par defaut. * * @param key La cle de l'option * @return l'option trouvé avec le prefix ou sinon celle sans le prefix * si pas trouvé. */ @Override public String getOption(String key) { String result = getParent().getOption(getPrefix() + key); if (result == null) { result = getParent().getOption(key); } return result; } /** * Surcharge de la methode pour que les options commencant par le prefix * soit modifiee pour qu'elle est la meme cle sans le prefix. Le but * est de garder les autres options et si une option avait le meme nom * qu'elle soit effacee par celle dont on a supprime le prefix * * @param replaceInner le prefix à remplacer * @return les options commencant par le prefix * soit modifiee pour qu'elle est la meme cle sans le prefix. Le but * est de garder les autres options et si une option avait le meme nom * qu'elle soit effacee par celle dont on a supprime le prefix */ @Override public Properties getFlatOptions(boolean replaceInner) { Properties result = getParent().getFlatOptions(replaceInner); Properties tmp = new Properties(); int lenght = getPrefix().length(); for (Map.Entry e : result.entrySet()) { String k = (String) e.getKey(); if (k.startsWith(getPrefix())) { k = k.substring(lenght); String v = (String) e.getValue(); tmp.setProperty(k, v); } } result.putAll(tmp); return result; } /** * Surcharge pour recupere les valeurs commencant par le prefix demande * en plus du prefix 'sub'. Les options sont ensuite fusionnee pour * permettre aussi les valeurs par defaut * * @param prefix prefix to use * @return les valeurs commençant par le prefix demandé en plus du * prefix 'sub'. */ @Override public Properties getOptionStartsWith(String prefix) { Properties result = getParent().getOptionStartsWith(prefix); Properties tmp = getParent().getOptionStartsWith(getPrefix() + prefix); int lenght = getPrefix().length(); for (Map.Entry e : tmp.entrySet()) { String k = (String) e.getKey(); k = k.substring(lenght); String v = (String) e.getValue(); // on ajout/ecrase les valeurs de result result.setProperty(k, v); } return result; } @Override protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if (propertyName.startsWith(getPrefix())) { propertyName = propertyName.substring(getPrefix().length()); getParent().firePropertyChange(propertyName, oldValue, newValue); } // else not fire event } @Override public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { getParent().addPropertyChangeListener(getPrefix() + propertyName, listener); } @Override public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { getParent().removePropertyChangeListener(getPrefix() + propertyName, listener); } @Override public boolean hasListeners(String propertyName) { return getParent().hasListeners(getPrefix() + propertyName); } // methode interdite dans le sub @Override public ApplicationConfig parse(String... args) throws ArgumentsParserException { throw new UnsupportedOperationException("This method is not supported in SubApplicationConfig"); } } /** * Le contrat de marquage des options, on utilise cette interface pour * caracteriser une option de configuration. *

    *

         *  public enum MyConfigOption implements OptionDef {
         *
         *   APP_CONFIG_FILE(
         *   ApplicationConfig.CONFIG_FILE_NAME,
         *   "Main configuration app file",
         *   "myApp-config.properties",
         *   String.class, true, true),
         *
         *   APP_NAME(
         *   ApplicationConfig.CONFIG_FILE_NAME,
         *   Application name,
         *   "MyApp",
         *   String.class, true, true);
         *
         *   public String key;
         *   public String description;
         *   public String defaultValue;
         *   public Class type;
         *   public boolean isTransient;
         *   public boolean isFinal;
         *
         *   private WikittyConfigOption(String key, String description,
         *           String defaultValue, Class type, boolean isTransient, boolean isFinal) {
         *       this.key = key;
         *       this.description = description;
         *       this.defaultValue = defaultValue;
         *       this.type = type;
         *       this.isTransient = isTransient;
         *       this.isFinal = isFinal;
         *   }
         *
         *   @Override
         *   public boolean isFinal() {
         *       return isFinal;
         *   }
         *
         *   @Override
         *   public boolean isTransient() {
         *       return isTransient;
         *   }
         *
         *   @Override
         *   public String getDefaultValue() {
         *       return defaultValue;
         *   }
         *
         *   @Override
         *   public String getDescription() {
         *       return description;
         *   }
         *
         *   @Override
         *   public String getKey() {
         *       return key;
         *   }
         *
         *   @Override
         *   public Class getType() {
         *       return type;
         *   }
         *
         *   @Override
         *   public void setDefaultValue(String defaultValue) {
         *       this.defaultValue = defaultValue;
         *   }
         *
         *   @Override
         *   public void setTransient(boolean isTransient) {
         *       this.isTransient = isTransient;
         *   }
         *
         *   @Override
         *   public void setFinal(boolean isFinal) {
         *       this.isFinal = isFinal;
         *   }
         * }
         * 
    * * @since 1.0.0-rc-9 */ public interface OptionDef extends Serializable { /** @return la clef identifiant l'option */ String getKey(); /** @return le type de l'option */ Class getType(); /** @return la clef i18n de description de l'option */ String getDescription(); /** * @return la valeur par defaut de l'option sous forme de chaine de * caracteres */ String getDefaultValue(); /** * @return true si l'option ne peut etre sauvegardee sur * disque (utile par exemple pour les mots de passe, ...) */ boolean isTransient(); /** * @return true si l'option n'est pas modifiable (utilise * par exemple pour la version de l'application, ...) */ boolean isFinal(); /** * Changes the default value of the option. * * @param defaultValue the new default value of the option */ void setDefaultValue(String defaultValue); /** * Changes the transient state of the option. * * @param isTransient the new value of the transient state */ void setTransient(boolean isTransient); /** * Changes the final state of the option. * * @param isFinal the new transient state value */ void setFinal(boolean isFinal); } /** * Le contrat de marquage des action, on utilise cette interface pour * caracteriser une action. *

    * Ex : *

    *

         * public enum MyAppConfigAction implements ActionDef {
         *     HELP(MyAppHelpAction.class.getName() + "#show", "-h", "--help");
         *     public String action;
         *     public String[] aliases;
         *
         *     private WikittyConfigAction(String action, String... aliases) {
         *         this.action = action;
         *         this.aliases = aliases;
         *     }
         *
         *     @Override
         *     public String getAction() {
         *         return action;
         *     }
         *
         *     @Override
         *     public String[] getAliases() {
         *         return aliases;
         *     }
         *
         * }
         * 
    * * @author sletellier * @since 1.5.2 */ public interface ActionDef extends Serializable { /** * Must return fully qualified method path : package.Class#method * * @return action to run */ String getAction(); /** * Return all alias used to execute action. * * @return aliases used to execute action */ String[] getAliases(); } /** * Defines a runtime action to be launched via the {@link #doAction()} * method. * * @author poussin */ public static class Action { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Step { int value() default 0; } protected int step; protected Object o; protected Method m; protected String[] params; public Action(int step, Object o, Method m, String... params) { this.step = step; this.o = o; this.m = m; this.params = params; } public void doAction() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { ObjectUtil.call(o, m, params); } } /** * Item used for cacheOption * * @param */ protected static class CacheItem { /** typed option value */ public T item; /** hash of string representation */ public int hash; public CacheItem(T item, int hash) { this.item = item; this.hash = hash; } } public static class OptionList { protected ApplicationConfig config; protected String key; protected String value; public OptionList(ApplicationConfig config, String key, String value) { this.config = config; this.key = key; this.value = value; } protected List convertListOption(Class type) { List result = (List) config.convertOption(type, key, value, true ); return result; } /** * Get option value as {@link String}. * * @return value as String */ public List getOption() { List result = convertListOption(String.class); return result; } /** * Get option value as {@link File}. * * @return value as file */ public List getOptionAsFile() { List tmp = convertListOption(File.class); List result = new ArrayList(tmp.size()); for (File file : tmp) { result.add(file.getAbsoluteFile()); } return result; } /** * Get option value as {@link URL}. * * @return value as URL */ public List getOptionAsURL() { List result = convertListOption(URL.class); return result; } /** * Get option value as {@link Class}. * * @return value as Class */ public List getOptionAsClass() { List result = convertListOption(Class.class); return result; } /** * Get option value as {@link Date}. * * @return value as Date */ public List getOptionAsDate() { List result = convertListOption(Date.class); return result; } /** * Get option value as {@link Time}. * * @return value as Time */ public List




  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy