Author: tchemit Date: 2008-08-09 09:32:42 +0000 (Sat, 09 Aug 2008) New Revision: 820 Added: trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactory.java trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactoryFromProvider.java Removed: trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactory.java trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactoryFromProvider.java Log: simplification du code : suppression du paquetage factory Copied: trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactory.java (from rev 798, trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactory.java) =================================================================== --- trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactory.java (rev 0) +++ trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactory.java 2008-08-09 09:32:42 UTC (rev 820) @@ -0,0 +1,131 @@ +/** + * # #% Copyright (C) 2008 Code Lutin, Tony Chemit + * This program is free software; you + * can redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; either version 2 + * 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 Public License for more details. You + * should have received a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place + * - Suite 330, Boston, MA 02111-1307, USA. + * # #% + */ +package org.codelutin.jaxx.action; + +import jaxx.runtime.JAXXObject; + +import javax.swing.JComponent; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Action factory using the ActionConfig annotations to configure the action. + * <p/> + * Use after the {@link #loadActions(jaxx.runtime.JAXXObject)} to instanciate + * actions in ui with id equals a known action... + * <p/> + * TODO Finish doc + * + * @author chemit + */ +public interface ActionFactory<A extends MyAbstractAction> { + + /** + * Method to init the dictionary of knwon action implementations. + * + * @return the dictionary of known action implementations + */ + Map<String, Class<? extends MyAbstractAction>> init(); + + /** @return the class of the base action of the factory. */ + Class<A> getBaseImpl(); + + /** clear the cache of instanciated actions. */ + void resetCache(); + + /** + * @param actionKey the key of an action + * @return the action with this key from cache, or <code>null</code> if this action is not in cache + */ + A get(String actionKey); + + /** + * For a given ui, load all actions registred in factory. + * <p/> + * The id of the widget in ui is directly mapped to a action key. + * + * @param ui the ui to treate + */ + void loadActions(JAXXObject ui); + + /** + * Obtain an action instance given his key and widget + * + * @param actionKey the key of action + * @param component the component using the action + * @return the instanciated action (could come from cache if already instanciated {@link #getActionFromCache(String)} + */ + A newAction(String actionKey, JComponent component); + + /** + * Obtain an action instance given his key (should call {@link #newAction(String, javax.swing.JComponent)} + * <p/> + * This is a convinient method when you want to obtain an action with no attached widget. + * + * @param actionKey the key of action + * @return the instanciated action (could come from cache if already instanciated {@link #getActionFromCache(String)} + */ + A newAction(String actionKey); + + /** @return the array of names of all actions known by the factory */ + String[] getActionNames(); + + /** @return the set of all the action's classes known by the factory. */ + Set<Entry<String, Class<? extends MyAbstractAction>>> implsEntrySet(); + + /** @return the set of all actions cached in factory indexed by their name */ + Set<Entry<String, A>> cacheEntrySet(); + + /** + * Fire an action given his key, his source and tthe widget responsible of action + * + * @param actionKey the action's key + * @param source the object source of action + * @param component the component doing the action + */ + void fireAction(String actionKey, Object source, JComponent component); + + /** + * Fire an action given his key and his source, no widget are involved here + * + * @param actionKey the action's key + * @param source the object source of action + */ + void fireAction(String actionKey, Object source); + + /** + * Fire an action given his action's key, his source and the real action. + * <p/> + * This is a convinient method when you need to modified action before fire it. + * + * @param actionKey action's key + * @param source source of action + * @param action real action + */ + void fireAction0(String actionKey, Object source, A action); + + /** + * @param actionKey the action's key + * @return the action in cache or <code>null</code> if action is not in cache + */ + MyAbstractAction getActionFromCache(String actionKey); + + /** + * dispose all actions in cache using {@link org.codelutin.jaxx.action.MyAbstractAction#disposeUI()} on each + * action, then {@link #resetCache()} + */ + void dispose(); +} Copied: trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactoryFromProvider.java (from rev 819, trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactoryFromProvider.java) =================================================================== --- trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactoryFromProvider.java (rev 0) +++ trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/ActionFactoryFromProvider.java 2008-08-09 09:32:42 UTC (rev 820) @@ -0,0 +1,410 @@ +/* +* ##% Copyright (C) 2007, 2008 Code Lutin, Tony Chemit +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* 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 Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* ##% */ +package org.codelutin.jaxx.action; + +import jaxx.runtime.JAXXObject; +import jaxx.runtime.swing.JAXXToggleButton; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.codelutin.jaxx.action.initializer.AbstractActionConfigurationResolver; +import org.codelutin.jaxx.action.initializer.ActionConfigConfigurationResolver; +import org.codelutin.jaxx.action.initializer.ActionConfigurationResolver; +import org.codelutin.jaxx.action.initializer.SelectActionConfigConfigurationResolver; +import org.codelutin.jaxx.action.initializer.ToggleActionConfigConfigurationResolver; + +import javax.swing.AbstractButton; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import java.awt.event.ActionEvent; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.TreeMap; + +/** + * A simple implementation of {@link ActionFactory} using some {@link ActionProvider} to seek actions. + * <p/> + * <p/> + * An entry is in that form : <code>action.actionName=fqn</code> where + * <p/> + * <code>actionName</code> is the key of action used in factory, and + * <code>fqn</code> is the fully qualified name of the implemented action class. + * <p/> + * A special clase is to have for a given entry a key like this : <code>action.:fqn'=fqn</code>, in that case, + * le fqn' is a classe of type {@link org.codelutin.jaxx.action.ActionNameProvider} which gives us at + * runtime the names of each entry to put in cache for the givne action fqn. + * + * @author chemit + */ +public class ActionFactoryFromProvider<A extends MyAbstractAction> implements ActionFactory<A> { + + protected static Log log = LogFactory.getLog(ActionFactoryFromProvider.class); + + public static <A extends MyAbstractAction> ActionFactory<A> newInstance(Class<A> klazz) { + return new ActionFactoryFromProvider<A>(klazz); + } + + /** class of encapsuling action */ + protected Class<A> baseImpl; + + /** dictionary of known actions implementations */ + private Map<String, Class<? extends MyAbstractAction>> impls; + + /** dictionary of instanciated actions */ + private Map<String, A> cache; + + protected final ActionConfigConfigurationResolver actionConfigInitializer; + protected final ToggleActionConfigConfigurationResolver toggleActionConfigInitializer; + protected final SelectActionConfigConfigurationResolver selectActionConfigInitializer; + + protected List<AbstractActionConfigurationResolver> configurationResolvers; + + protected ActionFactoryFromProvider(Class<A> baseImpl) { + this.baseImpl = baseImpl; + this.impls = init(); + this.cache = new TreeMap<String, A>(); + this.configurationResolvers = new java.util.ArrayList<AbstractActionConfigurationResolver>(); + + this.toggleActionConfigInitializer = registerInitializer(ToggleActionConfigConfigurationResolver.class); + this.actionConfigInitializer = registerInitializer(ActionConfigConfigurationResolver.class); + this.selectActionConfigInitializer = registerInitializer(SelectActionConfigConfigurationResolver.class); + } + + public Class<A> getBaseImpl() { + return baseImpl; + } + + public void resetCache() { + cache.clear(); + } + + public A get(String actionKey) { + return cache.get(actionKey); + } + + public void loadActions(JAXXObject ui) { + if (log.isDebugEnabled()) { + log.debug("for ui " + ui.getClass()); + } + for (Map.Entry<String, Class<? extends MyAbstractAction>> entry : implsEntrySet()) { + String actionKey = entry.getKey(); + Object comp = ui.getObjectById(actionKey); + if (comp == null || !(comp instanceof AbstractButton || comp instanceof JComboBox)) { + // nothing to do + continue; + } + if (log.isTraceEnabled()) { + log.trace("detect action " + actionKey); + } + if (comp instanceof AbstractButton) { + AbstractButton component = (AbstractButton) comp; + A action = newAction(actionKey, component); + + component.setAction(action); + + if (component instanceof JAXXToggleButton) { + JAXXToggleButton glueComponent = (JAXXToggleButton) component; + glueComponent.setIcon((Icon) action.getValue(Action.SMALL_ICON)); + Integer integer = (Integer) action.getValue(Action.MNEMONIC_KEY); + if (integer != null) { + glueComponent.setNormalMnemonic(integer); + } + glueComponent.setSelectedIcon((Icon) action.getValue(Action.SMALL_ICON + 2)); + integer = (Integer) action.getValue(Action.MNEMONIC_KEY + 2); + if (integer != null) { + glueComponent.setGlueMnemonic(integer); + } + glueComponent.setGlueText((String) action.getValue(Action.NAME + 2)); + glueComponent.setGlueTooltipText((String) action.getValue(Action.SHORT_DESCRIPTION + 2)); + + glueComponent.setNormalText((String) action.getValue(Action.NAME)); + glueComponent.setNormalTooltipText((String) action.getValue(Action.SHORT_DESCRIPTION)); + } + + Boolean value = (Boolean) action.getValue("hideActionText"); + component.setHideActionText(value != null && value); + action.setEnabled(true); + continue; + } + // is JComboBox + JComboBox component = (JComboBox) comp; + A action = newAction(actionKey, component); + + component.setAction(action); + Integer val = (Integer) action.getValue("selectedIndex"); + if (val != null && val != -1 && val < component.getItemCount() && val != component.getSelectedIndex()) { + component.setSelectedIndex(val); + } + } + } + + /** + * @param actionKey le nom de l'action tel que définie dans le fichier + * de mapping (sans le prefix action.) + * @param component le button où rattacher l'action + * @return une nouvelle instance de l'action associée à sa clef. + */ + public A newAction(String actionKey, JComponent component) { + // try first in cache + A result = getActionFromCache(actionKey); + if (result != null) { + return result; + } + + try { + result = newActionInstance(actionKey); + } catch (Exception e) { + throw new RuntimeException(e); + } + + if (log.isDebugEnabled()) { + log.debug("create <" + actionKey + " : " + result + ">"); + } + + // recherche de l'annotation de configuration + ActionConfigurationResolver<?, ?> configurationResolver = resolveActionConfiguration(result); + + if (configurationResolver != null) { + configurationResolver.applyConfiguration(component, result); + } + + try { + + if (configurationResolver != null) { + if (AbstractButton.class.isAssignableFrom(configurationResolver.getComponentImpl())) { + finalizeNewAction((AbstractButton) component, result, configurationResolver); + } + + if (JComboBox.class.isAssignableFrom(configurationResolver.getComponentImpl())) { + finalizeNewAction((JComboBox) component, result, configurationResolver); + } + + return result; + } + + if (component == null || component instanceof AbstractButton) { + finalizeNewAction((AbstractButton) component, result, configurationResolver); + return result; + } + + if (component instanceof JComboBox) { + finalizeNewAction((JComboBox) component, result, configurationResolver); + } + } finally { + // save result in cache + cache.put(actionKey, result); + } + + return result; + } + + public A newAction(String actionKey) { + return newAction(actionKey, null); + } + + public String[] getActionNames() { + return impls.keySet().toArray(new String[impls.size()]); + } + + public Set<Entry<String, Class<? extends MyAbstractAction>>> implsEntrySet() { + return impls.entrySet(); + } + + public Set<Entry<String, A>> cacheEntrySet() { + return cache.entrySet(); + } + + public void fireAction(String actionKey, Object source, JComponent component) { + A action = newAction(actionKey, component); + fireAction0(actionKey, source, action); + } + + public void fireAction(String actionKey, Object source) { + fireAction(actionKey, source, null); + } + + /** + * @param actionKey la clef de l'action + * @return l'action deja stockee dans le cache d'action, ou <code>null</code> si non trouvée. + */ + public A getActionFromCache(String actionKey) { + // on vérifie que l'action existe bien + checkRegistredAction(actionKey); + + // try in cache + if (cache.containsKey(actionKey)) { + // use cached action + A action = cache.get(actionKey); + if (log.isDebugEnabled()) { + log.debug("use cache action " + action); + } + return action; + } + return null; + } + + public void dispose() { + if (log.isInfoEnabled()) { + log.info(this); + } + for (String actionKey : getActionNames()) { + MyAbstractAction action = getActionFromCache(actionKey); + if (action != null) { + action.disposeUI(); + } + } + resetCache(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + resetCache(); + impls.clear(); + } + + /** + * @param component le button où rattacher l'action + * @param action action + * @param configurationResolver initializer + */ + protected void finalizeNewAction(AbstractButton component, MyAbstractAction action, ActionConfigurationResolver<?, ?> configurationResolver) { + + if (configurationResolver == null) { + // no configurationResolver matching, + if (component != null) { + action.putValue(Action.ACTION_COMMAND_KEY, component.getName()); + action.putValue(Action.SHORT_DESCRIPTION, component.getToolTipText()); + action.putValue(Action.SMALL_ICON, component.getIcon()); + action.putValue(Action.NAME, component.getText()); + action.putValue(Action.MNEMONIC_KEY, component.getMnemonic()); + action.putValue("hideActionText", component.getHideActionText()); + if (component instanceof JAXXToggleButton) { + JAXXToggleButton glueComponent = (JAXXToggleButton) component; + action.putValue(Action.SHORT_DESCRIPTION, glueComponent.getNormalTooltipText()); + action.putValue(Action.NAME, glueComponent.getNormalText()); + action.putValue(Action.SMALL_ICON, glueComponent.getIcon()); + action.putValue(Action.MNEMONIC_KEY, glueComponent.getNormalMnemonic()); + action.putValue(Action.SHORT_DESCRIPTION + 2, glueComponent.getGlueTooltipText()); + action.putValue(Action.NAME + 2, glueComponent.getGlueText()); + action.putValue(Action.SMALL_ICON + 2, glueComponent.getSelectedIcon()); + action.putValue(Action.MNEMONIC_KEY + 2, glueComponent.getGlueMnemonic()); + } + } + + } + + String text = (String) action.getValue(Action.NAME); + Integer mnemo = (Integer) action.getValue(Action.MNEMONIC_KEY); + if (mnemo != null && mnemo != '\0') { + int pos = text.indexOf((char) mnemo.intValue()); + if (pos == -1) { + pos = text.indexOf(Character.toLowerCase((char) mnemo.intValue())); + } + action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, pos); + } + + } + + /** + * @param component le select box où rattacher l'action + * @param action action + * @param configurationResolver initializer + */ + protected void finalizeNewAction(JComboBox component, MyAbstractAction action, ActionConfigurationResolver<?, ?> configurationResolver) { + + if (configurationResolver == null) { + action.putValue(Action.ACTION_COMMAND_KEY, component.getName()); + action.putValue(Action.SHORT_DESCRIPTION, component.getToolTipText()); + //result.putValue("selectedIndex", component.getSelectedIndex()); + } + + } + + protected ActionConfigurationResolver resolveActionConfiguration(MyAbstractAction action) { + for (ActionConfigurationResolver resolver : configurationResolvers) { + if (resolver.resolveConfiguration(action) != null) { + return resolver; + } + } + return null; + } + + protected <I extends AbstractActionConfigurationResolver> I registerInitializer(Class<I> initizalizer) { + try { + I instance = initizalizer.newInstance(); + configurationResolvers.add(instance); + return instance; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void fireAction0(String actionKey, Object source, A action) { + if (action == null) { + log.warn("could not find action " + actionKey); + return; + } + ActionEvent event = new ActionEvent(source, ActionEvent.ACTION_FIRST, actionKey); + action.actionPerformed(event); + } + + protected void checkRegistredAction(String actionKey) { + if (!impls.containsKey(actionKey)) { + throw new IllegalStateException("can not find a registered action for key " + actionKey); + } + } + + + @SuppressWarnings({"unchecked"}) + protected A newActionInstance(String actionKey) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Class<? extends MyAbstractAction> klazz = impls.get(actionKey); + MyAbstractAction result; + result = klazz.getConstructor(String.class).newInstance(actionKey); + result.putValue(Action.ACTION_COMMAND_KEY, actionKey); + if (!getBaseImpl().isAssignableFrom(klazz)) { + // the instanciated action must be boxed in the base Action of the factory + result = getBaseImpl().getConstructor(MyAbstractAction.class).newInstance(result); + } + return (A) result; + } + + + public Map<String, Class<? extends MyAbstractAction>> init() { + if (log.isDebugEnabled()) { + log.debug("start loading " + this); + } + // obtain a ServiceLoader on ActionProvider + ServiceLoader<ActionProvider> loader = ServiceLoader.load(ActionProvider.class); + Map<String, Class<? extends MyAbstractAction>> cache = new TreeMap<String, Class<? extends MyAbstractAction>>(); + + for (ActionProvider<?> actionProvider : loader) { + if (log.isDebugEnabled()) { + log.debug("found " + actionProvider); + } + cache.putAll(actionProvider.getClasses()); + } + return cache; + } + +} \ No newline at end of file Deleted: trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactory.java =================================================================== --- trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactory.java 2008-08-09 09:32:14 UTC (rev 819) +++ trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactory.java 2008-08-09 09:32:42 UTC (rev 820) @@ -1,132 +0,0 @@ -/** - * # #% Copyright (C) 2008 Code Lutin, Tony Chemit - * This program is free software; you - * can redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation; either version 2 - * 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 Public License for more details. You - * should have received a copy of the GNU General Public License along with this - * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - * - Suite 330, Boston, MA 02111-1307, USA. - * # #% - */ -package org.codelutin.jaxx.action.factory; - -import jaxx.runtime.JAXXObject; -import org.codelutin.jaxx.action.MyAbstractAction; - -import javax.swing.JComponent; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -/** - * Action factory using the ActionConfig annotations to configure the action. - * <p/> - * Use after the {@link #loadActions(jaxx.runtime.JAXXObject)} to instanciate - * actions in ui with id equals a known action... - * <p/> - * TODO Finish doc - * - * @author chemit - */ -public interface ActionFactory<A extends MyAbstractAction> { - - /** - * Method to init the dictionary of knwon action implementations. - * - * @return the dictionary of known action implementations - */ - Map<String, Class<? extends MyAbstractAction>> init(); - - /** @return the class of the base action of the factory. */ - Class<A> getBaseImpl(); - - /** clear the cache of instanciated actions. */ - void resetCache(); - - /** - * @param actionKey the key of an action - * @return the action with this key from cache, or <code>null</code> if this action is not in cache - */ - A get(String actionKey); - - /** - * For a given ui, load all actions registred in factory. - * <p/> - * The id of the widget in ui is directly mapped to a action key. - * - * @param ui the ui to treate - */ - void loadActions(JAXXObject ui); - - /** - * Obtain an action instance given his key and widget - * - * @param actionKey the key of action - * @param component the component using the action - * @return the instanciated action (could come from cache if already instanciated {@link #getActionFromCache(String)} - */ - A newAction(String actionKey, JComponent component); - - /** - * Obtain an action instance given his key (should call {@link #newAction(String, javax.swing.JComponent)} - * <p/> - * This is a convinient method when you want to obtain an action with no attached widget. - * - * @param actionKey the key of action - * @return the instanciated action (could come from cache if already instanciated {@link #getActionFromCache(String)} - */ - A newAction(String actionKey); - - /** @return the array of names of all actions known by the factory */ - String[] getActionNames(); - - /** @return the set of all the action's classes known by the factory. */ - Set<Entry<String, Class<? extends MyAbstractAction>>> implsEntrySet(); - - /** @return the set of all actions cached in factory indexed by their name */ - Set<Entry<String, A>> cacheEntrySet(); - - /** - * Fire an action given his key, his source and tthe widget responsible of action - * - * @param actionKey the action's key - * @param source the object source of action - * @param component the component doing the action - */ - void fireAction(String actionKey, Object source, JComponent component); - - /** - * Fire an action given his key and his source, no widget are involved here - * - * @param actionKey the action's key - * @param source the object source of action - */ - void fireAction(String actionKey, Object source); - - /** - * Fire an action given his action's key, his source and the real action. - * <p/> - * This is a convinient method when you need to modified action before fire it. - * - * @param actionKey action's key - * @param source source of action - * @param action real action - */ - void fireAction0(String actionKey, Object source, A action); - - /** - * @param actionKey the action's key - * @return the action in cache or <code>null</code> if action is not in cache - */ - MyAbstractAction getActionFromCache(String actionKey); - - /** - * dispose all actions in cache using {@link org.codelutin.jaxx.action.MyAbstractAction#disposeUI()} on each - * action, then {@link #resetCache()} - */ - void dispose(); -} Deleted: trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactoryFromProvider.java =================================================================== --- trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactoryFromProvider.java 2008-08-09 09:32:14 UTC (rev 819) +++ trunk/lutinjaxx/jaxx-swing-action/src/main/java/org/codelutin/jaxx/action/factory/ActionFactoryFromProvider.java 2008-08-09 09:32:42 UTC (rev 820) @@ -1,412 +0,0 @@ -/* -* ##% Copyright (C) 2007, 2008 Code Lutin, Tony Chemit -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 -* 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 Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -* ##% */ -package org.codelutin.jaxx.action.factory; - -import jaxx.runtime.JAXXObject; -import jaxx.runtime.swing.JAXXToggleButton; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.codelutin.jaxx.action.ActionProvider; -import org.codelutin.jaxx.action.MyAbstractAction; -import org.codelutin.jaxx.action.initializer.AbstractActionConfigurationResolver; -import org.codelutin.jaxx.action.initializer.ActionConfigConfigurationResolver; -import org.codelutin.jaxx.action.initializer.ActionConfigurationResolver; -import org.codelutin.jaxx.action.initializer.SelectActionConfigConfigurationResolver; -import org.codelutin.jaxx.action.initializer.ToggleActionConfigConfigurationResolver; - -import javax.swing.AbstractButton; -import javax.swing.Action; -import javax.swing.Icon; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import java.awt.event.ActionEvent; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.TreeMap; - -/** - * A simple implementation of {@link ActionFactory} using some {@link ActionProvider} to seek actions. - * <p/> - * <p/> - * An entry is in that form : <code>action.actionName=fqn</code> where - * <p/> - * <code>actionName</code> is the key of action used in factory, and - * <code>fqn</code> is the fully qualified name of the implemented action class. - * <p/> - * A special clase is to have for a given entry a key like this : <code>action.:fqn'=fqn</code>, in that case, - * le fqn' is a classe of type {@link org.codelutin.jaxx.action.ActionNameProvider} which gives us at - * runtime the names of each entry to put in cache for the givne action fqn. - * - * @author chemit - */ -public class ActionFactoryFromProvider<A extends MyAbstractAction> implements ActionFactory<A> { - - protected static Log log = LogFactory.getLog(ActionFactoryFromProvider.class); - - public static <A extends MyAbstractAction> ActionFactory<A> newInstance(Class<A> klazz) { - return new ActionFactoryFromProvider<A>(klazz); - } - - /** class of encapsuling action */ - protected Class<A> baseImpl; - - /** dictionary of known actions implementations */ - private Map<String, Class<? extends MyAbstractAction>> impls; - - /** dictionary of instanciated actions */ - private Map<String, A> cache; - - protected final ActionConfigConfigurationResolver actionConfigInitializer; - protected final ToggleActionConfigConfigurationResolver toggleActionConfigInitializer; - protected final SelectActionConfigConfigurationResolver selectActionConfigInitializer; - - protected List<AbstractActionConfigurationResolver> configurationResolvers; - - protected ActionFactoryFromProvider(Class<A> baseImpl) { - this.baseImpl = baseImpl; - this.impls = init(); - this.cache = new TreeMap<String, A>(); - this.configurationResolvers = new java.util.ArrayList<AbstractActionConfigurationResolver>(); - - this.toggleActionConfigInitializer = registerInitializer(ToggleActionConfigConfigurationResolver.class); - this.actionConfigInitializer = registerInitializer(ActionConfigConfigurationResolver.class); - this.selectActionConfigInitializer = registerInitializer(SelectActionConfigConfigurationResolver.class); - } - - public Class<A> getBaseImpl() { - return baseImpl; - } - - public void resetCache() { - cache.clear(); - } - - public A get(String actionKey) { - return cache.get(actionKey); - } - - public void loadActions(JAXXObject ui) { - if (log.isDebugEnabled()) { - log.debug("for ui " + ui.getClass()); - } - for (Map.Entry<String, Class<? extends MyAbstractAction>> entry : implsEntrySet()) { - String actionKey = entry.getKey(); - Object comp = ui.getObjectById(actionKey); - if (comp == null || !(comp instanceof AbstractButton || comp instanceof JComboBox)) { - // nothing to do - continue; - } - if (log.isTraceEnabled()) { - log.trace("detect action " + actionKey); - } - if (comp instanceof AbstractButton) { - AbstractButton component = (AbstractButton) comp; - A action = newAction(actionKey, component); - - component.setAction(action); - - if (component instanceof JAXXToggleButton) { - JAXXToggleButton glueComponent = (JAXXToggleButton) component; - glueComponent.setIcon((Icon) action.getValue(Action.SMALL_ICON)); - Integer integer = (Integer) action.getValue(Action.MNEMONIC_KEY); - if (integer != null) { - glueComponent.setNormalMnemonic(integer); - } - glueComponent.setSelectedIcon((Icon) action.getValue(Action.SMALL_ICON + 2)); - integer = (Integer) action.getValue(Action.MNEMONIC_KEY + 2); - if (integer != null) { - glueComponent.setGlueMnemonic(integer); - } - glueComponent.setGlueText((String) action.getValue(Action.NAME + 2)); - glueComponent.setGlueTooltipText((String) action.getValue(Action.SHORT_DESCRIPTION + 2)); - - glueComponent.setNormalText((String) action.getValue(Action.NAME)); - glueComponent.setNormalTooltipText((String) action.getValue(Action.SHORT_DESCRIPTION)); - } - - Boolean value = (Boolean) action.getValue("hideActionText"); - component.setHideActionText(value != null && value); - action.setEnabled(true); - continue; - } - // is JComboBox - JComboBox component = (JComboBox) comp; - A action = newAction(actionKey, component); - - component.setAction(action); - Integer val = (Integer) action.getValue("selectedIndex"); - if (val != null && val != -1 && val < component.getItemCount() && val != component.getSelectedIndex()) { - component.setSelectedIndex(val); - } - } - } - - /** - * @param actionKey le nom de l'action tel que définie dans le fichier - * de mapping (sans le prefix action.) - * @param component le button où rattacher l'action - * @return une nouvelle instance de l'action associée à sa clef. - */ - public A newAction(String actionKey, JComponent component) { - // try first in cache - A result = getActionFromCache(actionKey); - if (result != null) { - return result; - } - - try { - result = newActionInstance(actionKey); - } catch (Exception e) { - throw new RuntimeException(e); - } - - if (log.isDebugEnabled()) { - log.debug("create <" + actionKey + " : " + result + ">"); - } - - // recherche de l'annotation de configuration - ActionConfigurationResolver<?, ?> configurationResolver = resolveActionConfiguration(result); - - if (configurationResolver != null) { - configurationResolver.applyConfiguration(component, result); - } - - try { - - if (configurationResolver != null) { - if (AbstractButton.class.isAssignableFrom(configurationResolver.getComponentImpl())) { - finalizeNewAction((AbstractButton) component, result, configurationResolver); - } - - if (JComboBox.class.isAssignableFrom(configurationResolver.getComponentImpl())) { - finalizeNewAction((JComboBox) component, result, configurationResolver); - } - - return result; - } - - if (component == null || component instanceof AbstractButton) { - finalizeNewAction((AbstractButton) component, result, configurationResolver); - return result; - } - - if (component instanceof JComboBox) { - finalizeNewAction((JComboBox) component, result, configurationResolver); - } - } finally { - // save result in cache - cache.put(actionKey, result); - } - - return result; - } - - public A newAction(String actionKey) { - return newAction(actionKey, null); - } - - public String[] getActionNames() { - return impls.keySet().toArray(new String[impls.size()]); - } - - public Set<Entry<String, Class<? extends MyAbstractAction>>> implsEntrySet() { - return impls.entrySet(); - } - - public Set<Entry<String, A>> cacheEntrySet() { - return cache.entrySet(); - } - - public void fireAction(String actionKey, Object source, JComponent component) { - A action = newAction(actionKey, component); - fireAction0(actionKey, source, action); - } - - public void fireAction(String actionKey, Object source) { - fireAction(actionKey, source, null); - } - - /** - * @param actionKey la clef de l'action - * @return l'action deja stockee dans le cache d'action, ou <code>null</code> si non trouvée. - */ - public A getActionFromCache(String actionKey) { - // on vérifie que l'action existe bien - checkRegistredAction(actionKey); - - // try in cache - if (cache.containsKey(actionKey)) { - // use cached action - A action = cache.get(actionKey); - if (log.isDebugEnabled()) { - log.debug("use cache action " + action); - } - return action; - } - return null; - } - - public void dispose() { - if (log.isInfoEnabled()) { - log.info(this); - } - for (String actionKey : getActionNames()) { - MyAbstractAction action = getActionFromCache(actionKey); - if (action != null) { - action.disposeUI(); - } - } - resetCache(); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - resetCache(); - impls.clear(); - } - - /** - * @param component le button où rattacher l'action - * @param action action - * @param configurationResolver initializer - */ - protected void finalizeNewAction(AbstractButton component, MyAbstractAction action, ActionConfigurationResolver<?, ?> configurationResolver) { - - if (configurationResolver == null) { - // no configurationResolver matching, - if (component != null) { - action.putValue(Action.ACTION_COMMAND_KEY, component.getName()); - action.putValue(Action.SHORT_DESCRIPTION, component.getToolTipText()); - action.putValue(Action.SMALL_ICON, component.getIcon()); - action.putValue(Action.NAME, component.getText()); - action.putValue(Action.MNEMONIC_KEY, component.getMnemonic()); - action.putValue("hideActionText", component.getHideActionText()); - if (component instanceof JAXXToggleButton) { - JAXXToggleButton glueComponent = (JAXXToggleButton) component; - action.putValue(Action.SHORT_DESCRIPTION, glueComponent.getNormalTooltipText()); - action.putValue(Action.NAME, glueComponent.getNormalText()); - action.putValue(Action.SMALL_ICON, glueComponent.getIcon()); - action.putValue(Action.MNEMONIC_KEY, glueComponent.getNormalMnemonic()); - action.putValue(Action.SHORT_DESCRIPTION + 2, glueComponent.getGlueTooltipText()); - action.putValue(Action.NAME + 2, glueComponent.getGlueText()); - action.putValue(Action.SMALL_ICON + 2, glueComponent.getSelectedIcon()); - action.putValue(Action.MNEMONIC_KEY + 2, glueComponent.getGlueMnemonic()); - } - } - - } - - String text = (String) action.getValue(Action.NAME); - Integer mnemo = (Integer) action.getValue(Action.MNEMONIC_KEY); - if (mnemo != null && mnemo != '\0') { - int pos = text.indexOf((char) mnemo.intValue()); - if (pos == -1) { - pos = text.indexOf(Character.toLowerCase((char) mnemo.intValue())); - } - action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, pos); - } - - } - - /** - * @param component le select box où rattacher l'action - * @param action action - * @param configurationResolver initializer - */ - protected void finalizeNewAction(JComboBox component, MyAbstractAction action, ActionConfigurationResolver<?, ?> configurationResolver) { - - if (configurationResolver == null) { - action.putValue(Action.ACTION_COMMAND_KEY, component.getName()); - action.putValue(Action.SHORT_DESCRIPTION, component.getToolTipText()); - //result.putValue("selectedIndex", component.getSelectedIndex()); - } - - } - - protected ActionConfigurationResolver resolveActionConfiguration(MyAbstractAction action) { - for (ActionConfigurationResolver resolver : configurationResolvers) { - if (resolver.resolveConfiguration(action) != null) { - return resolver; - } - } - return null; - } - - protected <I extends AbstractActionConfigurationResolver> I registerInitializer(Class<I> initizalizer) { - try { - I instance = initizalizer.newInstance(); - configurationResolvers.add(instance); - return instance; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public void fireAction0(String actionKey, Object source, A action) { - if (action == null) { - log.warn("could not find action " + actionKey); - return; - } - ActionEvent event = new ActionEvent(source, ActionEvent.ACTION_FIRST, actionKey); - action.actionPerformed(event); - } - - protected void checkRegistredAction(String actionKey) { - if (!impls.containsKey(actionKey)) { - throw new IllegalStateException("can not find a registered action for key " + actionKey); - } - } - - - @SuppressWarnings({"unchecked"}) - protected A newActionInstance(String actionKey) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { - Class<? extends MyAbstractAction> klazz = impls.get(actionKey); - MyAbstractAction result; - result = klazz.getConstructor(String.class).newInstance(actionKey); - result.putValue(Action.ACTION_COMMAND_KEY, actionKey); - if (!getBaseImpl().isAssignableFrom(klazz)) { - // the instanciated action must be boxed in the base Action of the factory - result = getBaseImpl().getConstructor(MyAbstractAction.class).newInstance(result); - } - return (A) result; - } - - - public Map<String, Class<? extends MyAbstractAction>> init() { - if (log.isDebugEnabled()) { - log.debug("start loading " + this); - } - // obtain a ServiceLoader on ActionProvider - ServiceLoader<ActionProvider> loader = ServiceLoader.load(ActionProvider.class); - Map<String, Class<? extends MyAbstractAction>> cache = new TreeMap<String, Class<? extends MyAbstractAction>>(); - - for (ActionProvider<?> actionProvider : loader) { - if (log.isDebugEnabled()) { - log.debug("found " + actionProvider); - } - cache.putAll(actionProvider.getClasses()); - } - return cache; - } - -} \ No newline at end of file