/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012-2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotoolkit.processing.chain.model.event;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.event.EventListenerList;
import org.apache.sis.util.ArgumentChecks;
import org.geotoolkit.utility.parameter.ExtendedParameterDescriptor;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessFinder;
import org.geotoolkit.process.ProcessingRegistry;
import org.geotoolkit.processing.chain.ChainProcessDescriptor;
import org.geotoolkit.processing.chain.model.Chain;
import org.geotoolkit.processing.chain.model.ElementProcess;
import org.geotoolkit.processing.chain.model.ClassFull;
import org.geotoolkit.processing.chain.model.Constant;
import org.geotoolkit.processing.chain.model.DataLink;
import org.geotoolkit.processing.chain.model.Element;
import org.geotoolkit.processing.chain.model.FlowLink;
import org.geotoolkit.processing.chain.model.Parameter;
import org.apache.sis.measure.NumberRange;
import org.geotoolkit.util.collection.CollectionChangeEvent;
import org.geotoolkit.util.collection.NotifiedCheckedList;
import org.apache.sis.util.ObjectConverter;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.util.NoSuchIdentifierException;
/**
* Extends a Chain, provides automatic link cleaning and events.
*
* @author Johann Sorel (Geomatys)
*/
public class EventChain extends Chain {
/**
* listeners
*/
private final EventListenerList listeners = new EventListenerList();
/**
* Available process factories.
*/
private final List<ProcessingRegistry> factories = new ArrayList<ProcessingRegistry>();
/**
* Type convertion tester.
*/
private final ConverterMatcher matcher ;
private static Collection<? extends ProcessingRegistry> getDefaultRegistries(){
final List<ProcessingRegistry> factories = new ArrayList<ProcessingRegistry>();
final Iterator<ProcessingRegistry> ite = ProcessFinder.getProcessFactories();
while(ite.hasNext()){
factories.add(ite.next());
}
return factories;
}
public EventChain() {
this(null,getDefaultRegistries(),ConverterMatcher.DEFAULT);
}
public EventChain(final Chain chain) {
this(chain,getDefaultRegistries(),ConverterMatcher.DEFAULT);
}
public EventChain(Collection<? extends ProcessingRegistry> factories, ConverterMatcher matcher){
this(null,factories, matcher);
}
public EventChain(final Chain chain,Collection<? extends ProcessingRegistry> factories, ConverterMatcher matcher){
super(chain);
if(factories != null){
this.factories.addAll((Collection)factories);
}
if(matcher == null){
//use a default matcher, relying only on natural convertions.
this.matcher = new ConverterMatcher(
new HashMap<ClassFull, List<ClassFull>>(),
new ArrayList<ObjectConverter>());
}else{
this.matcher = matcher;
}
}
public ConverterMatcher getMatcher() {
return matcher;
}
public List<ProcessingRegistry> getFactories() {
return factories;
}
@Override
public List<DataLink> getDataLinks() {
if(links == null){
links = new NotifiedCheckedList<DataLink>(DataLink.class) {
@Override
protected void notifyAdd(DataLink item, int index) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", item);
fireDataLinkChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_ADDED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyAdd(Collection<? extends DataLink> items, NumberRange<Integer> range) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", items);
for(Object lk : items){
ArgumentChecks.ensureNonNull("item", lk);
}
fireDataLinkChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_ADDED, range, null));
}
@Override
protected void notifyRemove(DataLink item, int index) {
fireDataLinkChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_REMOVED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyRemove(Collection<? extends DataLink> items, NumberRange<Integer> range) {
fireDataLinkChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_REMOVED, range, null));
}
@Override
protected void notifyChange(DataLink oldItem, DataLink newItem, int index) {
}
};
}
return links;
}
@Override
public List<FlowLink> getFlowLinks() {
if (executionLinks == null){
executionLinks = new NotifiedCheckedList<FlowLink>(FlowLink.class) {
@Override
protected void notifyAdd(FlowLink item, int index) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", item);
fireFlowLinkChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_ADDED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyAdd(Collection<? extends FlowLink> items, NumberRange<Integer> range) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", items);
for(Object lk : items){
ArgumentChecks.ensureNonNull("item", lk);
}
fireFlowLinkChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_ADDED, range, null));
}
@Override
protected void notifyRemove(FlowLink item, int index) {
fireFlowLinkChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_REMOVED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyRemove(Collection<? extends FlowLink> items, NumberRange<Integer> range) {
fireFlowLinkChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_REMOVED, range, null));
}
@Override
protected void notifyChange(FlowLink oldItem, FlowLink newItem, int index) {
}
};
}
return executionLinks;
}
@Override
public List<Element> getElements() {
if(chainElements == null){
chainElements = new NotifiedCheckedList<Element>(Element.class) {
@Override
protected void notifyAdd(Element item, int index) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", item);
fireDescriptorChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_ADDED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyAdd(Collection<? extends Element> items, NumberRange<Integer> range) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", items);
for(Object item : items){
ArgumentChecks.ensureNonNull("item", item);
}
fireDescriptorChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_ADDED, range, null));
}
@Override
protected void notifyRemove(Element item, int index) {
clearObsoleteLinks();
fireDescriptorChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_REMOVED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyRemove(Collection<? extends Element> items, NumberRange<Integer> range) {
clearObsoleteLinks();
fireDescriptorChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_REMOVED, range, null));
}
@Override
protected void notifyChange(Element oldItem, Element newItem, int index) {
}
};
}
return chainElements;
}
@Override
public List<Constant> getConstants() {
if(constants == null){
constants = new NotifiedCheckedList<Constant>(Constant.class) {
@Override
protected void notifyAdd(Constant item, int index) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", item);
fireConstantChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_ADDED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyAdd(Collection<? extends Constant> items, NumberRange<Integer> range) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", items);
for(Object item : items){
ArgumentChecks.ensureNonNull("item", item);
}
fireConstantChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_ADDED, range, null));
}
@Override
protected void notifyRemove(Constant item, int index) {
clearObsoleteLinks();
fireConstantChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_REMOVED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyRemove(Collection<? extends Constant> items, NumberRange<Integer> range) {
clearObsoleteLinks();
fireConstantChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_REMOVED, range, null));
}
@Override
protected void notifyChange(Constant oldItem, Constant newItem, int index) {
}
};
}
return constants;
}
@Override
public List<Parameter> getInputs() {
if(inputs == null){
inputs = new NotifiedCheckedList<Parameter>(Parameter.class) {
@Override
protected void notifyAdd(Parameter item, int index) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", item);
fireInputChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_ADDED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyAdd(Collection<? extends Parameter> items, NumberRange<Integer> range) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", items);
for(Object item : items){
ArgumentChecks.ensureNonNull("item", item);
}
fireInputChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_ADDED, range, null));
}
@Override
protected void notifyRemove(Parameter item, int index) {
clearObsoleteLinks();
fireInputChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_REMOVED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyRemove(Collection<? extends Parameter> items, NumberRange<Integer> range) {
clearObsoleteLinks();
fireInputChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_REMOVED, range, null));
}
@Override
protected void notifyChange(Parameter oldItem, Parameter newItem, int index) {
}
};
}
return inputs;
}
@Override
public List<Parameter> getOutputs() {
if(outputs == null){
outputs = new NotifiedCheckedList<Parameter>(Parameter.class) {
@Override
protected void notifyAdd(Parameter item, int index) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", item);
fireOutputChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_ADDED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyAdd(Collection<? extends Parameter> items, NumberRange<Integer> range) {
//Null values not allowed
ArgumentChecks.ensureNonNull("item", items);
for(Object item : items){
ArgumentChecks.ensureNonNull("item", item);
}
fireOutputChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_ADDED, range, null));
}
@Override
protected void notifyRemove(Parameter item, int index) {
clearObsoleteLinks();
fireOutputChange(new CollectionChangeEvent(
this, item, CollectionChangeEvent.ITEM_REMOVED, NumberRange.create(index, true, index, true), null));
}
@Override
protected void notifyRemove(Collection<? extends Parameter> items, NumberRange<Integer> range) {
clearObsoleteLinks();
fireOutputChange(new CollectionChangeEvent(
this, items, CollectionChangeEvent.ITEM_REMOVED, range, null));
}
@Override
protected void notifyChange(Parameter oldItem, Parameter newItem, int index) {
}
};
}
return outputs;
}
/**
* Not supported on this class, events would be lost.
* @param descriptors
*/
@Override
public void setElements(final List<Element> descriptors) {
throw new UnsupportedOperationException("Replacing chain collections is not permitted.");
}
/**
* Not supported on this class, events would be lost.
* @param constants
*/
@Override
public void setConstants(final List<Constant> constants) {
throw new UnsupportedOperationException("Replacing chain collections is not permitted.");
}
/**
* Not supported on this class, events would be lost.
* @param inputs
*/
@Override
public void setInputs(final List<Parameter> inputs) {
throw new UnsupportedOperationException("Replacing chain collections is not permitted.");
}
/**
* Not supported on this class, events would be lost.
* @param links
*/
@Override
public void setDataLinks(final List<DataLink> links) {
throw new UnsupportedOperationException("Replacing chain collections is not permitted.");
}
/**
* Not supported on this class, events would be lost.
* @param links
*/
@Override
public void setFlowLinks(final List<FlowLink> executionLinks) {
throw new UnsupportedOperationException("Replacing chain collections is not permitted.");
}
/**
* Not supported on this class, events would be lost.
* @param outputs
*/
@Override
public void setOutputs(final List<Parameter> outputs) {
throw new UnsupportedOperationException("Replacing chain collections is not permitted.");
}
/**
* @return integer, next id not used.
*/
public synchronized int getNextId(){
int maxId = 0;
for(Element desc : getElements()){
if(desc.getId() > maxId) {maxId = desc.getId();}
}
for(Constant cst : getConstants()){
if(cst.getId() > maxId) {maxId = cst.getId();}
}
return maxId+1;
}
/**
* Check if given execution link is valid.
* @param link
* @return true only if the link is valid
*/
public boolean isValidFlowLink(final FlowLink link){
if (link.getSourceId() == -1 || link.getTargetId() == -1 || link.getSourceId() == link.getTargetId()
|| link.getTargetId() == Integer.MIN_VALUE || link.getSourceId() == Integer.MAX_VALUE) {
return false;
}
return true;
}
/**
* Check if the given link is valid.
* @return true only if the link is valid
*/
public boolean isValidLink(final DataLink link){
if(link == null) {return false;}
final Object source = link.getSource(this);
final Object target = link.getTarget(this);
//source and target must exist
if(source == null || target == null){
return false;
}
//input can not go on output of same process
if(source == target){
return false;
}
//we can not have more then one link pointing to the same target
final List<DataLink> otherLinks = new ArrayList<DataLink>();
for(DataLink lk : getDataLinks()){
if(lk.getTargetId() == link.getTargetId() && lk.getTargetCode().equals(link.getTargetCode())){
otherLinks.add(lk);
}
}
if (!otherLinks.isEmpty()) {
final Integer newSourceId = link.getSourceId();
final List<FlowLink> execLinks = getFlowLinkFromId(link.getTargetId(), true);
clearFlowLinkList(execLinks, otherLinks);
if (execLinks.isEmpty()) {
return false;
} else {
final Set<Integer> newSourceConnditional = new HashSet<Integer>();
searchForConditionalParent(newSourceId, newSourceConnditional);
if (newSourceConnditional.isEmpty()) {
return false;
} else {
final Set<Integer> otherSourceConditonal = new HashSet<Integer>();
for (FlowLink exec : execLinks) {
if (exec.getSourceId() != newSourceId) {
searchForConditionalParent(exec.getSourceId(), otherSourceConditonal);
}
}
boolean valid = false;
for (Integer id : newSourceConnditional) {
if (otherSourceConditonal.contains(id)) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
}
}
//check types
final Object sourceDataType = getDataType(source, false, link.getSourceCode());
final Object targetDataType = getDataType(target, true, link.getTargetCode());
return matcher.canBeConverted(sourceDataType, targetDataType);
}
private Object getElementFromId(final Integer identifier) {
for (Element chainElement : getElements()) {
if (chainElement.getId().equals(identifier)) {
return chainElement;
}
}
return null;
}
private void clearFlowLinkList(final List<FlowLink> inputExecLinks, final List<DataLink> inputLinks) {
final List<FlowLink> clearedExecLinkList = new ArrayList<FlowLink>();
for (DataLink lk : inputLinks) {
for (FlowLink executionLink : inputExecLinks) {
if (executionLink.getSourceId() == lk.getSourceId()) {
clearedExecLinkList.add(executionLink);
break;
}
}
}
inputLinks.removeAll(clearedExecLinkList);
}
private List<FlowLink> getFlowLinkFromId(final Integer identifier, boolean source) {
final List<FlowLink> links = new ArrayList<FlowLink>();
for (FlowLink execLink : getFlowLinks()) {
if (source) {
if (execLink.getTargetId() == identifier.intValue()) {
links.add(execLink);
}
} else {
if (execLink.getSourceId() == identifier.intValue()) {
links.add(execLink);
}
}
}
return links;
}
private void searchForConditionalParent(final Integer startId, final Set<Integer> conditionalIdentifiers) {
final List<FlowLink> execLinks = getFlowLinkFromId(startId, true);
for (FlowLink exec : execLinks) {
final Object sourceElement = getElementFromId(exec.getSourceId());
if (sourceElement instanceof ElementProcess){
final ElementProcess chainElement = (ElementProcess) sourceElement;
if (!chainElement.getCode().equals("begin")) {
searchForConditionalParent(chainElement.getId(), conditionalIdentifiers);
}
}
}
}
/**
* Return the Java class of a parameter or constant.
*
* @param obj Could be a Parameter, Constant or ChainElement
* @param in in case of ChainElement obj, search code in input or output parameters. True to search in inputs, false for outputs.
* @param code of the parameter in case of ChainElement obj.
* @return Class or ClassFull for Parameter.
*/
private Object getDataType(final Object obj, final boolean in, final String code){
if(obj instanceof Parameter){
final Parameter dto = (Parameter) obj;
return dto.getType();
}else if(obj instanceof Constant){
final Constant dto = (Constant) obj;
return dto.getType();
}else if(obj instanceof ElementProcess){
final ElementProcess dto = (ElementProcess) obj;
try {
final ProcessDescriptor pd = ProcessFinder.getProcessDescriptor(
getFactories().iterator(), dto.getAuthority(), dto.getCode());
final GeneralParameterDescriptor gd = (in) ?
pd.getInputDescriptor().descriptor(code) :
pd.getOutputDescriptor().descriptor(code);
if(gd instanceof ExtendedParameterDescriptor){
Object type = ((ExtendedParameterDescriptor)gd).getUserObject().get(ChainProcessDescriptor.KEY_DISTANT_CLASS);
if(type == null){
//rely on base type
type = ((ExtendedParameterDescriptor)gd).getValueClass();
}
return type;
}else if(gd instanceof ParameterDescriptor){
final Object type = ((ParameterDescriptor)gd).getValueClass();
return type;
}
} catch (NoSuchIdentifierException ex) {
return null;
}
}
return null;
}
/**
* Search in links list the link with element identifier and parameter code in source or target.
* @param elementId identifier of the widget element.
* @param code of the parameter
* @param source if true, search elementId in source, and in target if false.
* @return a list of Link or empty list if not found.
*/
public List<DataLink> findDataLink(final int elementId, final String code, final boolean source) {
final List<DataLink> foundLinks = new ArrayList<DataLink>();
if (links != null) {
for (final DataLink link : links) {
if (source && link.getSourceId() == elementId ) {
if (link.getSource(this) instanceof Constant) {
foundLinks.add(link);
} else if (link.getSourceCode().equals(code)) {
foundLinks.add(link);
}
} else if (!source && link.getTargetId() == elementId) {
if (link.getTarget(this) instanceof Constant) {
foundLinks.add(link);
} else if (link.getTargetCode().equals(code)) {
foundLinks.add(link);
}
}
}
}
return foundLinks;
}
public void addListener(final ChainListener listener){
listeners.add(ChainListener.class, listener);
}
public void removeListener(final ChainListener listener){
listeners.remove(ChainListener.class, listener);
}
private void clearObsoleteLinks(){
final List<DataLink> lst = new ArrayList<DataLink>(getDataLinks());
for(final DataLink link : lst){
//remove link if source or target doesn't exist anymore
if(link.getSource(this) == null){
getDataLinks().remove(link);
}else if(link.getTarget(this) == null){
getDataLinks().remove(link);
}
}
final List<FlowLink> lstEx = new ArrayList<FlowLink>(getFlowLinks());
for(final FlowLink link : lstEx){
//remove link if source or target doesn't exist anymore
if(link.getSource(this) == null){
getFlowLinks().remove(link);
}else if(link.getTarget(this) == null){
getFlowLinks().remove(link);
}
}
}
private void fireConstantChange(final CollectionChangeEvent event){
//can be null for a short time when using copy constructor of Chain
if(listeners != null){
for(ChainListener lst : listeners.getListeners(ChainListener.class)){
lst.constantChange(event);
}
}
}
private void fireDescriptorChange(final CollectionChangeEvent event){
//can be null for a short time when using copy constructor of Chain
if(listeners != null){
for(ChainListener lst : listeners.getListeners(ChainListener.class)){
lst.descriptorChange(event);
}
}
}
private void fireDataLinkChange(final CollectionChangeEvent event){
//can be null for a short time when using copy constructor of Chain
if(listeners != null){
for(ChainListener lst : listeners.getListeners(ChainListener.class)){
lst.linkChange(event);
}
}
}
private void fireFlowLinkChange(final CollectionChangeEvent event){
//can be null for a short time when using copy constructor of Chain
if(listeners != null){
for(ChainListener lst : listeners.getListeners(ChainListener.class)){
lst.executionLinkChange(event);
}
}
}
private void fireInputChange(final CollectionChangeEvent event){
//can be null for a short time when using copy constructor of Chain
if(listeners != null){
for(ChainListener lst : listeners.getListeners(ChainListener.class)){
lst.inputChange(event);
}
}
}
private void fireOutputChange(final CollectionChangeEvent event){
//can be null for a short time when using copy constructor of Chain
if(listeners != null){
for(ChainListener lst : listeners.getListeners(ChainListener.class)){
lst.outputChange(event);
}
}
}
}