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

org.nuiton.jaxx.runtime.context.DefaultApplicationContext Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * JAXX :: Runtime
 * %%
 * Copyright (C) 2008 - 2024 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.context;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.runtime.JAXXContext;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * The default context to be used for an application.
 *
 * This extends the {@link DefaultJAXXContext} and add a possibility to
 * auto-instanciate some classes asked via {@link #getContextValue(Class)} and
 * {@link #getContextValue(Class, String)} methods.
 *
 * To registred a such class, just annotate your class with {@link AutoLoad}.
 *
 * @author Tony Chemit - [email protected]
 * @see DefaultJAXXContext
 * @since 1.2
 */
public class DefaultApplicationContext extends DefaultJAXXContext {

    /**
     * A class annotated @AutoLoad is used by context to auto instanciate
     * the class when required.
     *
     * Note : A such class always have a public default constructor.
     *
     * @author Tony Chemit - [email protected]
     * @version 1.0, 21/02/09
     * @since 1.2
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AutoLoad {
        //TODO use this to add a method to init 

        String initMethod() default "";
    }

    /**
     * A class annotated @MethodAccess is used by context to obtain the
     * value of an entry via a declared method.
     *
     * Note : A such class must have a method called {@link #methodName()} with
     * a single String parameter.
     *
     * @author Tony Chemit - [email protected]
     * @version 1.0, 21/02/09
     * @since 1.2
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MethodAccess {

        /**
         * Define a forward to a target class. When the target class will be
         * asked with method {@link JAXXContext#getContextValue(Class, String)}
         * it will be delegating to this class.
         *
         * @return the forwarded class
         */
        Class target() default Object.class;

        /** @return the name of the method to access data */
        String methodName();
    }

    /** Map of forwarded classes (key) to classes (values). */
    protected final Map, Class> forwards;

    /**
     * Map of entries to watch associated with the property to fires if a
     * modification was found.
     */
    protected Map, String> entryListened;

    public DefaultApplicationContext() {
        forwards = new HashMap<>();
        pcs = new PropertyChangeSupport(this);
    }

    /** Logger */
    static private final Logger log =
            LogManager.getLogger(DefaultApplicationContext.class);

    /** to manage properties modifications */
    protected final PropertyChangeSupport pcs;

    @SuppressWarnings({"unchecked"})
    @Override
    public  T getContextValue(Class clazz, String name) {
        Object value;
        MethodAccess access;

        Class realClass;

        if (forwards.containsKey(clazz)) {
            // this is a forward class            
            realClass = forwards.get(clazz);
            // always call the forwarder with no name
            value = getContextValue(realClass, null);
            if (log.isDebugEnabled()) {
                log.debug("detect forward from " + clazz + " to " + realClass +
                                  " (" + value + ")");
            }

        } else {
            realClass = clazz;
            value = super.getContextValue(realClass, name);
        }

        //TC-20091007 TODO Make possible use of named autoload entries
        //(add a parameter on AutoLoad annotation)
        if (value == null) {
            AutoLoad anno = clazz.getAnnotation(AutoLoad.class);

            if (anno == null) {
                // no annotation, so do nothing
                return null;
            }

            if (name != null) {
                throw new IllegalArgumentException(
                        "an " + AutoLoad.class.getName() + " can not have " +
                                "a named context but was call with this one : " + name);
            }
            value = newInstance(clazz);
            if (!anno.initMethod().trim().isEmpty()) {
                // apply method on class
                newAccess(clazz, value, anno.initMethod().trim());
            }
            if (log.isDebugEnabled()) {
                log.debug("new instance " + clazz + " : " + value);
            }
            // save new instance            
            setContextValue(value, null);
        }

        access = realClass.getAnnotation(MethodAccess.class);

        if (access != null) {
            if (name == null) {
                if (!Object.class.equals(access.target())) {
                    // register forward
                    Class targetClass = access.target();
                    if (!forwards.containsKey(targetClass)) {
                        // register forward
                        forwards.put(targetClass, clazz);
                        if (log.isDebugEnabled()) {
                            log.debug("register forward from " + targetClass +
                                              " to " + clazz);
                        }
                    }
                }
            } else {
                // specialized access
                value = newAccess(realClass, value, access.methodName(), name);
            }
        }
        return (T) value;
    }

    @Override
    public  void removeContextValue(Class klass, String name) {
        Entry, Class> entry;
        if (name == null && forwards.containsValue(klass)) {
            // remove forward
            Iterator, Class>> itr =
                    forwards.entrySet().iterator();
            while (itr.hasNext()) {
                entry = itr.next();
                if (entry.getValue().equals(klass)) {
                    itr.remove();
                    if (log.isDebugEnabled()) {
                        log.debug("removed forward from " + entry.getKey() +
                                          " to " + klass);
                    }
                    break;
                }
            }
        }
        //FIXME should update forwards state
        JAXXContextEntryDef entryToDel = getEntry(klass, name);
        Object t = remove0(klass, name);
        if (log.isDebugEnabled()) {
            log.debug("removed object = " + t);
        }
        if (t != null && entryToDel != null) {
            // a value was removed
            // notify listeners
            fireEntryChanged(entryToDel, t, null);
        }
//        super.removeContextValue(klass, name);
    }

    @Override
    public  void setContextValue(T o, String name) {
//        super.setContextValue(o, name);
        if (name == null && PARENT_CONTEXT_ENTRY.accept2(o.getClass(), null)) {
            setParentContext((JAXXContext) o);
            return;
        }
        JAXXContextEntryDef entry = getKey(name, o.getClass());
        // first remove entry
        Object oldValue = remove0(o.getClass(), name);
        if (oldValue != null) {
            if (log.isDebugEnabled()) {
                log.debug("remove value " + oldValue.getClass() + " for " +
                                  entry);
            }
        }
        // then can put safely
        data.put(entry, o);
        if (log.isDebugEnabled()) {
            log.debug("[" + name + "] set value for entry " + entry);
        }
        // firezs
        fireEntryChanged(entry.getKlass(), name, oldValue, o);
    }

    /**
     * To add a listen modification of the given entry in the context.
     *
     * @param entry    the entry to listen
     * @param name     the property name to fire if necessary
     * @param listener the listener to notify if entry has changed
     * @since 2.0.1
     */
    public void addPropertyChangeListener(JAXXContextEntryDef entry,
                                          String name,
                                          PropertyChangeListener listener) {
        if (entryListened == null) {
            entryListened = new HashMap<>();
        }
        entryListened.put(entry, name);
        if (log.isDebugEnabled()) {
            log.debug("[" + name + "] for " + entry);
        }
        pcs.addPropertyChangeListener(name, listener);
    }

    /**
     * To remove a listen modification of the given entry in the context.
     *
     * @param entry    the entry to listen
     * @param name     the property name to fire if necessary
     * @param listener the listener to notify if entry has changed
     * @since 2.0.1
     */
    public void removePropertyChangeListener(JAXXContextEntryDef entry,
                                             String name,
                                             PropertyChangeListener listener) {
        if (entryListened == null) {
            entryListened = new HashMap<>();
        }
        entryListened.remove(entry);
        pcs.removePropertyChangeListener(name, listener);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }


    public void addPropertyChangeListener(String propertyName,
                                          PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(propertyName, listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(String propertyName,
                                             PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(propertyName, listener);
    }

    public synchronized boolean hasListeners(String propertyName) {
        return pcs.hasListeners(propertyName);
    }

    public synchronized PropertyChangeListener[] getPropertyChangeListeners(
            String propertyName) {
        return pcs.getPropertyChangeListeners(propertyName);
    }

    public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
        return pcs.getPropertyChangeListeners();
    }

    protected void fireEntryChanged(Class klass,
                                    String name,
                                    Object oldValue,
                                    Object newValue) {
        // a value was removed
        if (entryListened != null) {
            // look if the entry was listened

            if (log.isTraceEnabled()) {
                log.trace("data size : " + data.size());
                for (Entry, Object> e : data.entrySet()) {
                    log.trace(e.getKey());
                }
            }
            JAXXContextEntryDef entry2 = getEntry(klass, name);
            if (log.isDebugEnabled()) {
                log.debug("[" + name + "] : try to find a changer for " + entry2);
            }
            if (entry2 != null) {
                // entry find directly on this context
                String propertyName = entryListened.get(entry2);
                if (log.isTraceEnabled()) {
                    log.trace("registred property name = " + propertyName);
                }
                if (propertyName != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("will notify modification on " + entry2);
                    }
                    // fires the removed
                    firePropertyChange(propertyName, oldValue, newValue);
                }
            }
        }
    }

    protected void fireEntryChanged(JAXXContextEntryDef entryDef,
                                    Object oldValue,
                                    Object newValue) {
        // a value was removed
        if (entryListened != null) {
            // look if the entry was listened

            if (log.isTraceEnabled()) {
                log.trace("data size : " + data.size());
                for (Entry, Object> e : data.entrySet()) {
                    log.trace(e.getKey());
                }
            }
//            JAXXContextEntryDef entry2 = getEntry(klass, name);
//            if (log.isDebugEnabled()) {
//                log.debug("[" + name + "] : try to find a changer for " + entry2);
//            }
            if (entryDef != null) {
                // entry find directly on this context
                String propertyName = entryListened.get(entryDef);
                if (log.isTraceEnabled()) {
                    log.trace("registred property name = " + propertyName);
                }
                if (propertyName != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("will notify modification on " + entryDef);
                    }
                    // fires the removed
                    firePropertyChange(propertyName, oldValue, newValue);
                }
            }
        }
    }

    protected Object newInstance(Class clazz) throws
            IllegalArgumentException {

        Object value;

        Constructor constructor;
        try {
            constructor = clazz.getConstructor();
            // auto instanciate the class
            if (constructor == null) {
                throw new IllegalArgumentException(
                        clazz + " has no public constructor");
            }
        } catch (NoSuchMethodException | SecurityException ex) {
            throw new IllegalArgumentException(ex);
        }
        try {
            value = constructor.newInstance();

        } catch (InstantiationException | InvocationTargetException | IllegalAccessException ex) {
            throw new IllegalArgumentException(ex);
        }
        return value;
    }

    protected Object newAccess(
            Class clazz,
            Object parent,
            String methodName,
            String name) throws IllegalArgumentException {
        Object value;
        try {
            Method m = clazz.getMethod(methodName, String.class);
            value = m.invoke(parent, name);
            return value;
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | SecurityException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    protected Object newAccess(
            Class clazz,
            Object parent,
            String methodName) throws IllegalArgumentException {
        Object value;
        try {
            Method m = clazz.getMethod(methodName);
            value = m.invoke(parent);
            return value;
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | SecurityException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    protected void firePropertyChange(String name,
                                      Object oldValue,
                                      Object newValue) {
        pcs.firePropertyChange(name, oldValue, newValue);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy