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

org.nuiton.jaxx.runtime.JAXXUtil Maven / Gradle / Ivy

There is a newer version: 3.1.5
Show newest version
/*
 * #%L
 * JAXX :: Runtime
 * %%
 * Copyright (C) 2008 - 2023 Code Lutin, Ultreia.io
 * %%
 * 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.jaxx.runtime;

import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.jaxx.runtime.context.JAXXContextEntryDef;
import org.nuiton.jaxx.runtime.context.JAXXInitialContext;

import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeListenerProxy;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

public class JAXXUtil {

    static private final Logger log = LogManager.getLogger(JAXXUtil.class);

    public static final String PARENT = "parent";

    // Maps root objects to lists of event listeners

    private static final Map>>
            eventListeners = new WeakHashMap<>();

    private static final Map>>
            dataBindingUpdateListeners = new WeakHashMap<>();

    private static final PropertyChangeListener[] EMPTY_ARRAY_PROPERTY_CHANGE_LISTENERS = new PropertyChangeListener[0];

    private static class EventListenerDescriptor {

        Class listenerClass;

        String listenerMethodName;

        String methodName;

        Object eventListener;
    }

    /**
     * Decodes the serialized representation of a JAXXObjectDescriptor.  The
     * string must be a byte-to-character mapping of the binary serialization
     * data for a JAXXObjectDescriptor.  See the comments in
     * JAXXCompiler.createJAXXObjectDescriptorField for the rationale behind
     * this (admittedly ugly) approach.
     *
     * @param descriptor descriptor to decode
     * @return the dedoced descriptor
     */
    public static JAXXObjectDescriptor decodeJAXXObjectDescriptor(
            String descriptor) {
        try {
            return (JAXXObjectDescriptor) Base64Coder.deserialize(descriptor,
                                                                  false);
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Internal error: can't-happen error", e);
        }
    }

    public static JAXXObjectDescriptor decodeCompressedJAXXObjectDescriptor(
            String descriptor) {
        try {
            return (JAXXObjectDescriptor) Base64Coder.deserialize(descriptor,
                                                                  true);
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Internal error: can't-happen error", e);
        }
    }

    public static  JAXXContextEntryDef newContextEntryDef(
            Class klass) {
        return newContextEntryDef(null, klass);
    }

    public static  JAXXContextEntryDef newContextEntryDef(
            String name, Class klass) {
        return new JAXXContextEntryDef<>(name, (Class) klass);
    }

    public static  JAXXContextEntryDef> newListContextEntryDef() {
        return newListContextEntryDef(null);
    }

    public static  JAXXContextEntryDef> newListContextEntryDef(
            String name) {
        Class> castList = castList();
        return new JAXXContextEntryDef<>(name, castList);
    }

    public static  JAXXContextEntryDef> newMapContextEntryDef(
            String name) {
        return new JAXXContextEntryDef<>(Map.class, name);
    }

    @SuppressWarnings({"unchecked"})
    protected static  Class> castMap() {
        return (Class>) Collections.emptyMap().getClass();
    }

    @SuppressWarnings({"unchecked"})
    protected static  Class> castList() {
        return (Class>) List.of().getClass();
    }

    /**
     * Method to initialize the context of a ui.
     *
     * @param ui            the ui
     * @param parentContext the context to set in ui
     */
    public static void initContext(JAXXObject ui, JAXXContext parentContext) {

        if (parentContext instanceof JAXXInitialContext) {
            ((JAXXInitialContext) parentContext).to(ui);
        } else {
            ui.setContextValue(parentContext);
        }
        // if parentContext is a JAXXObject then
        // add an special parent entry to can go up
        if (parentContext instanceof JAXXObject) {
            ui.setContextValue(parentContext, PARENT);
        }
    }

    public static  E getEventListener(
            Class listenerClass,
            final String listenerMethodName,
            final Object methodContainer,
            String methodName) {

        WeakReference> ref =
                eventListeners.get(methodContainer);
        List descriptors = ref != null ?
                ref.get() : null;
        if (descriptors == null) {
            descriptors = new ArrayList<>();
            eventListeners.put(
                    methodContainer,
                    new WeakReference<>(
                            descriptors));
        } else {
            for (EventListenerDescriptor descriptor : descriptors) {
                if (descriptor.listenerClass.equals(listenerClass) &&
                        (listenerMethodName == null ?
                                descriptor.listenerMethodName == null :
                                listenerMethodName.equals(
                                        descriptor.listenerMethodName)) &&
                        methodName.equals(descriptor.methodName)) {
                    return (E) descriptor.eventListener;
                }
            }
        }

        // else we need to create a new listener
        final EventListenerDescriptor descriptor = new EventListenerDescriptor();
        descriptor.listenerClass = listenerClass;
        descriptor.listenerMethodName = listenerMethodName;
        descriptor.methodName = methodName;
        try {
            final List listenerMethods =
                    Arrays.asList(listenerClass.getMethods());
            Method listenerMethod = null;
            if (listenerMethodName != null) {
                for (Method listenerMethod1 : listenerMethods) {
                    if (listenerMethod1.getName().equals(listenerMethodName)) {
                        listenerMethod = listenerMethod1;
                        break;
                    }
                }
            }
            if (listenerMethodName != null && listenerMethod == null) {
                throw new IllegalArgumentException(
                        "no method named " + listenerMethodName +
                                " found in class " + listenerClass.getName());
            }
            // tchemit 2010-12-01 : we must the exact method found, some none javaBeans
            // api does use different signature for some of them listener
            // an exemple is the TableColumnModelListener : http://download.oracle.com/javase/6/docs/api/javax/swing/event/TableColumnModelListener.html
            // This fix the bug https://forge.nuiton.org/issues/show/1124
            Class[] parameterTypes;
            if (listenerMethodName != null) {

                // search an exact method, so must use the exact found listener method
                parameterTypes = listenerMethod.getParameterTypes();
            } else {

                // keep this horrible code which is not safe at all :
                // see previous comment
                parameterTypes = listenerMethods.get(0).getParameterTypes();
            }
            Class methodContainerClass = methodContainer.getClass();
            final Method targetMethod =
                    methodContainerClass.getMethod(methodName, parameterTypes);
            descriptor.eventListener =
                    Proxy.newProxyInstance(listenerClass.getClassLoader(),
                                           new Class[]{listenerClass},
                                           (proxy, method, args) -> {
                                               String methodName1 = method.getName();
                                               if (listenerMethodName == null &&
                                                       listenerMethods.contains(method) ||
                                                       methodName1.equals(listenerMethodName)) {
                                                   try {
                                                       targetMethod.setAccessible(true);
                                                       return targetMethod.invoke(
                                                               methodContainer, args);
                                                   } catch (IllegalAccessException e) {
                                                       throw new RuntimeException(
                                                               "could not invoke on container " +
                                                                       methodContainer, e);
                                                   } catch (InvocationTargetException e) {
                                                       throw new RuntimeException(e);
                                                   }
                                               }
                                               if (methodName1.equals("toString")) {
                                                   return descriptor.toString();
                                               }
                                               if (methodName1.equals("equals")) {
                                                   return descriptor.eventListener == args[0];
                                               }
                                               if (methodName1.equals("hashCode")) {
                                                   return descriptor.hashCode();
                                               }
                                               return null;
                                           });
            descriptors.add(descriptor);
            return (E) descriptor.eventListener;
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public static  E getEventListener(
            Class listenerClass,
            Object methodContainer,
            String methodName) {
        return getEventListener(listenerClass, null, methodContainer,
                                methodName);
    }

    public static DataBindingUpdateListener getDataBindingUpdateListener(
            JAXXObject object, String bindingName) {
        WeakReference> ref =
                dataBindingUpdateListeners.get(object);
        List listeners = ref == null ? null :
                ref.get();
        if (listeners == null) {
            listeners = new ArrayList<>();
            dataBindingUpdateListeners.put(
                    object,
                    new WeakReference<>(
                            listeners));
        } else {
            for (DataBindingUpdateListener listener : listeners) {
                if (bindingName.equals(listener.getBindingName())) {
                    return listener;
                }
            }
        }
        DataBindingUpdateListener listener =
                new DataBindingUpdateListener(object, bindingName);
        listeners.add(listener);
        return listener;
    }

    /**
     * Remove all listeners registred in given {@code pcs}.
     *
     * @param pcs the pcs to clean
     */
    public static void destroy(PropertyChangeSupport pcs) {
        PropertyChangeListener[] listeners = pcs.getPropertyChangeListeners();
        for (PropertyChangeListener l : listeners) {
            if (log.isInfoEnabled()) {
                if (l instanceof PropertyChangeListenerProxy) {
                    PropertyChangeListenerProxy ll = (PropertyChangeListenerProxy) l;
                    log.info("remove property change listener " + ll.getPropertyName() + " : " + l);
                } else {
                    log.info("remove property change listener " + l);
                }
            }
            pcs.removePropertyChangeListener(l);
        }
    }

    /**
     * Remove all listeners registred in given {@code component}.
     *
     * @param component the pcs to clean
     */
    public static void destroy(Component component) {
        PropertyChangeListener[] listeners = component.getPropertyChangeListeners();
        for (PropertyChangeListener l : listeners) {
            if (log.isInfoEnabled()) {
                if (l instanceof PropertyChangeListenerProxy) {
                    PropertyChangeListenerProxy ll = (PropertyChangeListenerProxy) l;
                    log.info("remove property change listener " + ll.getPropertyName() + " : " + l);
                } else {
                    log.info("remove property change listener " + l);
                }
            }
            component.removePropertyChangeListener(l);
        }
    }

    /**
     * Compute the string representation of an object.
     * 

* Return empty string if given object is null * * @param value the value to write * @return the string representation of the given object or an empty string * if object is null. */ public static String getStringValue(Object value) { String result; result = value == null ? "" : value.toString(); return result; } /** * Test if some entries exists in a given context and throw an * IllegalArgumentException if not. * * @param context the context to test * @param defs the definitions of entries to seek in context * @throws IllegalArgumentException if the entry is not found in context. */ public static void checkJAXXContextEntries(JAXXContext context, JAXXContextEntryDef... defs) throws IllegalArgumentException { for (JAXXContextEntryDef def : defs) { Object value = def.getContextValue(context); if (value == null) { throw new IllegalArgumentException( "the context entry [" + def + "] ] was not found in " + "context " + context); } } } /** * Test if a type of entry exists in a given context and throw an * IllegalArgumentException if not found. *

* If entry is found, return his value in context. * * @param the type of required data * @param context the context to test * @param def the definition of the entry to seek in context * @return the value from the context * @throws IllegalArgumentException if the entry is not found in context. */ public static T checkJAXXContextEntry(JAXXContext context, JAXXContextEntryDef def) throws IllegalArgumentException { T value = def.getContextValue(context); if (value == null) { throw new IllegalArgumentException( "the context entry [" + def + "] ] was not found in " + "context " + context); } return value; } /** * To reload a binding, the method will invoke *

    *
  • {@link JAXXBinding#removeDataBinding()}
  • *
  • {@link JAXXBinding#applyDataBinding()}
  • *
* * @param binding the binding to reload. * @since 2.4.2 */ public static void reloadBinding(JAXXBinding binding) { binding.removeDataBinding(); binding.applyDataBinding(); } /** * Convinient method to reload a given binding by his id on a JAXX ui. * * @param src the ui to treate * @param bindingId the id of binding to reload. * @since 2.4.2 */ public static void reloadBinding(JAXXObject src, String bindingId) { JAXXBinding dataBinding = src.getDataBinding(bindingId); if (dataBinding != null) { reloadBinding(dataBinding); } else { if (log.isWarnEnabled()) { log.warn("Could not found binding[" + bindingId + "] on ui " + src); } } } /** * Convinient method to apply more than one binding on a JAXX ui. * * @param src the ui to treate * @param bindings the list of binding to process. */ public static void applyDataBinding(JAXXObject src, String... bindings) { for (String binding : bindings) { src.applyDataBinding(binding); } } /** * Convinient method to apply more than one binding on a JAXX ui. * * @param src the ui to treate * @param bindings the list of binding to process. */ public static void applyDataBinding(JAXXObject src, Collection bindings) { for (String binding : bindings) { src.applyDataBinding(binding); } } /** * Convinient method to process more than one binding on a JAXX ui. * * @param src the ui to treate * @param bindings the list of binding to process. */ public static void processDataBinding(JAXXObject src, String... bindings) { for (String binding : bindings) { src.processDataBinding(binding); } } /** * Convinient method to remove more than one binding on a JAXX ui. * * @param src the ui to treate * @param bindings the list of binding to process. */ public static void removeDataBinding(JAXXObject src, String... bindings) { for (String binding : bindings) { src.removeDataBinding(binding); } } /** * Convinient method to remove all than one binding on a JAXX ui. * * @param src the ui to treate * @since 2.10 */ public static void removeAllDataBindings(JAXXObject src) { JAXXBinding[] bindings = src.getDataBindings(); for (JAXXBinding binding : bindings) { removeDataBinding(src, binding.getId()); } } /** * detects all PropertychangedListener added by Jaxx uis (should be a {@link * DataBindingListener} * * @param propertyNames the array of property names to find * @param listeners the array of listeners to filter * @return the filtered listeners */ public static PropertyChangeListener[] findJaxxPropertyChangeListener( String[] propertyNames, PropertyChangeListener... listeners) { if (listeners == null || listeners.length == 0) { return EMPTY_ARRAY_PROPERTY_CHANGE_LISTENERS; } List pNames = Arrays.asList(propertyNames); List toRemove = new ArrayList<>(); for (PropertyChangeListener listener : listeners) { String pName = null; PropertyChangeListenerProxy plistener = null; if (listener instanceof PropertyChangeListenerProxy) { plistener = (PropertyChangeListenerProxy) listener; if (!pNames.contains(plistener.getPropertyName())) { // not on the good property continue; } listener = plistener.getListener(); pName = plistener.getPropertyName(); } if (plistener != null && pName != null && listener instanceof DataBindingListener) { if (log.isDebugEnabled()) { log.debug("find config listener to remove [" + pName + "] : " + listener); } toRemove.add(plistener); //toRemove.add(listener); } } return toRemove.toArray(new PropertyChangeListener[toRemove.size()]); } /** * Overrides the commons method to have generict cast fiex. * * @param type the type of objet to instanciate * @param prototype prototype of the constructor * @param parms params to pass to constructor * @param type of object to create * @return the new object * @throws Exception if something wrong * @since 2.1 */ @SuppressWarnings({"unchecked"}) public static O invokeConstructor(Class type, Class[] prototype, Object... parms) throws Exception { return ConstructorUtils.invokeConstructor(type, parms, prototype ); } /** * Copy to clipBoard the content of the given text. * * @param text text to copy to clipboard * @since 2.17 */ public static void copyToClipBoard(String text) { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); if (log.isInfoEnabled()) { log.info("Put in clipboard :\n" + text); } StringSelection selection = new StringSelection(text); clipboard.setContents(selection, selection); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy