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

com.pekinsoft.framework.ActionMapX Maven / Gradle / Ivy

/*
 * Copyright (C) 2024 PekinSOFT Systems
 *
 * 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 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 Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 *
 * *****************************************************************************
 *  Project    :   application-framework-api
 *  Class      :   ActionMapX.java
 *  Author     :   Sean Carrick
 *  Created    :   Jul 13, 2024
 *  Modified   :   Jul 13, 2024
 *
 *  Purpose: See class JavaDoc for explanation
 *
 *  Revision History:
 *
 *  WHEN          BY                   REASON
 *  ------------  -------------------  -----------------------------------------
 *  Jul 13, 2024  Sean Carrick         Initial creation.
 * *****************************************************************************
 */
package com.pekinsoft.framework;

import com.pekinsoft.api.Targetable;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import javax.swing.Action;
import javax.swing.ActionMap;

/**
 * {@code ActionMapX} is an extension of the
 * {@link javax.swing.ActionMap ActionMap} that has been created to play nicely
 * within the context of an Application Framework application, and with the
 * {@link ActionX} actions.
 *
 * @author Sean Carrick <sean at pekinsoft dot com>
 *
 * @version 1.0
 * @since 1.0
 */
public class ActionMapX extends ActionMap {

    public ActionMapX(ApplicationContext context, Class actionsClass,
            Object actionsObject, ResourceMap resourceMap) {
        Objects.requireNonNull(context, "null ApplicationContext");
        Objects.requireNonNull(actionsClass, "null actionsClass");
        Objects.requireNonNull(actionsObject, "null actionsObject");
        if (!(actionsClass.isInstance(actionsObject))) {
            throw new IllegalArgumentException(
                    "actionsObject not an instance of "
                    + "actionsClass");
        }

        this.context = context;
        this.actionsClass = actionsClass;
        this.actionsObject = actionsObject;
        this.resourceMap = resourceMap;
        this.proxyActions = new ArrayList<>();
        addAnnotationActions(resourceMap);
        maybeAddActionsPCL();
    }

    public final Class getActionsClass() {
        return actionsClass;
    }

    public final Object getActionsObject() {
        return actionsObject;
    }

    public ActionX addAction(ActionX action) {
        return addAction(action.getValue(Action.ACTION_COMMAND_KEY), action);
    }

    /**
     * Adds an {@link ActionX} to the {@code ActionMapX}.
     *
     * @param id     the value of the action ID, which is the value of the
     *               {@link Action#ACTION_COMMAND_KEY}
     * @param action the {@code ActionX} to be added
     *
     * @return the action that was added
     */
    public ActionX addAction(Object id, ActionX action) {
        put(id, action);
        return action;
    }

    /**
     * Retrieves the IDs for all of the managed actions.
     * 

* An action ID is a unique identifier which can be used to retrieve the * corresponding Action from the {@code ActionMap}. This identifier can also * be used to set the properties of the action through the ActionMap, like * setting the state of the enabled or selected properties. *

* If this {@code ActionMapX} contains no action IDs, an empty {@link Set} * is returned. Guaranteed to not return {@code null}. * * @return a set which represents all the action IDs */ public Set getActionIds() { Object[] keys = keys(); if (keys == null) { return Collections.emptySet(); } return new HashSet<>(Arrays.asList(keys)); } /** * Retrieves the action corresponding to an action ID. * * @param id value of the action ID * * @return an Action or {@code null} if the ID is not found */ public Action getAction(Object id) { return get(id); } /** * All of the {@code @ProxyActions} recursively defined by this * {@code ActionMapX} and its parent ancestors. *

* Returns a read-only list of the {@code @ProxyActions} defined by * this {@code ActionX}'s {@code actionsClass} and, recursively, by this * {@code ActionMapX}'s parent(s). If there are no {@code proxyActions}, an * empty list is returned. * * @return a list of all the {@code proxyActions} for this * {@code ActionMapX} */ public List getProxyActions() { ArrayList allProxyActions = new ArrayList<>( proxyActions); ActionMap parent = getParent(); while (parent != null) { if (parent instanceof ActionMapX amx) { allProxyActions.addAll(amx.getProxyActions()); } parent = parent.getParent(); } return Collections.unmodifiableList(allProxyActions); } private String aString(String s, String emptyValue) { return (s.length() == 0) ? emptyValue : s; } private void putAction(String key, ActionX action) { if (get(key) != null) { logger.log(Level.WARNING, key + " already exists in the ActionMap. " + "Try a different key that is unique."); throw new IllegalArgumentException("Redundant keys: " + key); } put(key, action); } /* * Add actions for each actionsClass method with an @AppAction annotation * and for the class's @ProxyActions annotation. */ private void addAnnotationActions(ResourceMap resourceMap) { Class actionsClass = getActionsClass(); for (Method m : actionsClass.getDeclaredMethods()) { AppAction appAction = m.getAnnotation(AppAction.class); if (appAction != null) { String methodName = m.getName(); String enabledProperty = aString(appAction.enabledProperty(), null); String selectedProperty = aString(appAction.selectedProperty(), null); String actionName = aString(appAction.name(), methodName); BackgroundTask.BlockingScope block = appAction.block(); String menuBaseName = aString(appAction.menuBaseName(), null); String toolbarBaseName = aString(appAction.toolbarBaseName(), null); ActionX action = null; if (appAction.isTargetable()) { try { /* * Attempt to get a static method named "getTargetable" * that may be called to get the instance of the object. * If that method does not exist, try (in order) * getDefault and getInstance. If none of the above * singleton getters exist, throw an exception. */ Method getTargetable = actionsClass.getDeclaredMethod( "getTargetable"); if (getTargetable == null) { getTargetable = actionsClass.getDeclaredMethod( "getDefault"); if (getTargetable == null) { getTargetable = actionsClass.getDeclaredMethod( "getInstance"); if (getTargetable == null) { throw new NoTargetHandlerException( actionsClass); } } } Targetable target = (Targetable) getTargetable.invoke( actionsObject); TargetManager.getInstance().setTarget(target); action.putValue(ActionX.MENU_BASE_NAME, appAction.menuBaseName()); action.putValue(ActionX.MENU_ACTION_INDEX, appAction.menuActionIndex()); action.putValue(ActionX.MENU_PRE_SEPARATOR, appAction.menuSepBefore()); action.putValue(ActionX.MENU_POST_SEPARATOR, appAction.menuSepAfter()); action.putValue(ActionX.TOOLBAR_NAME, resourceMap.getString(appAction.toolbarBaseName() + ".toolbar.name")); action.putValue(ActionX.TOOLBAR_ACTION_INDEX, appAction.toolbarActionIndex()); action.putValue(ActionX.TOOLBAR_INDEX, resourceMap.getByte(appAction.toolbarBaseName() + ".toolbar.index")); action.putValue(ActionX.TOOLBAR_POST_SEPARATOR, appAction.toolbarSepAfter()); action.putValue(ActionX.TOOLBAR_PRE_SEPARATOR, appAction.toolbarSepBefore()); action.putValue(ActionX.GROUP, appAction.groupId()); } catch (NoTargetHandlerException | IllegalAccessException | NoSuchMethodException | SecurityException | InvocationTargetException e) { NoTargetHandlerException nth = new NoTargetHandlerException(actionsClass); nth.initCause(e); throw nth; } } else { action = new ActionX( this, resourceMap, actionName, m, enabledProperty, selectedProperty, block, menuBaseName, appAction.menuActionIndex(), appAction.menuSepBefore(), appAction.menuSepAfter(), toolbarBaseName != null, toolbarBaseName, appAction.toolbarActionIndex(), appAction.toolbarSepBefore(), appAction.toolbarSepAfter()); action.putValue(ActionX.MENU_BASE_NAME, appAction.menuBaseName()); action.putValue(ActionX.MENU_ACTION_INDEX, appAction.menuActionIndex()); action.putValue(ActionX.MENU_PRE_SEPARATOR, appAction.menuSepBefore()); action.putValue(ActionX.MENU_POST_SEPARATOR, appAction.menuSepAfter()); action.putValue(ActionX.TOOLBAR_NAME, resourceMap.getString(appAction.toolbarBaseName() + ".toolbar.name")); action.putValue(ActionX.TOOLBAR_ACTION_INDEX, appAction.toolbarActionIndex()); action.putValue(ActionX.TOOLBAR_INDEX, resourceMap.getByte(appAction.toolbarBaseName() + ".toolbar.index")); action.putValue(ActionX.TOOLBAR_POST_SEPARATOR, appAction.toolbarSepAfter()); action.putValue(ActionX.TOOLBAR_PRE_SEPARATOR, appAction.toolbarSepBefore()); action.putValue(ActionX.GROUP, appAction.groupId()); } putAction(actionName, action); } } ProxyActions proxyActionAnnotation = actionsClass.getAnnotation( ProxyActions.class); if (proxyActionAnnotation != null) { for (String actionName : proxyActionAnnotation.values()) { ActionX action = new ActionX(this, resourceMap, actionName); action.setEnabled(false); // will track the enabled property if bound putAction(actionName, action); proxyActions.add(action); } } } /* * If any of the ActionX need to track an enabled or selected property * defined in the action class, then add our PropertyChangeListener. If none * of the @AppActions in acitonClass provide an enabled or selected property * argument, then we don't need to do this. */ private void maybeAddActionsPCL() { boolean needsPCL = false; Object[] keys = keys(); if (keys != null) { for (Object key : keys) { Action value = get(key); if (value instanceof ActionX ax) { if ((ax.getEnabledProperty() != null) || ax.getSelectedProperty() != null) { needsPCL = true; break; } } } if (needsPCL) { try { Class aclass = getActionsClass(); Method m = actionsClass.getMethod( "addPropertyChangeListener", PropertyChangeListener.class); m.invoke(getActionsObject(), new ActionsPCL()); } catch (IllegalAccessException | NoSuchMethodException | SecurityException | InvocationTargetException e) { String s = "addPropertyChangeListener undefined " + actionsClass; throw new Error(s); } } } } private static final Logger logger = System.getLogger(ActionMapX.class. getName()); private final ApplicationContext context; private final ResourceMap resourceMap; private final Class actionsClass; private final Object actionsObject; private final List proxyActions; // ------------------------------------------- Private Class Declarations -- private class ActionsPCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent pce) { String propertyName = pce.getPropertyName(); Object[] keys = keys(); if (keys != null) { for (Object key : keys) { Action value = get(key); if (value instanceof ActionX ax) { if (propertyName.equals(ax.getEnabledProperty())) { ax.forwardPropertyChangeEvent(pce, "enabled"); } else if (propertyName.equals(ax.getSelectedProperty())) { ax.forwardPropertyChangeEvent(pce, "selected"); } } } } } } }