/** * 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.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.eobjects.analyzer.beans.api.OutputColumns; import org.eobjects.analyzer.beans.api.Transformer; import org.eobjects.analyzer.configuration.InjectionManager; import org.eobjects.analyzer.data.InputColumn; import org.eobjects.analyzer.data.MutableInputColumn; import org.eobjects.analyzer.data.TransformedInputColumn; import org.eobjects.analyzer.descriptors.TransformerBeanDescriptor; import org.eobjects.analyzer.job.AnalysisJobImmutabilizer; import org.eobjects.analyzer.job.BeanConfiguration; import org.eobjects.analyzer.job.ComponentRequirement; import org.eobjects.analyzer.job.HasComponentRequirement; import org.eobjects.analyzer.job.IdGenerator; import org.eobjects.analyzer.job.ImmutableBeanConfiguration; import org.eobjects.analyzer.job.ImmutableTransformerJob; import org.eobjects.analyzer.job.InputColumnSinkJob; import org.eobjects.analyzer.job.InputColumnSourceJob; import org.eobjects.analyzer.job.TransformerJob; import org.eobjects.analyzer.lifecycle.LifeCycleHelper; import org.eobjects.analyzer.util.StringUtils; /** * * * @param <T> * the transformer type being configured */ public final class TransformerJobBuilder<T extends Transformer<?>> extends AbstractBeanWithInputColumnsBuilder<TransformerBeanDescriptor<T>, T, TransformerJobBuilder<T>> implements InputColumnSourceJob, InputColumnSinkJob, HasComponentRequirement { private final String _id; private final List<MutableInputColumn<?>> _outputColumns = new ArrayList<MutableInputColumn<?>>(); private final List<String> _automaticOutputColumnNames = new ArrayList<String>(); private final IdGenerator _idGenerator; private final List<TransformerChangeListener> _localChangeListeners; public TransformerJobBuilder(AnalysisJobBuilder analysisJobBuilder, TransformerBeanDescriptor<T> descriptor, IdGenerator idGenerator) { super(analysisJobBuilder, descriptor, TransformerJobBuilder.class); _id = "trans-" + idGenerator.nextId(); _idGenerator = idGenerator; _localChangeListeners = new ArrayList<TransformerChangeListener>(0); } /** * Gets the output column of this transformation with it's current * configuration. * * @return */ public List<MutableInputColumn<?>> getOutputColumns() { if (!isConfigured()) { // as long as the transformer is not configured, just return an // empty list return Collections.emptyList(); } final Transformer<?> component = getComponentInstance(); final TransformerBeanDescriptor<T> descriptor = getDescriptor(); final InjectionManager injectionManager = getAnalysisJobBuilder().getConfiguration().getInjectionManager(null); final LifeCycleHelper lifeCycleHelper = new LifeCycleHelper(injectionManager, null, false); // mimic the configuration of a real transformer bean instance final BeanConfiguration beanConfiguration = new ImmutableBeanConfiguration(getConfiguredProperties()); lifeCycleHelper.assignConfiguredProperties(descriptor, component, beanConfiguration); lifeCycleHelper.assignProvidedProperties(descriptor, component); // only validate, don't initialize lifeCycleHelper.validate(descriptor, component); final OutputColumns outputColumns = component.getOutputColumns(); if (outputColumns == null) { throw new IllegalStateException("getOutputColumns() returned null on transformer: " + component); } boolean changed = false; // adjust the amount of output columns final int expectedCols = outputColumns.getColumnCount(); final int existingCols = _outputColumns.size(); if (expectedCols != existingCols) { changed = true; int colDiff = expectedCols - existingCols; if (colDiff > 0) { for (int i = 0; i < colDiff; i++) { final int nextIndex = _outputColumns.size(); final String name = getColumnName(outputColumns, nextIndex); final String id = _id + "-" + _idGenerator.nextId(); _outputColumns.add(new TransformedInputColumn<Object>(name, id)); _automaticOutputColumnNames.add(name); } } else if (colDiff < 0) { for (int i = 0; i < Math.abs(colDiff); i++) { // remove from the tail _outputColumns.remove(_outputColumns.size() - 1); _automaticOutputColumnNames.remove(_automaticOutputColumnNames.size() - 1); } } // reset the names when the number of output columns change and the // initial name has changed for (int i = 0; i < expectedCols; i++) { final MutableInputColumn<?> column = _outputColumns.get(i); final String previousProposedName = column.getInitialName(); final String newProposedName = outputColumns.getColumnName(i); if (newProposedName != null && !newProposedName.equals(previousProposedName)) { column.setName(newProposedName); } } } // automatically update names and types of columns if they have not been // manually set for (int i = 0; i < expectedCols; i++) { final String proposedName = getColumnName(outputColumns, i); Class<?> dataType = outputColumns.getColumnType(i); if (dataType == null) { dataType = descriptor.getOutputDataType(); } final TransformedInputColumn<?> col = (TransformedInputColumn<?>) _outputColumns.get(i); col.setInitialName(proposedName); if (dataType != col.getDataType()) { col.setDataType(dataType); changed = true; } final String automaticName = _automaticOutputColumnNames.get(i); final String columnName = col.getName(); if (StringUtils.isNullOrEmpty(columnName) || automaticName.equals(columnName)) { if (proposedName != null) { col.setName(proposedName); _automaticOutputColumnNames.set(i, proposedName); } } } if (changed) { // notify listeners onOutputChanged(); } return Collections.unmodifiableList(_outputColumns); } private String getColumnName(OutputColumns outputColumns, int index) { String name = outputColumns.getColumnName(index); if (name == null) { name = getDescriptor().getDisplayName() + " (" + (index + 1) + ")"; } return name; } public void onOutputChanged() { // notify listeners List<TransformerChangeListener> listeners = getAllListeners(); for (TransformerChangeListener listener : listeners) { listener.onOutputChanged(this, _outputColumns); } } public TransformerJob toTransformerJob() throws IllegalStateException { return toTransformerJob(true); } public TransformerJob toTransformerJob(final AnalysisJobImmutabilizer immutabilizer) throws IllegalStateException { return toTransformerJob(true, immutabilizer); } public TransformerJob toTransformerJob(final boolean validate) { return toTransformerJob(validate, new AnalysisJobImmutabilizer()); } public TransformerJob toTransformerJob(final boolean validate, final AnalysisJobImmutabilizer immutabilizer) { if (validate && !isConfigured(true)) { throw new IllegalStateException("Transformer job is not correctly configured"); } final ComponentRequirement componentRequirement = immutabilizer.load(getComponentRequirement()); return new ImmutableTransformerJob(getName(), getDescriptor(), new ImmutableBeanConfiguration( getConfiguredProperties()), getOutputColumns(), componentRequirement, getMetadataProperties()); } @Override public String toString() { return "TransformerJobBuilder[transformer=" + getDescriptor().getDisplayName() + ",inputColumns=" + getInputColumns() + "]"; } /** * Builds a temporary list of all listeners, both global and local * * @return */ private List<TransformerChangeListener> getAllListeners() { List<TransformerChangeListener> globalChangeListeners = getAnalysisJobBuilder().getTransformerChangeListeners(); List<TransformerChangeListener> list = new ArrayList<TransformerChangeListener>(globalChangeListeners.size() + _localChangeListeners.size()); list.addAll(globalChangeListeners); list.addAll(_localChangeListeners); return list; } /** * Gets an output column by name. * * @see #getOutputColumns() * * @param name * @return */ public MutableInputColumn<?> getOutputColumnByName(String name) { if (StringUtils.isNullOrEmpty(name)) { return null; } final List<MutableInputColumn<?>> outputColumns = getOutputColumns(); for (MutableInputColumn<?> inputColumn : outputColumns) { if (name.equals(inputColumn.getName())) { return inputColumn; } } return null; } @Override public void onConfigurationChanged() { super.onConfigurationChanged(); // trigger getOutputColumns which will notify consumers in the case of // output changes if (isConfigured()) { getOutputColumns(); } List<TransformerChangeListener> listeners = getAllListeners(); for (TransformerChangeListener listener : listeners) { listener.onConfigurationChanged(this); } } @Override public void onRequirementChanged() { super.onRequirementChanged(); List<TransformerChangeListener> listeners = getAllListeners(); for (TransformerChangeListener listener : listeners) { listener.onRequirementChanged(this); } } @Override public InputColumn<?>[] getInput() { return getInputColumns().toArray(new InputColumn<?>[0]); } @Override public MutableInputColumn<?>[] getOutput() { return getOutputColumns().toArray(new MutableInputColumn<?>[0]); } /** * Notification method invoked when transformer is removed. */ protected void onRemoved() { List<TransformerChangeListener> listeners = getAllListeners(); for (TransformerChangeListener listener : listeners) { listener.onOutputChanged(this, new LinkedList<MutableInputColumn<?>>()); listener.onRemove(this); } } /** * Adds a change listener to this component * * @param listener */ public void addChangeListener(TransformerChangeListener listener) { _localChangeListeners.add(listener); } /** * Removes a change listener from this component * * @param listener * @return whether or not the listener was found and removed. */ public boolean removeChangeListener(TransformerChangeListener listener) { return _localChangeListeners.remove(listener); } }