package fr.openwide.core.wicket.more.model; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import fr.openwide.core.wicket.more.markup.repeater.collection.ICollectionModel; import fr.openwide.core.wicket.more.markup.repeater.collection.IItemModelAwareCollectionModel; import fr.openwide.core.wicket.more.util.model.Detachables; import fr.openwide.core.wicket.more.util.model.Models; /** * An abstract base for implementations of {@link ICollectionModel} whose content is to be "cloned" (i.e. copied to a new * collection) each time {@link #setObject(Collection)} is called. * * <p>This is typically what you want when editing a collection in a form. * * <p>Content is stored as a list of element models. Thus, subclasses of this one are guaranteed to always return the * same model for a given element, up to this element's removal from the collection. * * <p><strong>WARNING:</strong> this model is only intended to contain small collections. It is absolutely not optimized * for large collections (say, more than just one or two dozens of items). Performance issues may arise when dealing * with large collections. */ abstract class AbstractCollectionCopyModel<T, C extends Collection<T>, M extends IModel<T>> extends LoadableDetachableModel<C> implements IItemModelAwareCollectionModel<T, C, M> { private static final long serialVersionUID = -1768835911782930879L; private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCollectionCopyModel.class); private final List<M> modelList = Lists.newArrayList(); public AbstractCollectionCopyModel() { super(); } @Override public void detach() { if (!isAttached()) { /* * Make sure the models are given a chance to process post-detach changes on their object. * Useful in particular for GenericEntityModel.detach() */ Detachables.detach(modelList); return; } else { super.detach(); } } @Override protected void onDetach() { updateModelsIfExternalChangeIsPossible(); Detachables.detach(modelList); } protected final void updateModelsIfExternalChangeIsPossible() { if (isAttached()) { updateModels(); } } private void updateModels() { // Save the previous models for re-use based on their current content Map<T, M> oldModels = Maps.newHashMap(); int index = 0; for (M model : modelList) { T value = model.getObject(); Object previousModelForThisValue = oldModels.put(model.getObject(), model); if (previousModelForThisValue != null) { LOGGER.warn( "Detected multiple models for the same value {} at index {} in {}. One value might have been" + " lost while updating models." + " If you need a model supporting multiple identical items, use an index-based implementation", value, index, model ); } ++index; } // Build the model list based on the current object modelList.clear(); C currentObject = getObject(); for (T item : currentObject) { M itemModel = oldModels.get(item); if (itemModel == null) { // No existing model, create a new one itemModel = createModel(item); } modelList.add(itemModel); } } /** * WARNING: if the client calls <code>setObject(null)</code>, a subsequent call to <code>getObject()</code> * will not return <code>null</code>, but <em>an empty collection</em>. */ @Override public void setObject(C object) { C collection = createCollection(); if (object != null) { collection.addAll(object); } super.setObject(collection); } @Override protected final C load() { C collection = createCollection(); for (IModel<? extends T> itemModel : modelList) { collection.add(itemModel.getObject()); } return collection; } @Override public Iterator<M> iterator() { updateModelsIfExternalChangeIsPossible(); return Models.collectionModelIterator(modelList); } @Override public final Iterator<M> iterator(long offset, long limit) { updateModelsIfExternalChangeIsPossible(); return Models.collectionModelIterator(modelList, offset, limit); } @Override public final long size() { updateModelsIfExternalChangeIsPossible(); return modelList.size(); } @Override public final void add(T item) { /* * We must add to an instance of the underlying collection, not to the model list, so as to respect the * properties of this collection (make the collection itself pick the element to be replaced, for instance). */ C collection = getObject(); collection.add(item); updateModelsIfExternalChangeIsPossible(); } @Override public final void remove(T item) { /* * We must remove from an instance of the underlying collection, not from the model list, so as to respect the * properties of this collection (make the collection itself pick the element to be removed). */ C collection = getObject(); collection.remove(item); updateModelsIfExternalChangeIsPossible(); } @Override public void clear() { super.setObject(createCollection()); // Remove the cached collection from LoadableDetachableModel modelList.clear(); detach(); } protected abstract C createCollection(); protected abstract M createModel(T item); }