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);
}
}