blob: 56ab7ff958a018b48498d201f62363bc1666d0ed [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* 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:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.client.ui.form;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.scout.commons.BeanUtility;
import org.eclipse.scout.commons.ConfigurationUtility;
import org.eclipse.scout.commons.annotations.InjectFieldTo;
import org.eclipse.scout.commons.annotations.Order;
import org.eclipse.scout.commons.annotations.Replace;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.rt.client.ui.form.fields.ICompositeField;
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
/**
* Default implementation that inserts new fields at the right place based on their {@link Order} annotation and that
* replaces existing fields using the {@link Replace} annotation.
*
* @since 3.8.2
*/
public class DefaultFormFieldInjection implements IFormFieldInjection {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(DefaultFormFieldInjection.class);
private final Map<IFormField, Set<Class<? extends IFormField>>> m_replacingFormFieldsByContainer;
private final Map<Class<?>, Class<? extends IFormField>> m_replacementMapping;
private final ArrayList<Class<? extends IFormField>> m_injectedFieldList;
private final Object m_enclosingContext;
private Set<Class<? extends IFormField>> m_injectingFields;
private Set<Class<? extends IFormField>> m_replacingFields;
/**
* Creates a new instance for the given enclosing context (i.e. an {@link IForm} or an {@link ICompositeField}).
*
* @param enclosingContext
*/
public DefaultFormFieldInjection(Object enclosingContext) {
m_replacingFormFieldsByContainer = new HashMap<IFormField, Set<Class<? extends IFormField>>>();
m_replacementMapping = new HashMap<Class<?>, Class<? extends IFormField>>();
m_injectedFieldList = new ArrayList<Class<? extends IFormField>>();
m_enclosingContext = enclosingContext;
}
public void addField(Class<? extends IFormField> fieldClass) {
if (fieldClass == null) {
throw new IllegalArgumentException("fieldClass must not be null");
}
Replace replace = fieldClass.getAnnotation(Replace.class);
InjectFieldTo injectFieldTo = fieldClass.getAnnotation(InjectFieldTo.class);
if (replace == null && injectFieldTo == null) {
LOG.warn("Ignoring field [" + fieldClass + "] since neither @" + InjectFieldTo.class.getSimpleName() + " nor @" + Replace.class.getSimpleName() + " is declared.");
return;
}
if (replace != null && injectFieldTo != null) {
LOG.warn("@" + InjectFieldTo.class.getSimpleName() + " annotation is ignored since @" + Replace.class.getSimpleName() + " is available as well on class [" + fieldClass + "]. You should remove one of both annotations.");
}
else if (injectFieldTo != null && !ICompositeField.class.isAssignableFrom(injectFieldTo.value())) {
LOG.warn("Ignoring field [" + fieldClass + "] since it is not injected into an " + ICompositeField.class.getSimpleName() + ", but @" + InjectFieldTo.class.getSimpleName() + "(" + injectFieldTo.value() + ")");
return;
}
m_injectedFieldList.add(fieldClass);
m_injectingFields = null;
m_replacingFields = null;
}
public void addFields(Class<? extends IFormField>[] fieldClasses) {
for (Class<? extends IFormField> c : fieldClasses) {
addField(c);
}
}
/**
* @return Returns the mapping of all replaced fields or <code>null</code>, if no field replacements were performed.
*/
public Map<Class<?>, Class<? extends IFormField>> getReplacementMapping() {
if (m_replacementMapping.isEmpty()) {
return null;
}
return m_replacementMapping;
}
private void ensurePrepared() {
if (m_injectingFields == null || m_replacingFields == null) {
prepare();
}
}
private void prepare() {
m_injectingFields = new HashSet<Class<? extends IFormField>>();
m_replacingFields = new HashSet<Class<? extends IFormField>>();
// 1. separate injected fields by annotation
for (Class<? extends IFormField> f : m_injectedFieldList) {
if (f.isAnnotationPresent(Replace.class)) {
m_replacingFields.add(f);
}
else if (f.isAnnotationPresent(InjectFieldTo.class)) {
m_injectingFields.add(f);
}
}
// 2. remove transitive replacements (i.e. compute replacing leaf classes)
if (!m_replacingFields.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends IFormField>[] classes = m_replacingFields.toArray(new Class[m_replacingFields.size()]);
m_replacingFields = ConfigurationUtility.getReplacingLeafClasses(classes);
}
// 3. remove injected fields that are replaced and treat those replacing fields as injected fields
if (!m_injectingFields.isEmpty() && !m_replacingFields.isEmpty()) {
Set<Class<? extends IFormField>> replacingInjectedFields = new HashSet<Class<? extends IFormField>>();
for (Class<? extends IFormField> replacingField : m_replacingFields) {
for (Iterator<Class<? extends IFormField>> it = m_injectingFields.iterator(); it.hasNext();) {
Class<? extends IFormField> injectedField = it.next();
if (injectedField.isAssignableFrom(replacingField)) {
it.remove();
replacingInjectedFields.add(replacingField);
// do not break since injectedField could extend another injected field
}
}
}
m_replacingFields.removeAll(replacingInjectedFields);
m_injectingFields.addAll(replacingInjectedFields);
}
}
@Override
public void filterFields(IFormField container, List<Class<? extends IFormField>> fieldList) {
if (container == null || fieldList == null || fieldList.isEmpty()) {
return;
}
ensurePrepared();
if (m_replacingFields.isEmpty()) {
return;
}
Set<Class<? extends IFormField>> replacingFields = new HashSet<Class<? extends IFormField>>();
// remove all replaced classes
for (Iterator<Class<? extends IFormField>> it = fieldList.iterator(); it.hasNext();) {
Class<? extends IFormField> field = it.next();
for (Class<? extends IFormField> replacingField : m_replacingFields) {
if (field.isAssignableFrom(replacingField)) {
replacingFields.add(replacingField);
it.remove();
break;
}
}
}
if (!replacingFields.isEmpty()) {
m_replacingFormFieldsByContainer.put(container, replacingFields);
}
}
@Override
public void injectFields(IFormField container, List<IFormField> fieldList) {
if (container == null || fieldList == null || m_injectedFieldList.isEmpty()) {
return;
}
ensurePrepared();
// 1. replace fields if required
Set<Class<? extends IFormField>> replacingFields = m_replacingFormFieldsByContainer.remove(container);
if (replacingFields != null) {
for (Class<? extends IFormField> replacingField : replacingFields) {
// compute order and create field
Order order = getOrder(replacingField);
createAndInsertField(container, fieldList, replacingField, order);
addReplacementMappings(replacingField);
}
}
// 2. insert injected fields
for (Class<? extends IFormField> injectedField : m_injectingFields) {
Class<?> tmpClass = injectedField;
while (tmpClass.isAnnotationPresent(Replace.class)) {
tmpClass = tmpClass.getSuperclass();
}
if (tmpClass.getAnnotation(InjectFieldTo.class).value() == container.getClass()) {
Order order = getOrder(injectedField);
createAndInsertField(container, fieldList, injectedField, order);
addReplacementMappings(injectedField);
}
}
}
/**
* Adds class mappings from replaced classes to the given one. This method does nothing if the given
* <code>field</code> is not annotated with {@link Replace}.
*
* @param field
*/
private void addReplacementMappings(Class<? extends IFormField> field) {
Class<?> tmpClass = field;
while (tmpClass.isAnnotationPresent(Replace.class)) {
tmpClass = tmpClass.getSuperclass();
m_replacementMapping.put(tmpClass, field);
}
}
/**
* Adds the field f to the list at the right place regarding the {@link Order} annotation.
*/
private void createAndInsertField(IFormField container, List<IFormField> list, Class<? extends IFormField> fieldClass, Order order) {
//check if list already contains the field
for (IFormField f : list) {
if (f.getClass() == fieldClass) {
return;
}
}
try {
IFormField f = newInnerInstance(container, fieldClass);
// add ordered
if (order != null) {
double orderValue = order.value();
for (int i = 0, n = list.size(); i < n; i++) {
Class<?> existingClazz = list.get(i).getClass();
Order existingClazzOrder = getOrder(existingClazz);
if (existingClazzOrder != null && orderValue < existingClazzOrder.value()) {
list.add(i, f);
return;
}
}
}
// default at end
list.add(f);
}
catch (Exception e) {
LOG.error("exception while injecting a field", e);
}
}
/**
* Returns the first {@link Order} annotation found up the replacement hierarchy.
*/
private Order getOrder(Class<?> c) {
Order order = c.getAnnotation(Order.class);
while (order == null && c.isAnnotationPresent(Replace.class)) {
c = c.getSuperclass();
order = c.getAnnotation(Order.class);
}
return order;
}
/**
* Creates a new form field instance within the given container.
*
* @param container
* @param fieldClass
*/
private <T> T newInnerInstance(IFormField container, Class<T> fieldClass) throws Exception {
try {
// try to create the field using its container
T t = BeanUtility.createInstance(fieldClass, m_enclosingContext, container);
if (t != null) {
return t;
}
}
catch (Throwable t) {
}
return ConfigurationUtility.newInnerInstance(m_enclosingContext, fieldClass);
}
}