/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket.model; import org.apache.wicket.util.lang.Args; import org.danekja.java.util.function.serializable.SerializableBiFunction; import org.danekja.java.util.function.serializable.SerializableFunction; import org.danekja.java.util.function.serializable.SerializablePredicate; import org.danekja.java.util.function.serializable.SerializableSupplier; /** * A IModel wraps the actual model Object used by a Component. IModel implementations are used as a * facade for the real model so that users have control over the actual persistence strategy. Note * that objects implementing this interface will be stored in the Session. Hence, you should use * (non-transient) instance variables sparingly. * <ul> * <li><b>Basic Models </b>- To implement a basic (non-detachable) model which holds its entire * state in the Session, you can use the simple model wrapper {@link Model}. * * <li><b>Detachable Models </b>- IModel inherits a hook, {@link IDetachable#detach()}, so that * interface implementers can detach transient information when a model is no longer being actively * used by the framework. This reduces memory use and reduces the expense of replicating the model * in a clustered server environment. To implement a detachable model, you should generally extend * {@link LoadableDetachableModel} instead of implementing IModel directly. * * <li><b>Property Models </b>- The AbstractPropertyModel class provides default functionality for * property models. A property model provides access to a particular property of its wrapped model. * * <li><b>Compound Property Models </b>- The IModel interface is parameterized by Component, * allowing a model to be shared among several Components. When the {@link IModel#getObject()}method * is called, the value returned will depend on the Component which is asking for the value. * Likewise, the {@link IModel#setObject(Object)}method sets a different property depending on which * Component is doing the setting. For more information on CompoundPropertyModels and model * inheritance, see {@link org.apache.wicket.model.CompoundPropertyModel}and * {@link org.apache.wicket.Page}. * </ul> * * @see org.apache.wicket.Component#sameInnermostModel(org.apache.wicket.Component) * @see org.apache.wicket.Component#sameInnermostModel(IModel) * * @author Chris Turner * @author Eelco Hillenius * @author Jonathan Locke * * @param <T> * Model object. */ @FunctionalInterface public interface IModel<T> extends IDetachable { /** * Gets the model object. * * @return The model object */ T getObject(); /** * Sets the model object. * * @param object * The model object * @throws UnsupportedOperationException * unless overridden */ default void setObject(final T object) { throw new UnsupportedOperationException( "Override this method to support setObject(Object)"); } @Override default void detach() { } /** * Returns a IModel checking whether the predicate holds for the contained object, if it is not * {@code null}. If the predicate doesn't evaluate to {@code true}, the contained object will be {@code null}. * * @param predicate * a predicate to be used for testing the contained object * @return a new IModel */ default IModel<T> filter(SerializablePredicate<? super T> predicate) { Args.notNull(predicate, "predicate"); return (IModel<T>)() -> { T object = IModel.this.getObject(); if (object != null && predicate.test(object)) { return object; } else { return null; } }; } /** * Returns a IModel applying the given mapper to the contained object, if it is not {@code null}. * * @param <R> * the new type of the contained object * @param mapper * a mapper, to be applied to the contained object * @return a new IModel */ default <R> IModel<R> map(SerializableFunction<? super T, R> mapper) { Args.notNull(mapper, "mapper"); return (IModel<R>)() -> { T object = IModel.this.getObject(); if (object == null) { return null; } else { return mapper.apply(object); } }; } /** * Returns a {@link IModel} applying the given combining function to the current model object and * to the one from the other model, if they are not {@code null}. * * @param <R> * the resulting type * @param <U> * the other models type * @param other * another model to be combined with this one * @param combiner * a function combining this and the others object to a result. * @return a new IModel */ default <R, U> IModel<R> combineWith(IModel<U> other, SerializableBiFunction<? super T, ? super U, R> combiner) { Args.notNull(combiner, "combiner"); Args.notNull(other, "other"); return (IModel<R>)() -> { T t = IModel.this.getObject(); U u = other.getObject(); if (t != null && u != null) { return combiner.apply(t, u); } else { return null; } }; } /** * Returns a IModel applying the given IModel-bearing mapper to the contained object, if it is not {@code null}. * * @param <R> * the new type of the contained object * @param mapper * a mapper, to be applied to the contained object * @return a new IModel */ default <R> IModel<R> flatMap(SerializableFunction<? super T, IModel<R>> mapper) { Args.notNull(mapper, "mapper"); return new IModel<R>() { private static final long serialVersionUID = 1L; @Override public R getObject() { T object = IModel.this.getObject(); if (object != null) { IModel<R> model = mapper.apply(object); if (model != null) { return model.getObject(); } else { return null; } } else { return null; } } @Override public void setObject(R object) { T modelObject = IModel.this.getObject(); if (modelObject != null) { IModel<R> model = mapper.apply(modelObject); if (model != null) { model.setObject(object); } } } @Override public void detach() { T object = IModel.this.getObject(); if (object != null) { IModel<R> model = mapper.apply(object); if (model != null) { model.detach(); } } } }; } /** * Returns a IModel, returning either the contained object or the given default value, depending * on the {@code null}ness of the contained object. * * @param other * a default value * @return a new IModel */ default IModel<T> orElse(T other) { return (IModel<T>)() -> { T object = IModel.this.getObject(); if (object == null) { return other; } else { return object; } }; } /** * Returns a IModel, returning either the contained object or invoking the given supplier to get * a default value. * * @param other * a supplier to be used as a default * @return a new IModel */ default IModel<T> orElseGet(SerializableSupplier<? extends T> other) { Args.notNull(other, "other"); return (IModel<T>)() -> { T object = IModel.this.getObject(); if (object == null) { return other.get(); } else { return object; } }; } /** * Suppresses generics warning when casting model types. * * @param <T> * @param model * @return cast <code>model</code> */ @SuppressWarnings("unchecked") static <T> IModel<T> of(IModel<?> model) { return (IModel<T>)model; } }