package fr.openwide.core.wicket.more.util.model; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; import java.util.Map; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.wicket.Component; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IComponentAssignedModel; import org.apache.wicket.model.IDetachable; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.primitives.Ints; import fr.openwide.core.commons.util.collections.Iterators2; 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.markup.repeater.map.IMapModel; public final class Models { private Models() { } @SuppressWarnings("unchecked") // SerializableModelFactory works for any T extending Serializable public static <T extends Serializable> Function<T, Model<T>> serializableModelFactory() { return (Function<T, Model<T>>) (Object) SerializableModelFactory.INSTANCE; } @SuppressWarnings({"rawtypes", "unchecked"}) // SerializableModelFactory works for any T extending Serializable private enum SerializableModelFactory implements Function<Serializable, Model> { INSTANCE; @Override public Model apply(Serializable input) { return new Model(input); } } @SuppressWarnings("unchecked") // ModelGetObjectFunction works for any T public static <T> Function<? super IModel<? extends T>, T> getObject() { return (Function<? super IModel<? extends T>, T>) ModelGetObjectFunction.INSTANCE; } @SuppressWarnings("rawtypes") // ModelGetObjectFunction works for any T private enum ModelGetObjectFunction implements Function<IModel, Object> { INSTANCE; @Override public Object apply(IModel input) { return input == null ? null : input.getObject(); } } /** * A "placeholder" model, for use when the actual behavior of getObject() and setObject() do not matter. * * <p>{@link #getObject()} always returns null and {@link #setObject(Object)} and {@link #detach()} do nothing. */ @SuppressWarnings("unchecked") // Works for any T public static <T> IModel<T> placeholder() { return (IModel<T>) PlaceholderModel.INSTANCE; } private enum PlaceholderModel implements IModel<Object> { INSTANCE; private Object readResolve() { return INSTANCE; } @Override public Object getObject() { return null; } @Override public void setObject(Object object) { // Does nothing } @Override public void detach() { // Does nothing } }; /** * A static model of <code>Map<String, Object></code>. Useful as a data model for {@link StringResourceModel}. */ public static MapModelBuilder<String, Object> dataMap() { return new MapModelBuilder<String, Object>(); } public static class MapModelBuilder<K, V> { private ImmutableMap.Builder<K, IModel<? extends V>> delegate = ImmutableMap.builder(); public MapModelBuilder<K, V> put(K key, V value) { delegate.put(key, transientModel(value)); return this; } public MapModelBuilder<K, V> put(K key, IModel<? extends V> valueModel) { delegate.put(key, valueModel); return this; } public IModel<Map<K, V>> build() { return new MapModelBuilderMap<>(delegate.build()); } private static class MapModelBuilderMap<K, V> extends LoadableDetachableModel<Map<K, V>> { private static final long serialVersionUID = 1L; private Map<K, IModel<? extends V>> source; public MapModelBuilderMap(Map<K, IModel<? extends V>> source) { super(); this.source = source; } @Override protected Map<K, V> load() { return Maps.transformValues(source, Models.<V>getObject()); } @Override protected void onDetach() { super.onDetach(); for (IDetachable detachable : source.values()) { detachable.detach(); } } } } /** * A constant, non-serializable model. * <p>Useful when calling */ public static <T> IModel<T> transientModel(T value) { return new TransientModel<>(value); } private static class TransientModel<T> extends AbstractReadOnlyModel<T> { private static final long serialVersionUID = -2160512073899616819L; private final T value; public TransientModel(T value) { super(); this.value = value; } @Override public T getObject() { return value; } } /** * @param model The model to be wrapped. * @param component The component to be used as a wrapping parameter. * @return The wrapped model, or the original model if it does not implement {@link IComponentAssignedModel}. */ public static <T> IModel<T> wrap(IModel<T> model, Component component) { if (model instanceof IComponentAssignedModel) { return ((IComponentAssignedModel<T>)model).wrapOnAssignment(component); } else { return model; } } public static <T, C extends Collection<T>, M extends IModel<T>> IItemModelAwareCollectionModel<T, C, M> filterByModel( IItemModelAwareCollectionModel<T, ? extends Collection<T>, M> unfiltered, Predicate<M> modelPredicate, Supplier<? extends C> collectionSupplier) { return new FilterByModelItemModelAwareCollectionModel<T, C, M>( unfiltered, modelPredicate, collectionSupplier ); } /** * A utility method that provides a sensible default implementation of {@link IItemModelAwareCollectionModel#iterator()}. * <p>In particular, this implementation defers the call to {@code iterator} on the underlying {@link Iterable}. * This is necessary because of how RefreshingView works: it first gets the iterator and *then* detaches its items, * which may indirectly detach the {@link ICollectionModel} and thus trigger changes to the * underlying {@link Iterable}. * @param iterable The model iterable. * @return The model iterator. */ public static <T> Iterator<T> collectionModelIterator(Iterable<T> iterable) { Iterator<T> iterator = Iterators2.deferred(iterable); return Iterators.unmodifiableIterator(iterator); } /** * A utility method that provides a sensible default implementation of {@link ICollectionModel#iterator(long, long)}. * <p>In particular, this implementation defers the call to {@code iterator} on the underlying {@link Iterable}. * This is necessary because of how RefreshingView works: it first gets the iterator and *then* detaches its items, * which may indirectly detach the {@link ICollectionModel} and thus trigger changes to the * underlying {@link Iterable}. * @param iterable The model iterable. * @param offset The offset the returned iterator should start from. * @param limit The maximum number of items the returned iterator should provide. * @return The model iterator. */ public static <T> Iterator<T> collectionModelIterator(Iterable<T> iterable, long offset, long limit) { Iterable<T> offsetedIterable = Iterables.skip( iterable, Ints.saturatedCast(offset) ); return Iterators.limit(collectionModelIterator(offsetedIterable), Ints.saturatedCast(limit)); } /** * A utility method that provides a sensible default implementation for {@link IMapModel#valueModel(IModel)}. */ public static <K, V> IModel<V> mapModelValueModel(final IMapModel<K, V, ?> mapModel, final IModel<? extends K> keyModel) { return new MapModelValueModel<>(mapModel, keyModel); } private static final class MapModelValueModel<K,V> implements IModel<V> { private static final long serialVersionUID = 1L; private final IMapModel<K, V, ?> mapModel; private final IModel<? extends K> keyModel; public MapModelValueModel(IMapModel<K, V, ?> mapModel, IModel<? extends K> keyModel) { this.mapModel = mapModel; this.keyModel = keyModel; } @Override public void detach() { mapModel.detach(); keyModel.detach(); } @Override public V getObject() { Map<K, V> map = mapModel.getObject(); return map == null ? null : map.get(keyModel.getObject()); } @Override public void setObject(V object) { mapModel.put(keyModel.getObject(), object); } @Override public boolean equals(Object obj) { if (!(obj instanceof MapModelValueModel<?,?>)) { return false; } MapModelValueModel<?,?> other = (MapModelValueModel<?,?>) obj; return new EqualsBuilder() .append(mapModel, other.mapModel) .append(keyModel, other.keyModel) .build(); } @Override public int hashCode() { return new HashCodeBuilder() .append(mapModel) .append(keyModel) .build(); } } }