
org.nuiton.util.ApplicationConfig Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nuiton-utils Show documentation
Show all versions of nuiton-utils Show documentation
Library of usefull class to be used in any project.
/*
* #%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:
*
* - {@link #CONFIG_PATH} option
*
- system dependant path
*
*
* @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