[Lutinutil-commits] r965 - in trunk/lutinutil/src/main/java/org/codelutin: i18n util
Author: bpoussin Date: 2008-08-04 13:30:38 +0000 (Mon, 04 Aug 2008) New Revision: 965 Modified: trunk/lutinutil/src/main/java/org/codelutin/i18n/I18n.java trunk/lutinutil/src/main/java/org/codelutin/util/ApplicationConfig.java trunk/lutinutil/src/main/java/org/codelutin/util/HashMapMultiKey.java trunk/lutinutil/src/main/java/org/codelutin/util/ObjectUtil.java Log: Version utilisable de ApplicationConfig ajout de I18nf pour utiliser String.format Modified: trunk/lutinutil/src/main/java/org/codelutin/i18n/I18n.java =================================================================== --- trunk/lutinutil/src/main/java/org/codelutin/i18n/I18n.java 2008-08-04 11:33:21 UTC (rev 964) +++ trunk/lutinutil/src/main/java/org/codelutin/i18n/I18n.java 2008-08-04 13:30:38 UTC (rev 965) @@ -164,8 +164,11 @@ * @param message message formate avec la m?me syntaxe que {@link MessageFormat} * @param args les parametres pour le message. * @return la traduction si possible ou la chaine passee en parametre - * sinon. + * sinon. + * @deprecated use {@link I18nf#_} that used String.format. When all project are + * switched this method use String.format too. */ + @Deprecated public static String _(String message, Object... args) { String result = message; Language language = loader == null ? null : loader.getLanguage(); @@ -198,12 +201,15 @@ /** * Retourne la chaine passée en argument. * - * @param message message formaté avec la même syntaxe que {@link - * MessageFormat} - * @param args les paramètres pour le message. - * @return le message passé en argument mais formatté - * avec les paramètres + * @param message message formate avec la meme syntaxe que {@link + * MessageFormat}Message + * @param args les parametres pour le message. + * @return le message passe en argument mais formatte + * avec les parametres + * @deprecated use {@link I18nf#n_} that used String.format. When all project are + * switched this method use String.format too. */ + @Deprecated public static String n_(String message, Object... args) { try { return MessageFormat.format(message, args); Modified: trunk/lutinutil/src/main/java/org/codelutin/util/ApplicationConfig.java =================================================================== --- trunk/lutinutil/src/main/java/org/codelutin/util/ApplicationConfig.java 2008-08-04 11:33:21 UTC (rev 964) +++ trunk/lutinutil/src/main/java/org/codelutin/util/ApplicationConfig.java 2008-08-04 13:30:38 UTC (rev 965) @@ -19,8 +19,14 @@ package org.codelutin.util; +import java.util.logging.Level; +import java.util.logging.Logger; +import static org.codelutin.i18n.I18nf._; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -29,6 +35,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -37,14 +46,33 @@ import java.util.ListIterator; import java.util.Map; import java.util.Properties; +import org.apache.commons.beanutils.ConstructorUtils; +import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Usage: + * <h1>To do</h1> + * + * <ul> + * <li> ajout d'annotations sur les methodes + * pour precisser plus de chose pour les options (pattern, min/max, alias, + * description, ...) + * <li> 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. + * <li> prise en compte du flag {@link #useOnlyAliases} + * <li> 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 :( + * </ul> + * + * <h1>Usage</h1> * <li> create subclass of ApplicationConfig, where in constructor you call * addAliases, setConfigFileName, setDefaultActionPackage, setDefaultActionClass, setDefaultActionMethod * to have properly value. + * * <li> conf = new MonAppConfig(); * <li> conf.parse(args); * <li> here you can used conf.getOption(key); @@ -52,30 +80,188 @@ * <li> ... * <li> conf.doAction(n); * + * <h1>Lecture des fichiers de configuration</h1> + * + * <p> + * La lecture des fichiers de configuration se fait durant l'appel de la methode + * {@link #parse} en utilisant la valeur de {@link #getConfigFileName} pour + * trouver les fichiers (voir Les options de configuration pour l'ordre de + * chargement des fichiers) + * + * <h1>La sauvegarde</h1> + * + * <p> + * La sauvegarde des options se fait via une des trois methodes disponible + * + * <ul> + * <li> {@link #save(File, boolean)} sauve les donn�es dans le fichier demand� + * <li> {@link #saveForSystem} sauvegarde les donnees dans /etc + * <li> {@link #saveForUser} sauvegarde les donnees dans $HOME + * </ul> + * + * Lors de l'utilisation de la methode {@link saveForSystem} ou + * {@link saveForUser} seul les options lu dans un fichier ou modifier par + * programmation ({@link #setOption} seront sauvegardees. Par exemple les + * options passees sur la ligne de commande ne seront pas sauvees. + * + * <h1>Les options de configuration</h1> + * + * <p> + * 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 la suivante (le premier le plus + * important). + * + * <ul> + * <li>options ajoutees par programmation: {@link #setOption}(key, value)</li> + * <li>ligne de commande</li> + * <li>variable d'environnement de la JVM: java -Dkey=value</li> + * <li>variable d'environnement; export key=value</li> + * <li>fichier de configuration du repertoire courant: $user.dir/filename</li> + * <li>fichier de configuration du repertoire home de l'utilisateur: $user.home/.filename</li> + * <li>fichier de configuration du repertoire /etc: /etc/filename</li> + * <li>fichier de configuration trouve dans le classpath: $CLASSPATH/filename</li> + * <li>options ajoutees par programmation: {@link #defaults}.put(key, value)</li> + * </ul> + * + * <p> + * Les options sur la ligne de commande sont de la forme: * <pre> - * Librairie de parsing de ligne de commande - o Chaque action est en faite une classe java. Les arguments de la methode (type et nom) donne l'information pour la ligne de commande. - o MonAction{ maMethod(File file, Class clazz, String str, int i) {//implantation} } est appelable par MonAction.maMethod clazz=org.codelutin.util.Truc file= tmp/test i=10 str=coucou. Lors de la configuration du parseur on donne un package par defaut, une classe par defaut et une methode par defaut pour la recherche de la methode a appele. - + package.Class.method: aucun element par defaut n'est utilise - + Class.method: le package par defaut est utilise pour retrouver la classe - + method: le package et la classe par defaut sont utilises pour retrouver la methode - + Class: Le package et la methode par defaut sont utilises pour retourver la classe et la methode a appeler - o On peut alors soit avoir une classe par action, une classe pour toute les actions, un mixte des deux. Si la methode n'est pas n'est pas static l'objet la contenant est instancier (IoC ? picocontainer ?) - o Pour les Options de configuration de l'application, de la meme facon on configure une classe par defaut ou les options seront stockee. Cette classe heritera d'une classe generic qui elle meme etendra HashMap, pour implanter les get/set si on le souhaite, sinon les methodes put/get de la hashmap seront utilises. L'utilisateur aura les methodes getString, getInt, getFile, getClass, ... pour recuperer de facon type les options - o l'ordre de recherche d'une option est: depuis les valeurs par defaut, le fichier /etc/prop, le fichier $home/prop, le fichier $CUR/prop, env, line. Pour cela stockage dans N hashmap chainee. - + format du fichier key=value - + format de env key=value - + format de line. option key=value, ou si on a une methode set specifique key value (automatiquement la methode setKey est recherche grace au package par defaut et classe par defaut - o ordre d'execution: Parametrage de la lib, utilisation de la lib avec les arguments du programme, lecture des options de la ligne de commande(ce qui peut modifier le nom du fichier d'option), lecture des options de env(ce qui peut modifier le nom du fichier d'option), lecture des options dans les fichiers. Execution des actions dans l'ordre ou on les trouve sur la ligne de commande. - o Amelioration: Il sera possible de mettre des annotations sur les methodes pour precisser plus de chose pour les options (pattern, min/max, alias, description, ...) - o Les arguments non parser se retrouve dans une liste de chaine + * --option key value + * --monOption key value1 value2 * </pre> * + * <ul> + * <li>--option key value: est la syntaxe par defaut + * <li>--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. + * </ul> + * + * <h1>Les actions</h1> + * + * Les actions ne peuvent etre que sur la ligne de commande. Ils sont de la + * forme: + * <pre> + * --le.package.LaClass#laMethode arg1 arg2 arg3 ... argN + * </pre> + * + * <p> + * 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. + * + * <p> + * 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. + * + * <p> + * Les arguments sont automatiquement converti dans le bon type reclame par la + * methode. + * + * <p> + * Si l'on veut des arguments optionnels le seul moyen actuellement est + * d'utiliser une methode avec des arguments variants + * + * <p> Les actions ne sont pas execute mais seulement parsees. Pour les executer + * il faut utiilser la methode {@link #doAction} qui prend en argument un numero + * de '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. + * + * <pre> + * doAction(0); + * ... do something ... + * doAction(1); + * </pre> + * + * <p> + * dans cette exemple on fait un traitement entre l'execution des actions + * de niveau 0 et les actions de niveau 1. + * + * <h1>Les arguments non parses</h1> + * + * <p> + * 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: + * <pre> + * monProg "mon arg" --option k1 v1 -- --option k2 v2 -- autre + * </pre> + * + * <p> + * Dans cet exemple seule la premiere option sera considere comme une option. + * On retrouvera dans unparsed: "mon arg", "--option", "k2", "v2", "--", "autre" + * + * <h1>Les alias</h1> + * + * 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}. + * + * <pre> + * addAlias("-v", "--option", "verbose", "true"); + * addAlias("-o", "--option", "outputfile"); + * addAlias("-i", "--mon.package.MaClass#MaMethode", "import"); + * </pre> + * + * <p> + * 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: + * + * <pre> + * addAlias("cl", "Code Lutin"); + * addAlias("bp", "Benjamin POUSSIN); + * </pre> + * + * <p> + * 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". + * + * <h1>Conversion de type</h1> + * Pour la conversion de type nous utilisons common-beans. Les types supporte + * sont: + + * <ul> + * <li> les primitif (byte, short, int, long, float, double, char, boolean) + * <li> String + * <li> File + * <li> URL + * <li> Class + * <li> SqlDate + * <li> SqlTime + * <li> SqlTimestamps + * <li> les tableaux d'un type primitif ou String. Chaque element doit etre + * separe par une virgule + * </ul> +* + * Pour suporter d'autre type, il vous suffit d'enregistrer de nouveau + * converter dans commons-beans + * @author poussin * @version $Revision$ - * - * Last update: $Date$ - * by : $Author$ + * @since 0.30 + * + * Last update $Date$ + * by $Author$ */ public class ApplicationConfig { @@ -91,6 +277,11 @@ protected boolean useOnlyAliases = false; protected Map<String, List<String>> aliases = new HashMap<String, List<String>>(); + + /** file /etc/[filename] */ + String systemPath = File.separator + "etc" + File.separator; + /** file $user.home/.[filename] */ + String userPath = getUserHome() + File.separator + "."; protected Properties defaults = new Properties(); protected Properties classpath = new Properties(defaults); @@ -102,6 +293,10 @@ protected Properties line = new Properties(jvm); protected Properties options = new Properties(line); + protected Map<String, CacheItem> cacheOption = new HashMap<String, ApplicationConfig.CacheItem>(); + protected Map<Class, Object> cacheAction = new HashMap<Class, Object>(); + + protected List<String> unparsed = new ArrayList<String>(); protected Map<Integer, List<Action>> actions = new HashMap<Integer, List<Action>>(); @@ -126,16 +321,106 @@ this.params = params; } - public void doAction() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public void doAction() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { ObjectUtil.call(o, m, params); } } + /** + * Item used for cacheOption + * @param T + */ + static protected class CacheItem<T> { + /** 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 ApplicationConfig() { - defaults.setProperty(CONFIG_FILE_NAME, this.getClass().getSimpleName()); + setConfigFileName(this.getClass().getSimpleName()); } - + + static public String getUserHome() { + String result = System.getProperty("user.home"); + return result; + } + + public String getUsername() { + String result = getOption("user.name"); + return result; + } + /** + * Used to put default configuration option in config option. Those options + * are used as fallback value. + * @param key + * @param value + */ + protected void setDefaultOption(String key, String value) { + defaults.setProperty(key, value); + } + + /** + * Save configuration, in specified file + * @param logOnError if true not throw exception but log error + * @param file file where config will be writen + * @param forceAll if true save all config option + * (with defaults, classpath, env, command line) + */ + public void save(File file, boolean forceAll) throws IOException { + Properties prop = new Properties(); + 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); + + Writer writer = new FileWriter(file); + prop.store(writer, "Last saved " + new java.util.Date()); + } + + /** + * Save configuration, in system directory (/etc/) using the + * {@link #getConfigFileName}. Default, env and commande line note saved + */ + public void saveForSystem() { + File file = new File(systemPath + getConfigFileName()); + try { + save(file, false); + } catch (IOException eee) { + log.warn(_("Can't save config in file %s", file), eee); + } + } + + /** + * Save configuration, in user home directory using the + * {@link #getConfigFileName}. Default, env and commande line note saved + */ + public void saveForUser() { + File file = new File(userPath + getConfigFileName()); + try { + save(file, false); + } catch (IOException eee) { + log.warn(_("Can't save config in file %s", file), eee); + } + } + + /** * Return list of unparsed command line argument * @return list of unparsed arguments */ @@ -159,7 +444,7 @@ } } - public void doAction(int step) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public void doAction(int step) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { List<Action> list = actions.get(step); if (list != null) { for (Action a : list) { @@ -186,12 +471,23 @@ } /** + * Add alias for action. This method put just -- front the actionMethod and + * call {@link addAlias} + * @param alias + * @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} * @param name file name */ public void setConfigFileName(String name) { // put in defaults, this permit user to overwrite it on commande line - defaults.setProperty(CONFIG_FILE_NAME, name); + setDefaultOption(CONFIG_FILE_NAME, name); } public String getConfigFileName() { @@ -217,8 +513,126 @@ String value = options.getProperty(key); return value; } + + /** + * get option value as typed value + * @param clazz type of object wanted as return type + * @param key + * @return typed value + */ + public <T> T getOption(Class<T> clazz, String key) { + T result = null; + String cacheKey = key + "-" + clazz.getName(); + + String value = options.getProperty(key); + int hash = 0; + if (value != null) { + hash = value.hashCode(); + } + CacheItem<T> 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) { + result = (T) ConvertUtils.convert(value, clazz); + cacheItem = new CacheItem<T>(result, hash); + cacheOption.put(cacheKey, cacheItem); + } + + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public File getOptionAsFile(String key) { + File result = getOption(File.class, key); + result = result.getAbsoluteFile(); + return result; + } /** + * get option value as typed value + * @param key + * @return typed value + */ + public URL getOptionAsURL(String key) { + URL result = getOption(URL.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public Class getOptionAsClass(String key) { + Class result = getOption(Class.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public Date getOptionAsDate(String key) { + Date result = getOption(Date.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public Time getOptionAsTime(String key) { + Time result = getOption(Time.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public Timestamp getOptionAsTimestamp(String key) { + Timestamp result = getOption(Timestamp.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public int getOptionAsInt(String key) { + Integer result = getOption(Integer.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public double getOptionAsDouble(String key) { + Double result = getOption(Double.class, key); + return result; + } + + /** + * get option value as typed value + * @param key + * @return typed value + */ + public boolean getOptionAsBoolean(String key) { + Boolean result = getOption(Boolean.class, key); + return result; + } + + /** * 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 @@ -238,22 +652,35 @@ } /** - * Take required argument for method in args and call this method on target - * object. Argument used is removed from args - * @param target object where method is + * 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 * @throws java.lang.Exception if call failed */ protected String[] getParams(Method m, ListIterator<String> args) { - int paramLenght = m.getParameterTypes().length; - String[] result = new String[paramLenght]; - for (int i=0; i < paramLenght; i++) { - String p = args.next(); - args.remove(); // remove this arg because is used now - result[i] = p; + List<String> result = new ArrayList<String>(); + 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; + return result.toArray(new String[result.size()]); } /** @@ -263,7 +690,7 @@ * @param params * @return */ - protected Action createAction(String name, ListIterator<String> args) throws ArgumentsParserException, InstantiationException, IllegalAccessException { + protected Action createAction(String name, ListIterator<String> args) throws ArgumentsParserException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Action result = null; Class clazz; @@ -300,10 +727,22 @@ if (method != null) { args.remove(); // remove option from command line, because is used now - Object o = null; - if (!Modifier.isStatic(method.getModifiers())) { - o = clazz.newInstance(); + + // 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) { @@ -339,6 +778,10 @@ // first parse option for (ListIterator<String> 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)) { @@ -369,23 +812,20 @@ log.info("Chargement du fichier de config: " + inClasspath); classpath.load(inClasspath.openStream()); } - - // file /etc - File etcConfig = new File(File.separator + "etc" + File.separator + filename); + + File etcConfig = new File(systemPath + filename); if (etcConfig.exists()) { log.info("Chargement du fichier de config: " + etcConfig); etcfile.load(etcConfig.toURI().toURL().openStream()); } - // file /home - String home = System.getProperty("user.home"); - File homeConfig = new File(home + File.separator + filename); + File homeConfig = new File(userPath + filename); if (homeConfig.exists()) { log.info("Chargement du fichier de config: " + homeConfig); homefile.load(homeConfig.toURI().toURL().openStream()); } - // file $CURDIR + // file $CURDIR/filename File config = new File(filename); if (config.exists()) { log.info("Chargement du fichier de config: " + config); @@ -397,6 +837,10 @@ // for (ListIterator<String> 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); @@ -407,6 +851,7 @@ // // not used args added to unparsed // + arguments.remove("--"); unparsed.addAll(arguments); } catch (Exception eee) { Modified: trunk/lutinutil/src/main/java/org/codelutin/util/HashMapMultiKey.java =================================================================== --- trunk/lutinutil/src/main/java/org/codelutin/util/HashMapMultiKey.java 2008-08-04 11:33:21 UTC (rev 964) +++ trunk/lutinutil/src/main/java/org/codelutin/util/HashMapMultiKey.java 2008-08-04 13:30:38 UTC (rev 965) @@ -51,7 +51,11 @@ * complet des References. iterator(), entrySet(), ... peuvent ne pas * fonctionner comme il faut, c-a-d qu'il retourne les references et non * les objets. Mais les methodes simples fonctionnent: put, remove, get, ... + * + * @deprecated Cette classe semble avoir des memory leaks. Mieux vaut ne pas + * l'utiliser ou alors il faudrait refaire l'implantation complete de facon propre */ +@Deprecated public class HashMapMultiKey extends HashMap<Object, Object> { // HashMapMultiKey /** */ @@ -246,6 +250,7 @@ * @throws IllegalArgumentException si le parametre key n'est pas * une collection d'objet. */ + @Override public Object put(Object key, Object value){ if (key == null) { if(log.isWarnEnabled()) {log.warn("key is null");} @@ -272,11 +277,13 @@ } + @Override public boolean containsValue(Object value) { cleanQueue(); return valueToKey.containsKey(value); } + @Override public Object get(Object key) { // a chaque fois que l'on prend, on nettoie un peu avant cleanQueue(); @@ -305,6 +312,7 @@ * valeur associ�e a cette cle. Si un objet a ete passe, retourne la liste * des cles qui contient cet objet et qui ont �t� supprim�es. */ + @Override public Object remove(Object key){ // on nettoie un peu avant cleanQueue(); @@ -388,6 +396,7 @@ public Iterator<Object> iterator(){ return keys.iterator(); } + @Override public boolean equals(Object o){ if (o != null && o instanceof Reference) { // on inverse l'egalite pour que la Key soit egal a la ref @@ -398,6 +407,7 @@ keys.equals(((Key)o).keys); } } + @Override public int hashCode(){ int result = 0; for(int i=0; i<keys.size(); i++){ @@ -408,6 +418,7 @@ } return result; } + @Override public String toString(){ StringBuffer result = new StringBuffer("["); String v =""; Modified: trunk/lutinutil/src/main/java/org/codelutin/util/ObjectUtil.java =================================================================== --- trunk/lutinutil/src/main/java/org/codelutin/util/ObjectUtil.java 2008-08-04 11:33:21 UTC (rev 964) +++ trunk/lutinutil/src/main/java/org/codelutin/util/ObjectUtil.java 2008-08-04 13:30:38 UTC (rev 965) @@ -31,16 +31,26 @@ package org.codelutin.util; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; +import static org.codelutin.i18n.I18nf._; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.ConvertUtils; public class ObjectUtil { // ObjectUtil + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private Log log = LogFactory.getLog(ObjectUtil.class); + /** * ObjectUtil constructor * private because of this class is a static class : nobody @@ -95,6 +105,20 @@ return o; } + static protected Object convert(String v, Class clazz) { + Object t = ConvertUtils.convert(v, clazz); + + if (t != null && + !String.class.getName().equals(clazz.getName()) && + String.class.getName().equals(t.getClass().getName())) { + throw new IllegalArgumentException(String.format( + "Can convert argument to correct type. %s can't be" + + " converted from String to %s conversion is done to %s", + v, clazz.getName(), t.getClass().getName())); + } + return t; + } + /** * Call method m with params as String. Each param is converted to required type for * method with beanutils converter @@ -104,27 +128,43 @@ * @return returned method's value */ public static Object call(Object o, Method m, String ... params) - throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { Class[] types = m.getParameterTypes(); - if (params.length != types.length) { + if (!m.isVarArgs() && params.length != types.length) { throw new IllegalArgumentException(String.format( "Bad number params we have %1$s parameters and waiting %2$s.", params.length, types.length)); } + + int last = types.length; + if (m.isVarArgs()) { + // on traite le dernier differement + last--; + } + Object[] parameters = new Object[types.length]; - for (int i=0; i<types.length; i++) { + for (int i=0; i<last; i++) { + String v = params[i]; Class clazz = types[i]; - String v = params[i]; - parameters[i] = ConvertUtils.convert(v, clazz); - if (parameters[i] != null && - !String.class.getName().equals(clazz.getName()) && - String.class.getName().equals(parameters[i].getClass().getName()) ) { - throw new IllegalArgumentException(String.format( - "Can convert argument to correct type. %s can't be" + - " converted from String to %s conversion is done to %s", - v, clazz.getName(), parameters[i].getClass().getName())); + Object t = convert(v, clazz); + parameters[i] = t; + } + + if (m.isVarArgs()) { + Class clazz = types[last]; // get var args type + clazz = clazz.getComponentType(); // get array component type + List tmp = new ArrayList(); + for (int i=last; i<params.length; i++) { + String v = params[i]; + Object t = convert(v, clazz); + tmp.add(t); } + parameters[last] = tmp.toArray((Object[])Array.newInstance(clazz, tmp.size())); } + + if (log.isDebugEnabled()) { + log.debug(_("Invoke %s with %s", m, Arrays.toString(parameters))); + } Object result = m.invoke(o, parameters); return result; }
participants (1)
-
bpoussin@users.labs.libre-entreprise.org