/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.core.annotationprocessor; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import org.osgi.service.log.LogService; import org.eclipse.equinox.log.Logger; import org.eclipse.riena.core.Log4r; import org.eclipse.riena.core.singleton.SingletonProvider; import org.eclipse.riena.core.util.WeakRef; import org.eclipse.riena.core.wire.InjectExtension; import org.eclipse.riena.internal.core.annotationprocessor.IAnnotatedMethodHandlerExtension; /** * Annotation processor for {@code Object} annotations. * * @since 4.0 */ public final class AnnotationProcessor { /** * Fixed key for the target of the annotations. * @since 6.1 */ public static final String PROCESS_TARGET = "process.target"; //$NON-NLS-1$ private IAnnotatedMethodHandlerExtension[] extensions; private Map<Class<? extends Annotation>, IAnnotatedMethodHandler> handlerMap; private final Map<Object, ?> alreadyProcessed = new WeakHashMap<Object, Object>(); private final static IDisposer EMPTY_DISPOSER = new IDisposer() { public void dispose() { } }; private static final SingletonProvider<AnnotationProcessor> AP = new SingletonProvider<AnnotationProcessor>( AnnotationProcessor.class); private static final Logger LOGGER = Log4r.getLogger(AnnotationProcessor.class); /** * Answer the singleton <code>AnnotationProcessor</code> * * @return the {@code AnnotationProcessor} singleton */ public static AnnotationProcessor getInstance() { return AP.getInstance(); } private AnnotationProcessor() { // singleton } @InjectExtension() public synchronized void update(final IAnnotatedMethodHandlerExtension[] extensions) { this.extensions = extensions; handlerMap = null; } /** * Process the annotations on methods for the given {@link Object} <code>process</code> and set the {@link Object} <code>target</code> as argument. * * @param target * the {@link Object} which is passed as optional argument with the key {@link AnnotationProcessor#PROCESS_TARGET} * @param process * the {@link Object} to process * @return a {@code IDisposer} that allows to dispose everything the annotation handlers have <i>allocated</i>. * @since 6.1 */ public IDisposer processMethods(final Object target, final Object process) { final Map<String, Object> args = new HashMap<String, Object>(2); args.put(PROCESS_TARGET, target); return processMethods(process, args); } /** * Process the annotations on methods for the given {@link Object}. * * @param object * the {@link Object} to process * @return a {@code IDisposer} that allows to dispose everything the * annotation handlers have <i>allocated</i>. */ public IDisposer processMethods(final Object object) { return processMethods(object, object); } /** * Process the annotations on methods for the given {@link Object} with * optional arguments.<br> * Those arguments can be easily specified with the {@code Literal} class, * e.g. {@code Literal.map("node", node)}. * * @param object * the {@link Object} to process * @param optionalArgs * optional argument objects that might be required by the * handler * @return a {@code IDisposer} that allows to dispose everything the * annotation handlers have <i>allocated</i>. */ public IDisposer processMethods(final Object object, final Map<?, ?> optionalArgs) { synchronized (alreadyProcessed) { if (alreadyProcessed.containsKey(object)) { return EMPTY_DISPOSER; } alreadyProcessed.put(object, null); } final DisposerList disposers = new DisposerList(); processMethods(object, object.getClass(), optionalArgs, new AnnotatedOverriddenMethodsGuard(), disposers); prepareDispose(object, disposers); return disposers; } private void processMethods(final Object object, final Class<?> objectClass, final Map<?, ?> optionalArgs, final AnnotatedOverriddenMethodsGuard guard, final DisposerList disposers) { if (objectClass == Object.class) { return; } processMethods(object, objectClass.getSuperclass(), optionalArgs, guard, disposers); for (final Method method : objectClass.getDeclaredMethods()) { for (final Annotation annotation : method.getAnnotations()) { handle(annotation, object, method, optionalArgs, guard, disposers); } } } /** * Execute the handler for the given annotation,.. * <p> * <b>Note: </b>This method should only be called by annotation handlers * dealing with nested annotations. * * @param annotation * @param ridgetContainer * @param object * @param method * @param optionalArgs * @param disposers */ public void handle(final Annotation annotation, final Object object, final Method method, final Map<?, ?> optionalArgs, final AnnotatedOverriddenMethodsGuard guard, final DisposerList disposers) { final IAnnotatedMethodHandler handler = getHandler(annotation.annotationType()); if (handler != null && guard.add(annotation, method)) { handler.handleAnnotation(annotation, object, method, optionalArgs, guard, disposers); } } private synchronized IAnnotatedMethodHandler getHandler(final Class<? extends Annotation> annotationType) { if (handlerMap == null) { handlerMap = new HashMap<Class<? extends Annotation>, IAnnotatedMethodHandler>(); for (final IAnnotatedMethodHandlerExtension extension : extensions) { final IAnnotatedMethodHandler next = extension.createHandler(); final IAnnotatedMethodHandler previous = handlerMap.put(extension.getAnnotation(), next); if (previous != null) { throw new AnnotationProcessorFailure("There are at least two handlers (" + previous.getClass() //$NON-NLS-1$ + ", " + next.getClass() + ") for the same annotation " //$NON-NLS-1$//$NON-NLS-2$ + extension.getAnnotation().getName()); } } } return handlerMap.get(annotationType); } /** * Bind a {@code DisposerList} via a {@code WeakRef} to an object. The * {@code DisposerList} will be disposed if the specified object gets * garbage collected. * * @param object * @param disposers */ private void prepareDispose(final Object object, final DisposerList disposers) { if (object == null || disposers == null || disposers.isEmpty()) { return; } new WeakRef<Object>(object, new Runnable() { public void run() { try { disposers.dispose(); } catch (final Throwable t) { LOGGER.log(LogService.LOG_ERROR, "Exception occured while executing a disposer.", t); //$NON-NLS-1$ } } }); } }