/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.job.builder;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.metamodel.util.CollectionUtils;
import org.apache.metamodel.util.EqualsBuilder;
import org.eobjects.analyzer.data.InputColumn;
import org.eobjects.analyzer.descriptors.BeanDescriptor;
import org.eobjects.analyzer.descriptors.ConfiguredPropertyDescriptor;
import org.eobjects.analyzer.job.ComponentRequirement;
import org.eobjects.analyzer.job.FilterOutcome;
import org.eobjects.analyzer.job.HasComponentRequirement;
import org.eobjects.analyzer.job.HasFilterOutcomes;
import org.eobjects.analyzer.job.SimpleComponentRequirement;
import org.eobjects.analyzer.util.CollectionUtils2;
import org.eobjects.analyzer.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("unchecked")
public class AbstractBeanWithInputColumnsBuilder<D extends BeanDescriptor<E>, E, B extends ComponentBuilder> extends
AbstractBeanJobBuilder<D, E, B> {
private static final Logger logger = LoggerFactory.getLogger(AbstractBeanWithInputColumnsBuilder.class);
private ComponentRequirement _componentRequirement;
public AbstractBeanWithInputColumnsBuilder(AnalysisJobBuilder analysisJobBuilder, D descriptor,
Class<?> builderClass) {
super(analysisJobBuilder, descriptor, builderClass);
}
/**
* Removes/clears all input columns
*/
@Override
public void clearInputColumns() {
Set<ConfiguredPropertyDescriptor> configuredProperties = getDescriptor().getConfiguredPropertiesForInput();
for (ConfiguredPropertyDescriptor configuredProperty : configuredProperties) {
if (configuredProperty.isArray()) {
setConfiguredProperty(configuredProperty, new InputColumn[0]);
} else {
setConfiguredProperty(configuredProperty, null);
}
}
}
/**
*
* @param inputColumn
* @throws IllegalArgumentException
* if the input column data type family doesn't match the types
* accepted by this transformer.
*/
@Override
public B addInputColumn(InputColumn<?> inputColumn) throws IllegalArgumentException {
ConfiguredPropertyDescriptor propertyDescriptor = getDefaultConfiguredPropertyForInput();
return addInputColumn(inputColumn, propertyDescriptor);
}
@Override
public ConfiguredPropertyDescriptor getDefaultConfiguredPropertyForInput() throws UnsupportedOperationException {
Collection<ConfiguredPropertyDescriptor> inputProperties = getDescriptor().getConfiguredPropertiesForInput(
false);
if (inputProperties.isEmpty()) {
// if there are no required input columns, try optional input
// columns
inputProperties = getDescriptor().getConfiguredPropertiesForInput(true);
}
if (inputProperties.size() == 1) {
ConfiguredPropertyDescriptor propertyDescriptor = inputProperties.iterator().next();
return propertyDescriptor;
} else {
throw new UnsupportedOperationException("There are " + inputProperties.size()
+ " named input columns in \"" + getDescriptor().getDisplayName()
+ "\", please specify which one to configure");
}
}
// this is the main "addInputColumn" method that the other similar methods
// delegate to
@Override
public B addInputColumn(InputColumn<?> inputColumn, ConfiguredPropertyDescriptor propertyDescriptor)
throws IllegalArgumentException {
if (propertyDescriptor == null || !propertyDescriptor.isInputColumn()) {
throw new IllegalArgumentException("Property is not of InputColumn type: " + propertyDescriptor);
}
final Class<?> expectedDataType = propertyDescriptor.getTypeArgument(0);
if (expectedDataType != null && expectedDataType != Object.class) {
// check input column type parameter compatibility
final Class<?> actualDataType = inputColumn.getDataType();
if (!ReflectionUtils.is(actualDataType, expectedDataType, false)) {
throw new IllegalArgumentException("Unsupported InputColumn type: " + actualDataType + ", expected: "
+ expectedDataType);
}
}
Object inputColumns = getConfiguredProperty(propertyDescriptor);
if (inputColumns == null) {
if (propertyDescriptor.isArray()) {
inputColumns = new InputColumn[] { inputColumn };
} else {
inputColumns = inputColumn;
}
} else {
inputColumns = CollectionUtils2.array(InputColumn.class, inputColumns, inputColumn);
}
setConfiguredProperty(propertyDescriptor, inputColumns);
return (B) this;
}
// this is the main "addInputColumns" method that the other similar methods
// delegate to
@Override
public B addInputColumns(Collection<? extends InputColumn<?>> inputColumns,
ConfiguredPropertyDescriptor propertyDescriptor) {
if (propertyDescriptor == null || !propertyDescriptor.isInputColumn()) {
throw new IllegalArgumentException("Property is not of InputColumn type: " + propertyDescriptor);
}
final Class<?> expectedDataType = propertyDescriptor.getTypeArgument(0);
if (expectedDataType != null && expectedDataType != Object.class) {
// check input column type parameter compatibility
for (InputColumn<?> inputColumn : inputColumns) {
final Class<?> actualDataType = inputColumn.getDataType();
if (!ReflectionUtils.is(actualDataType, expectedDataType, false)) {
throw new IllegalArgumentException("Unsupported InputColumn type: " + actualDataType
+ ", expected: " + expectedDataType);
}
}
}
Object newInputColumns = getConfiguredProperty(propertyDescriptor);
if (newInputColumns == null) {
if (propertyDescriptor.isArray()) {
InputColumn<?>[] asArray = inputColumns.toArray(new InputColumn[inputColumns.size()]);
newInputColumns = asArray;
} else {
if (inputColumns == null || inputColumns.isEmpty()) {
newInputColumns = null;
} else if (inputColumns.size() > 1) {
throw new IllegalArgumentException(
"Property type is a single InputColumn, but a collection of more than one element was given");
} else {
newInputColumns = inputColumns.iterator().next();
}
}
} else {
InputColumn<?>[] asArray = inputColumns.toArray(new InputColumn[inputColumns.size()]);
newInputColumns = CollectionUtils2.array(InputColumn.class, newInputColumns, asArray);
}
setConfiguredProperty(propertyDescriptor, newInputColumns);
return (B) this;
}
@Override
public B addInputColumns(Collection<? extends InputColumn<?>> inputColumns) {
ConfiguredPropertyDescriptor propertyDescriptor = getDefaultConfiguredPropertyForInput();
addInputColumns(inputColumns, propertyDescriptor);
return (B) this;
}
@Override
public B addInputColumns(InputColumn<?>... inputColumns) {
List<InputColumn<?>> list = Arrays.asList(inputColumns);
ConfiguredPropertyDescriptor propertyDescriptor = getDefaultConfiguredPropertyForInput();
addInputColumns(list, propertyDescriptor);
return (B) this;
}
@Override
public B removeInputColumn(InputColumn<?> inputColumn) {
Set<ConfiguredPropertyDescriptor> propertyDescriptors = getDescriptor().getConfiguredPropertiesForInput();
if (propertyDescriptors.size() == 1) {
ConfiguredPropertyDescriptor propertyDescriptor = propertyDescriptors.iterator().next();
return removeInputColumn(inputColumn, propertyDescriptor);
} else {
throw new UnsupportedOperationException("There are " + propertyDescriptors.size()
+ " named input columns, please specify which one to configure");
}
}
@Override
public B removeInputColumn(InputColumn<?> inputColumn, ConfiguredPropertyDescriptor propertyDescriptor) {
Object inputColumns = getConfiguredProperty(propertyDescriptor);
if (inputColumns != null) {
if (inputColumns == inputColumn) {
inputColumns = null;
} else {
if (inputColumns.getClass().isArray()) {
inputColumns = CollectionUtils.arrayRemove(inputColumns, inputColumn);
}
}
setConfiguredProperty(propertyDescriptor, inputColumns);
propertyDescriptor.setValue(getComponentInstance(), inputColumns);
}
return (B) this;
}
public List<InputColumn<?>> getInputColumns() {
List<InputColumn<?>> result = new LinkedList<InputColumn<?>>();
Set<ConfiguredPropertyDescriptor> configuredPropertiesForInput = getDescriptor()
.getConfiguredPropertiesForInput();
for (ConfiguredPropertyDescriptor configuredProperty : configuredPropertiesForInput) {
Object inputColumns = getConfiguredProperty(configuredProperty);
if (inputColumns != null) {
if (inputColumns.getClass().isArray()) {
int length = Array.getLength(inputColumns);
for (int i = 0; i < length; i++) {
InputColumn<?> column = (InputColumn<?>) Array.get(inputColumns, i);
if (column == null) {
logger.warn("Element no. {} in array (size {}) is null! Value read from {}", new Object[] {
i, length, configuredProperty });
} else {
result.add(column);
}
}
} else {
result.add((InputColumn<?>) inputColumns);
}
}
}
return Collections.unmodifiableList(result);
}
public void setRequirement(FilterJobBuilder<?, ?> filterJobBuilder, Enum<?> category) {
EnumSet<?> categories = filterJobBuilder.getDescriptor().getOutcomeCategories();
if (!categories.contains(category)) {
throw new IllegalArgumentException("No such category found in available outcomes: " + category);
}
setRequirement(filterJobBuilder.getFilterOutcome(category));
}
public void setRequirement(FilterOutcome outcome) throws IllegalArgumentException {
if (!validateRequirementCandidate(outcome)) {
throw new IllegalArgumentException("Cyclic dependency detected when setting requirement: " + outcome);
}
if (outcome == null) {
setComponentRequirement(null);
} else if (outcome instanceof FilterOutcome) {
setComponentRequirement(new SimpleComponentRequirement((FilterOutcome) outcome));
} else {
throw new IllegalArgumentException("Unsupported outcome type (use ComponentRequirement instead): "
+ outcome);
}
}
@Override
public void setComponentRequirement(final ComponentRequirement requirement) {
if (!EqualsBuilder.equals(_componentRequirement, requirement)) {
_componentRequirement = requirement;
onRequirementChanged();
}
}
public boolean validateRequirementSource(HasFilterOutcomes outcomeSource) {
if (outcomeSource == null) {
return true;
}
final Collection<FilterOutcome> outcomes = outcomeSource.getFilterOutcomes();
if (outcomes == null || outcomes.isEmpty()) {
return true;
}
final FilterOutcome firstOutcome = outcomes.iterator().next();
return validateRequirementCandidate(firstOutcome);
}
public boolean validateRequirementCandidate(final ComponentRequirement requirement) {
if (requirement instanceof SimpleComponentRequirement) {
final SimpleComponentRequirement simpleComponentRequirement = (SimpleComponentRequirement) requirement;
final FilterOutcome outcome = simpleComponentRequirement.getOutcome();
return validateRequirementCandidate(outcome);
}
return true;
}
public boolean validateRequirementCandidate(final FilterOutcome requirement) {
if (requirement == null) {
return true;
}
final HasFilterOutcomes source = requirement.getSource();
if (source == this) {
return false;
}
if (source instanceof HasComponentRequirement) {
final ComponentRequirement componentRequirement = ((HasComponentRequirement) source)
.getComponentRequirement();
if (componentRequirement != null) {
final Collection<FilterOutcome> requirements = componentRequirement.getProcessingDependencies();
for (FilterOutcome transitiveRequirement : requirements) {
boolean transitiveValidation = validateRequirementCandidate(transitiveRequirement);
if (!transitiveValidation) {
return false;
}
}
}
}
return true;
}
/**
* method that can be used by sub-classes to add callback logic when the
* requirement of the bean changes
*/
public void onRequirementChanged() {
}
public void setRequirement(FilterJobBuilder<?, ?> filterJobBuilder, String category) {
EnumSet<?> categories = filterJobBuilder.getDescriptor().getOutcomeCategories();
for (Enum<?> c : categories) {
if (c.name().equals(category)) {
setRequirement(filterJobBuilder.getFilterOutcome(c));
return;
}
}
throw new IllegalArgumentException("No such category found in available outcomes: " + category);
}
@Override
public ComponentRequirement getComponentRequirement() {
return _componentRequirement;
}
@Override
public InputColumn<?>[] getInput() {
List<InputColumn<?>> inputColumns = getInputColumns();
return inputColumns.toArray(new InputColumn[inputColumns.size()]);
}
}