/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso 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, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso 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 Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.binding;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jspresso.framework.util.collection.CollectionHelper;
import org.jspresso.framework.util.event.IItemSelectable;
import org.jspresso.framework.util.event.IItemSelectionListener;
import org.jspresso.framework.util.event.ISelectionChangeListener;
import org.jspresso.framework.util.event.ItemSelectionEvent;
import org.jspresso.framework.util.event.SelectionChangeEvent;
import org.jspresso.framework.util.event.SelectionChangeSupport;
import org.jspresso.framework.util.event.ValueChangeEvent;
/**
* This class is the base class of all default collection connectors. It
* implements the dynamic management of the child connectors which represent the
* collection.
*
* @author Vincent Vandenschrick
*/
public abstract class AbstractCollectionConnector extends
AbstractCompositeValueConnector implements ICollectionConnector,
IItemSelectable {
private final ICompositeValueConnector childConnectorPrototype;
private final IMvcBinder mvcBinder;
private List<IValueConnector> removedChildrenConnectors;
private SelectionChangeSupport selectionChangeSupport;
private List<IValueConnector> connectorTank;
/**
* Creates a new {@code AbstractCollectionConnector}.
*
* @param id
* the connector id.
* @param binder
* the {@code IMvcBinder} used to bind dynamically created
* child connectors.
* @param childConnectorPrototype
* the connector prototype used to create new instances of child
* connectors.
*/
public AbstractCollectionConnector(String id, IMvcBinder binder,
ICompositeValueConnector childConnectorPrototype) {
super(id);
this.mvcBinder = binder;
this.childConnectorPrototype = childConnectorPrototype;
}
/**
* {@inheritDoc}
*/
@Override
public void addItemSelectionListener(IItemSelectionListener listener) {
implAddConnectorSelectionListener(listener);
}
/**
* {@inheritDoc}
*/
@Override
public void addSelectionChangeListener(ISelectionChangeListener listener) {
if (selectionChangeSupport == null) {
selectionChangeSupport = new SelectionChangeSupport(this);
}
selectionChangeSupport.addSelectionChangeListener(listener);
}
/**
* {@inheritDoc}
*/
@Override
public boolean areChildrenReadable() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean areChildrenWritable() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public AbstractCollectionConnector clone() {
return clone(getId());
}
/**
* {@inheritDoc}
*/
@Override
public AbstractCollectionConnector clone(String newConnectorId) {
AbstractCollectionConnector clonedConnector = (AbstractCollectionConnector) super
.clone(newConnectorId);
clonedConnector.selectionChangeSupport = null;
clonedConnector.removedChildrenConnectors = null;
clonedConnector.connectorTank = null;
return clonedConnector;
}
/**
* creates a new connector cloning the connector prototype.
* <p>
* {@inheritDoc}
*/
@Override
public IValueConnector createChildConnector(String newConnectorId) {
if (connectorTank != null && !connectorTank.isEmpty()) {
return connectorTank.remove(0);
}
return childConnectorPrototype.clone(newConnectorId);
}
/**
* {@inheritDoc}
*/
@Override
public void fireSelectedItemChange(ItemSelectionEvent evt) {
implFireSelectedItemChange(evt);
}
/**
* {@inheritDoc}
*/
@Override
public IValueConnector getChildConnector(int index) {
return getChildConnector(computeStorageKey(index));
}
/**
* Gets the childConnectorPrototype.
*
* @return the childConnectorPrototype.
*/
public ICompositeValueConnector getChildConnectorPrototype() {
return childConnectorPrototype;
}
/**
* Returns this.
* <p>
* {@inheritDoc}
*/
@Override
public ICollectionConnector getCollectionConnector() {
return this;
}
/**
* Returns singleton list of this.
* <p>
* {@inheritDoc}
*/
@Override
public List<ICollectionConnector> getCollectionConnectors() {
return Collections.singletonList((ICollectionConnector) this);
}
/**
* {@inheritDoc}
*/
@Override
public int[] getSelectedIndices() {
if (selectionChangeSupport == null) {
return new int[0];
}
return selectionChangeSupport.getSelectedIndices();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T getSelectedItem() {
return (T) implGetSelectedItem();
}
/**
* {@inheritDoc}
*/
@Override
public void removeItemSelectionListener(IItemSelectionListener listener) {
implRemoveConnectorSelectionListener(listener);
}
/**
* {@inheritDoc}
*/
@Override
public void removeSelectionChangeListener(ISelectionChangeListener listener) {
if (selectionChangeSupport != null) {
selectionChangeSupport.removeSelectionChangeListener(listener);
}
}
/**
* {@inheritDoc}
*/
@Override
public void selectionChange(SelectionChangeEvent evt) {
boolean sourceIsScl = evt.getSource() instanceof ISelectionChangeListener;
if (sourceIsScl && selectionChangeSupport == null) {
selectionChangeSupport = new SelectionChangeSupport(this);
}
if (sourceIsScl) {
selectionChangeSupport
.addInhibitedListener((ISelectionChangeListener) evt.getSource());
}
try {
setSelectedIndices(evt.getNewSelection(), evt.getLeadingIndex());
} finally {
if (sourceIsScl) {
selectionChangeSupport
.removeInhibitedListener((ISelectionChangeListener) evt.getSource());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void setSelectedIndices(int... newSelectedIndices) {
int leadingIndex = -1;
if (newSelectedIndices != null && newSelectedIndices.length > 0) {
leadingIndex = newSelectedIndices[newSelectedIndices.length - 1];
}
setSelectedIndices(newSelectedIndices, leadingIndex);
}
/**
* {@inheritDoc}
*/
@Override
public void setSelectedIndices(int[] selectedIndices, int leadingIndex) {
int[] newSelectedIndices = selectedIndices;
if (newSelectedIndices != null && newSelectedIndices.length == 0) {
newSelectedIndices = null;
}
int[] oldSelectedIndices = getSelectedIndices();
if (oldSelectedIndices != null && oldSelectedIndices.length == 0) {
oldSelectedIndices = null;
}
if (selectionChangeSupport == null) {
selectionChangeSupport = new SelectionChangeSupport(this);
}
selectionChangeSupport.setSelectedIndices(newSelectedIndices, leadingIndex);
if ((oldSelectedIndices == null && newSelectedIndices != null)
|| (oldSelectedIndices != null && newSelectedIndices == null)
|| (oldSelectedIndices != null && !Arrays.equals(oldSelectedIndices, newSelectedIndices))) {
if (newSelectedIndices == null) {
implFireSelectedConnectorChange(null);
} else {
implFireSelectedConnectorChange(getChildConnector(leadingIndex));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void setTracksChildrenSelection(boolean tracksChildrenSelection) {
implSetTracksChildrenSelection(tracksChildrenSelection);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return getId();
}
/**
* Dynamically adapts collection of child connectors (child connectors are
* added or removed depending on the state of the source connector of the
* event) before calling super implementation.
* <p>
* {@inheritDoc}
*/
@Override
public void valueChange(ValueChangeEvent evt) {
updateChildConnectors();
super.valueChange(evt);
}
/**
* Takes a snapshot of the collection (does not keep the reference itself).
* <p>
* {@inheritDoc}
*/
@Override
protected Object computeOldConnectorValue(Object connectorValue) {
return CollectionHelper.cloneCollection((Collection<?>) connectorValue);
}
/**
* Overrides the default to produce
* {@code CollectionConnectorValueChangeEvent}s.
* <p>
* {@inheritDoc}
*/
@Override
protected ValueChangeEvent createChangeEvent(Object oldConnectorValue,
Object newConnectorValue) {
CollectionConnectorValueChangeEvent changeEvent = new CollectionConnectorValueChangeEvent(
this, oldConnectorValue, newConnectorValue, removedChildrenConnectors);
removedChildrenConnectors = null;
return changeEvent;
}
private void cleanupConnector(IValueConnector removedConnector) {
removedConnector.recycle(mvcBinder);
if (connectorTank == null) {
connectorTank = new ArrayList<>();
}
connectorTank.add(removedConnector);
}
/**
* Updates the child connectors based on a new model collection.
*/
protected void updateChildConnectors() {
ICollectionConnector modelConnector = (ICollectionConnector) getModelConnector();
Map<IValueConnector, List<IValueConnector>> existingConnectorsByModel = new HashMap<>();
for (String connectorKey : new ArrayList<>(getChildConnectorKeys())) {
IValueConnector childConnector = getChildConnector(connectorKey);
List<IValueConnector> existingConnectors = existingConnectorsByModel
.get(childConnector.getModelConnector());
if (existingConnectors == null) {
existingConnectors = new ArrayList<>();
existingConnectorsByModel.put(childConnector.getModelConnector(),
existingConnectors);
}
existingConnectors.add(childConnector);
removeChildConnector(connectorKey);
}
if (modelConnector != null && modelConnector.getChildConnectorCount() > 0) {
for (int i = 0; i < modelConnector.getChildConnectorCount(); i++) {
IValueConnector connector;
IValueConnector nextModelConnector = modelConnector
.getChildConnector(i);
List<IValueConnector> existingConnectors = existingConnectorsByModel
.get(nextModelConnector);
if (existingConnectors != null && !existingConnectors.isEmpty()) {
connector = existingConnectors.remove(0);
} else {
connector = createChildConnector(getId() + "Element");
try {
((AbstractValueConnector) connector).setMute(true);
mvcBinder.bind(connector, nextModelConnector);
} finally {
((AbstractValueConnector) connector).setMute(false);
}
}
addChildConnector(computeStorageKey(i), connector);
if (removedChildrenConnectors != null) {
removedChildrenConnectors.remove(connector);
}
}
}
if (removedChildrenConnectors == null) {
removedChildrenConnectors = new ArrayList<>();
}
for (List<IValueConnector> obsoleteConnectors : existingConnectorsByModel
.values()) {
for (IValueConnector obsoleteConnector : obsoleteConnectors) {
cleanupConnector(obsoleteConnector);
removedChildrenConnectors.add(obsoleteConnector);
}
}
}
private String computeStorageKey(int i) {
return CollectionConnectorHelper.computeStorageKey(getId(), i);
}
}