/**
* Get more info at : www.jrebirth.org .
* Copyright JRebirth.org © 2011-2013
* Contact : sebastien.bordes@jrebirth.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jrebirth.af.core.link;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import org.jrebirth.af.api.annotation.AfterInit;
import org.jrebirth.af.api.annotation.BeforeInit;
import org.jrebirth.af.api.annotation.LinkComponent;
import org.jrebirth.af.api.annotation.LinkInnerComponent;
import org.jrebirth.af.api.annotation.OnRelease;
import org.jrebirth.af.api.annotation.SkipAnnotation;
import org.jrebirth.af.api.command.Command;
import org.jrebirth.af.api.component.basic.Component;
import org.jrebirth.af.api.exception.CoreException;
import org.jrebirth.af.api.exception.CoreRuntimeException;
import org.jrebirth.af.api.facade.FacadeReady;
import org.jrebirth.af.api.log.JRLogger;
import org.jrebirth.af.api.service.Service;
import org.jrebirth.af.api.ui.Model;
import org.jrebirth.af.api.wave.annotation.OnWave;
import org.jrebirth.af.api.wave.contract.WaveType;
import org.jrebirth.af.core.component.basic.AbstractComponent;
import org.jrebirth.af.core.component.basic.InnerComponentBase;
import org.jrebirth.af.core.log.JRLoggerFactory;
import org.jrebirth.af.core.util.ClassUtility;
import org.jrebirth.af.core.util.MultiMap;
import org.jrebirth.af.core.wave.WaveTypeRegistry;
/**
* The class <strong>ComponentEnhancer</strong> is an utility class used to manage Components Annotations.
*
* @author Sébastien Bordes
*/
public final class ComponentEnhancer implements LinkMessages {
/** The class logger. */
private static final JRLogger LOGGER = JRLoggerFactory.getLogger(ComponentEnhancer.class);
/**
* Private Constructor.
*/
private ComponentEnhancer() {
// Nothing to do
}
/**
* Check if annotation can be processed for the given class.
*
* @param componentClass the class to check
*
* @return true if annotation can be processed
*/
public static boolean canProcessAnnotation(final Class<? extends Component<?>> componentClass) {
final SkipAnnotation skip = ClassUtility.getLastClassAnnotation(componentClass, SkipAnnotation.class);
// No annotation or annotation deactivated ==> skip annotation processing
return !(skip == null || skip.value());
}
/**
* Inject component.
*
* @param component the component
*/
public static void injectComponent(final Component<?> component) {
// Retrieve all fields annotated with LinkComponent
for (final Field field : ClassUtility.getAnnotatedFields(component.getClass(), LinkComponent.class)) {
final String keyPart = field.getAnnotation(LinkComponent.class).value();
if (keyPart.isEmpty()) {
inject(component, field);
} else {
inject(component, field, keyPart);
}
}
}
/**
* Inject a component into the property of an other.
*
* @param component the component
* @param field the field
* @param keyParts the key parts
*/
@SuppressWarnings("unchecked")
private static void inject(final FacadeReady<?> component, final Field field, final Object... keyParts) {
try {
if (Command.class.isAssignableFrom(field.getType())) {
ClassUtility.setFieldValue(field, component, component.getLocalFacade().getGlobalFacade().getCommandFacade().retrieve((Class<Command>) field.getType(), keyParts));
} else if (Service.class.isAssignableFrom(field.getType())) {
ClassUtility.setFieldValue(field, component, component.getLocalFacade().getGlobalFacade().getServiceFacade().retrieve((Class<Service>) field.getType(), keyParts));
} else if (Model.class.isAssignableFrom(field.getType())) {
ClassUtility.setFieldValue(field, component, component.getLocalFacade().getGlobalFacade().getUiFacade().retrieve((Class<Model>) field.getType(), keyParts));
}
} catch (IllegalArgumentException | CoreException e) {
LOGGER.error(COMPONENT_INJECTION_FAILURE, component.getClass(), e);
}
}
/**
* Inject Inner component.
*
* @param component the component
*/
public static void injectInnerComponent(final Component<?> component) {
// Retrieve all fields annotated with LinkInnerComponent
for (final Field field : ClassUtility.getAnnotatedFields(component.getClass(), LinkInnerComponent.class)) {
injectInner(component, field, field.getAnnotation(LinkInnerComponent.class).value());
}
}
/**
* Inject a component into the property of an other.
*
* @param component the component
* @param field the field
* @param keyParts the key parts
*/
@SuppressWarnings("unchecked")
private static void injectInner(final FacadeReady<?> component, final Field field, final Object... keyParts) {
final ParameterizedType innerComponentType = (ParameterizedType) field.getGenericType();
final Class<?> componentType = (Class<?>) innerComponentType.getActualTypeArguments()[0];
try {
ClassUtility.setFieldValue(field, component, InnerComponentBase.create((Class<Command>) componentType, keyParts));
} catch (IllegalArgumentException | CoreException e) {
LOGGER.error(COMPONENT_INJECTION_FAILURE, component.getClass(), e);
}
}
/**
* Parse all methods to search annotated methods that are attached to a lifecycle phase.
*
* @param component the JRebirth component to manage
*
* @return the map that store all method that should be call sorted by lifecycle phase
*/
public static MultiMap<String, Method> defineLifecycleMethod(final Component<?> component) {
final MultiMap<String, Method> lifecycleMethod = new MultiMap<>();
manageLifecycleAnnotation(component, lifecycleMethod, BeforeInit.class);
manageLifecycleAnnotation(component, lifecycleMethod, AfterInit.class);
manageLifecycleAnnotation(component, lifecycleMethod, OnRelease.class);
return lifecycleMethod;
}
/**
* Store annotated method related to a lifecycle phase.
*
* @param component the JRebirth component to manage
* @param lifecycleMethod the map that store methods
* @param annotationClass the annotation related to lifecycle phase
*/
private static void manageLifecycleAnnotation(final Component<?> component, final MultiMap<String, Method> lifecycleMethod, final Class<? extends Annotation> annotationClass) {
for (final Method method : ClassUtility.getAnnotatedMethods(component.getClass(), annotationClass)) {
// Add a method to the multimap entry
// TODO sort
lifecycleMethod.add(annotationClass.getName(), method);
}
}
/**
* Manage {@link OnWave} annotation (defined on type and method).
*
* @param component the wave ready
*/
public static void manageOnWaveAnnotation(final Component<?> component) {
// Retrieve class annotations (Java 8 add support for repeatable annotations)
for (final OnWave clsOnWave : component.getClass().getAnnotationsByType(OnWave.class)) {
manageUniqueWaveTypeAction(component, clsOnWave.value(), null);
}
// Iterate over each annotated Method and all annotations
for (final Method method : ClassUtility.getAnnotatedMethods(component.getClass(), OnWave.class)) {
for (final OnWave clsOnWave : method.getAnnotationsByType(OnWave.class)) {
manageUniqueWaveTypeAction(component, clsOnWave.value(), method);
}
}
}
/**
* Manage unique {@link WaveType} subscription (from value field of {@link OnWave} annotation).
*
* @param component the wave ready
* @param waveActionName the {@link WaveType} unique name
* @param method the wave handler method
*/
private static void manageUniqueWaveTypeAction(final Component<?> component, final String waveActionName, final Method method) {
// Get the WaveType from the WaveType registry
final WaveType wt = WaveTypeRegistry.getWaveType(waveActionName);
if (wt == null) {
throw new CoreRuntimeException("WaveType '" + waveActionName + "' not found into WaveTypeRegistry.");
} else {
// Method is not defined or is the default fallback one
if (method == null || AbstractComponent.PROCESS_WAVE_METHOD_NAME.equals(method.getName())) {
// Just listen the WaveType
component.listen(wt);
} else {
// Listen the WaveType and specify the right method handler
component.listen(null, method, wt);
}
}
}
}