org.controlsfx.control.action.ActionMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of controlsfx Show documentation
Show all versions of controlsfx Show documentation
High quality UI controls and other tools to complement the core JavaFX distribution
/**
* Copyright (c) 2013, 2015, ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.controlsfx.control.action;
import javafx.event.ActionEvent;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
/**
* Action Map provides an ability to create an action map of any object.
* Attempts to convert methods annotated with {@link ActionProxy} to {@link Action}.
*
* Code Example
* Here's a very simple example of how to use ActionMap to register a class (in
* this class it is the application class itself), and to then retrieve actions
* out of the ActionMap (via the static {@link ActionMap#action(String)} method:
*
*
*
* public class ActionMapDemo extends Application {
* public ActionMapDemo() {
* ActionMap.register(this);
* Action action11 = ActionMap.action("action11");
* Button actionButton = ActionUtils.createButton(action11);
* }
*
* @ActionProxy(text="Action 1.1", graphic="start.png", accelerator="ctrl+shift+T")
* private void action11() {
* System.out.println( "Action 1.1 is executed");
* }
* }
*
*
* If you require more control over the creation of the Action objects, you can either set the
* global ActionFactory by calling ActionMap.setActionFactory() and/or you can use the factory
* property on individual @ActionProxy declarations to set the factory on a case-by-case basis.
*
* @see ActionProxy
* @see Action
*/
public class ActionMap {
private static AnnotatedActionFactory actionFactory = new DefaultActionFactory();
private static final Map actions = new HashMap<>();
private ActionMap() {
// no-op
}
/**
* Returns the action factory used by ActionMap to construct AnnotatedAction instances. By default, this
* is an instance of {@link DefaultActionFactory}.
*/
public static AnnotatedActionFactory getActionFactory() {
return actionFactory;
}
/**
* Sets the action factory used by ActionMap to construct AnnotatedAction instances. This factory can be overridden on
* a case-by-case basis by specifying a factory class in {@link ActionProxy#factory()}
*/
public static void setActionFactory( AnnotatedActionFactory factory ) {
Objects.requireNonNull( factory );
actionFactory = factory;
}
/**
* Attempts to convert target's methods annotated with {@link ActionProxy} to {@link Action}s.
* Three types of methods are currently converted: parameter-less methods,
* methods with one parameter of type {@link ActionEvent}, and methods with two parameters
* ({@link ActionEvent}, {@link Action}).
*
* Note that this method supports safe re-registration of a given instance or of another instance of the
* same class that has already been registered. If another instance of the same class is registered, then
* those actions will now be associated with the new instance. The first instance is implicitly unregistered.
*
* Actions are registered with their id or method name if id is not defined.
*
* @param target object to work on
* @throws IllegalStateException if a method with unsupported parameters is annotated with {@link ActionProxy}.
*/
public static void register(Object target) {
for (Method method : target.getClass().getDeclaredMethods()) {
// Only process methods that have the ActionProxy annotation
Annotation[] annotations = method.getAnnotationsByType(ActionProxy.class);
if (annotations.length == 0) {
continue;
}
// Only process methods that have
// a) no parameters OR
// b) one parameter of type ActionEvent OR
// c) two parameters (ActionEvent, Action)
int paramCount = method.getParameterCount();
Class[] paramTypes = method.getParameterTypes();
if (paramCount > 2) {
throw new IllegalArgumentException( String.format( "Method %s has too many parameters", method.getName() ) );
}
if (paramCount == 1 && !ActionEvent.class.isAssignableFrom( paramTypes[0] )) {
throw new IllegalArgumentException( String.format( "Method %s -- single parameter must be of type ActionEvent", method.getName() ) );
}
if (paramCount == 2 && (!ActionEvent.class.isAssignableFrom( paramTypes[0] ) ||
!Action.class.isAssignableFrom( paramTypes[1] ))) {
throw new IllegalArgumentException( String.format( "Method %s -- parameters must be of types (ActionEvent, Action)", method.getName() ) );
}
ActionProxy annotation = (ActionProxy) annotations[0];
AnnotatedActionFactory factory = determineActionFactory( annotation );
AnnotatedAction action = factory.createAction( annotation, method, target );
String id = annotation.id().isEmpty() ? method.getName() : annotation.id();
actions.put( id, action );
}
}
private static AnnotatedActionFactory determineActionFactory( ActionProxy annotation ) {
// Default to using the global action factory
AnnotatedActionFactory factory = actionFactory;
// If an action-factory has been specified on this specific ActionProxy, then
// instantiate it and use it instead.
String factoryClassName = annotation.factory();
if (!factoryClassName.isEmpty()) {
try {
Class factoryClass = Class.forName( factoryClassName );
factory = (AnnotatedActionFactory) factoryClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException( String.format( "Action proxy refers to non-existant factory class %s", factoryClassName ), ex );
} catch (InstantiationException | IllegalAccessException ex) {
throw new IllegalStateException( String.format( "Unable to instantiate action factory class %s", factoryClassName ), ex );
}
}
return factory;
}
/**
* Removes all the actions associated with target object from the action map.
* @param target object to work on
*/
public static void unregister(Object target) {
if ( target != null ) {
Iterator> entryIter = actions.entrySet().iterator();
while (entryIter.hasNext()) {
Map.Entry entry = entryIter.next();
Object actionTarget = entry.getValue().getTarget();
if (actionTarget == null || actionTarget == target) {
entryIter.remove();
}
}
}
}
/**
* Returns action by its id.
* @param id action id
* @return action or null if id was not found
*/
public static Action action(String id) {
return actions.get(id);
}
/**
* Returns collection of actions by ids. Useful to create {@link ActionGroup}s.
* Ids starting with "---" are converted to {@link ActionUtils#ACTION_SEPARATOR}.
* Incorrect ids are ignored.
* @param ids action ids
* @return collection of actions
*/
public static Collection actions(String... ids) {
List result = new ArrayList<>();
for( String id: ids ) {
if ( id.startsWith("---")) result.add(ActionUtils.ACTION_SEPARATOR); //$NON-NLS-1$
Action action = action(id);
if ( action != null ) result.add(action);
}
return result;
}
}