001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.model;
018
019
020import org.apache.wicket.util.lang.Args;
021import org.danekja.java.util.function.serializable.SerializableBiConsumer;
022import org.danekja.java.util.function.serializable.SerializableBiFunction;
023import org.danekja.java.util.function.serializable.SerializableFunction;
024import org.danekja.java.util.function.serializable.SerializablePredicate;
025import org.danekja.java.util.function.serializable.SerializableSupplier;
026
027/**
028 * A IModel wraps the actual model Object used by a Component. IModel implementations are used as a
029 * facade for the real model so that users have control over the actual persistence strategy. Note
030 * that objects implementing this interface will be stored in the Session. Hence, you should use
031 * (non-transient) instance variables sparingly.
032 * <ul>
033 * <li><b>Basic Models </b>- To implement a basic (non-detachable) model which holds its entire
034 * state in the Session, you can use the simple model wrapper {@link Model}.
035 * 
036 * <li><b>Detachable Models </b>- IModel inherits a hook, {@link IDetachable#detach()}, so that
037 * interface implementers can detach transient information when a model is no longer being actively
038 * used by the framework. This reduces memory use and reduces the expense of replicating the model
039 * in a clustered server environment. To implement a detachable model, you should generally extend
040 * {@link LoadableDetachableModel} instead of implementing IModel directly.
041 * 
042 * <li><b>Property Models </b>- The AbstractPropertyModel class provides default functionality for
043 * property models. A property model provides access to a particular property of its wrapped model.
044 * 
045 * <li><b>Compound Property Models </b>- The IModel interface is parameterized by Component,
046 * allowing a model to be shared among several Components. When the {@link IModel#getObject()}method
047 * is called, the value returned will depend on the Component which is asking for the value.
048 * Likewise, the {@link IModel#setObject(Object)}method sets a different property depending on which
049 * Component is doing the setting. For more information on CompoundPropertyModels and model
050 * inheritance, see {@link org.apache.wicket.model.CompoundPropertyModel}and
051 * {@link org.apache.wicket.Page}.
052 * </ul>
053 * 
054 * @see org.apache.wicket.Component#sameInnermostModel(org.apache.wicket.Component)
055 * @see org.apache.wicket.Component#sameInnermostModel(IModel)
056 * 
057 * @author Chris Turner
058 * @author Eelco Hillenius
059 * @author Jonathan Locke
060 * 
061 * @param <T>
062 *            Model object.
063 */
064@FunctionalInterface
065public interface IModel<T> extends IDetachable
066{
067        /**
068         * Gets the model object.
069         * 
070         * @return The model object
071         */
072        T getObject();
073
074        /**
075         * Sets the model object.
076         *
077         * @param object
078         *            The model object
079         * @throws UnsupportedOperationException
080         *             unless overridden
081         */
082        default void setObject(final T object)
083        {
084                throw new UnsupportedOperationException(
085                        "Override this method to support setObject(Object)");
086        }
087
088        @Override
089        default void detach()
090        {
091        }
092
093        /**
094         * Returns a IModel checking whether the predicate holds for the contained object, if it is not
095         * {@code null}. If the predicate doesn't evaluate to {@code true}, the contained object will be {@code null}.
096         *
097         * @param predicate
098         *            a predicate to be used for testing the contained object
099         * @return a new IModel
100         */
101        default IModel<T> filter(SerializablePredicate<? super T> predicate)
102        {
103                Args.notNull(predicate, "predicate");
104                return new IModel<T>()
105                {
106                        @Override
107                        public T getObject()
108                        {
109                                T object = IModel.this.getObject();
110                                if (object != null && predicate.test(object))
111                                {
112                                        return object;
113                                }
114                                else
115                                {
116                                        return null;
117                                }
118                        }
119
120                        @Override
121                        public void detach()
122                        {
123                                IModel.this.detach();
124                        }
125                };
126        }
127
128        /**
129         * Returns a IModel applying the given mapper to the contained object, if it is not {@code null}.
130         *
131         * @param <R>
132         *            the new type of the contained object
133         * @param mapper
134         *            a mapper, to be applied to the contained object
135         * @return a new IModel
136         */
137        default <R> IModel<R> map(SerializableFunction<? super T, R> mapper)
138        {
139                Args.notNull(mapper, "mapper");
140                return new IModel<R>() {
141                        @Override
142                        public R getObject()
143                        {
144                                T object = IModel.this.getObject();
145                                if (object == null)
146                                {
147                                        return null;
148                                } else
149                                {
150                                        return mapper.apply(object);
151                                }
152                        }
153
154                        @Override
155                        public void detach()
156                        {
157                                IModel.this.detach();
158                        }
159                };
160        }
161
162        /**
163         * Returns a {@link IModel} applying the given combining function to the current model object and
164         * to the one from the other model, if they are not {@code null}.
165         *
166         * @param <R>
167         *            the resulting type
168         * @param <U>
169         *            the other models type
170         * @param other
171         *            another model to be combined with this one
172         * @param combiner
173         *            a function combining this and the others object to a result.
174         * @return a new IModel
175         */
176        default <R, U> IModel<R> combineWith(IModel<U> other,
177                SerializableBiFunction<? super T, ? super U, R> combiner)
178        {
179                Args.notNull(combiner, "combiner");
180                Args.notNull(other, "other");
181                return new IModel<R>() {
182                        @Override
183                        public R getObject()
184                        {
185                                T t = IModel.this.getObject();
186                                U u = other.getObject();
187                                if (t != null && u != null)
188                                {
189                                        return combiner.apply(t, u);
190                                } else
191                                {
192                                        return null;
193                                }
194                        }
195
196                        @Override
197                        public void detach()
198                        {
199                                other.detach();
200                                IModel.this.detach();
201                        }
202                };
203        }
204
205        /**
206         * Returns a IModel applying the given IModel-bearing mapper to the contained object, if it is not {@code null}.
207         *
208         * @param <R>
209         *            the new type of the contained object
210         * @param mapper
211         *            a mapper, to be applied to the contained object
212         * @return a new IModel
213         * @see LambdaModel#of(IModel, SerializableFunction, SerializableBiConsumer)
214         */
215        default <R> IModel<R> flatMap(SerializableFunction<? super T, IModel<R>> mapper)
216        {
217                Args.notNull(mapper, "mapper");
218                return new IModel<R>()
219                {
220                        private static final long serialVersionUID = 1L;
221
222                        @Override
223                        public R getObject()
224                        {
225                                T object = IModel.this.getObject();
226                                if (object != null)
227                                {
228                                        IModel<R> model = mapper.apply(object);
229                                        if (model != null) 
230                                        {
231                                                return model.getObject();
232                                        }
233                                        else 
234                                        {
235                                                return null;
236                                        }
237                                }
238                                else
239                                {
240                                        return null;
241                                }
242                        }
243
244                        @Override
245                        public void setObject(R object)
246                        {
247                                T modelObject = IModel.this.getObject();
248                                if (modelObject != null)
249                                {
250                                        IModel<R> model = mapper.apply(modelObject);
251                                        if (model != null)
252                                        {
253                                                model.setObject(object);
254                                        }
255                                }
256                        }
257
258                        @Override
259                        public void detach()
260                        {
261                                T object = IModel.this.getObject();
262                                IModel.this.detach();
263                                if (object != null)
264                                {
265                                        IModel<R> model = mapper.apply(object);
266                                        if (model != null)
267                                        {
268                                                model.detach();
269                                        }
270                                }
271                        }
272                };
273        }
274
275        /**
276         * Returns a IModel, returning either the contained object or the given default value, depending
277         * on the {@code null}ness of the contained object.
278         *
279         * <p>
280         * Possible usages:
281         * <ul>
282         *     <li>{@code myComponent = new AnyComponent(&quot;someId&quot;, someModel.orElse(defaultValue));}
283         *      - This way Wicket will make use of the default value if the model object of <em>someModel</em>
284         *      is {@code null}.
285         *     </li>
286         *     <li>in the middle of the application logic: {@code ... = someModel.orElse(default).getModelObject();}</li>
287         * </ul>
288         *
289         * </p>
290         *
291         * @param other
292         *            a default value
293         * @return a new IModel
294         */
295        default IModel<T> orElse(T other)
296        {
297                return new IModel<T>() {
298                        @Override
299                        public T getObject()
300                        {
301                                T object = IModel.this.getObject();
302                                if (object == null)
303                                {
304                                        return other;
305                                } else
306                                {
307                                        return object;
308                                }
309                        }
310
311                        @Override
312                        public void detach()
313                        {
314                                IModel.this.detach();
315                        }
316                };
317        }
318
319        /**
320         * Returns a IModel, returning either the contained object or invoking the given supplier to get
321         * a default value.
322         *
323         * @param other
324         *            a supplier to be used as a default
325         * @return a new IModel
326         */
327        default IModel<T> orElseGet(SerializableSupplier<? extends T> other)
328        {
329                Args.notNull(other, "other");
330                return new IModel<T>() {
331                        @Override
332                        public T getObject()
333                        {
334                                T object = IModel.this.getObject();
335                                if (object == null)
336                                {
337                                        return other.get();
338                                } else
339                                {
340                                        return object;
341                                }
342                        }
343
344                        @Override
345                        public void detach()
346                        {
347                                IModel.this.detach();
348                        }
349                };
350        }
351        
352        /**
353         * Returns a IModel, returning whether the contained object is non-null.
354         *
355         * @return a new IModel
356         */
357        default IModel<Boolean> isPresent() {
358                return new IModel<Boolean>() {
359                        @Override
360                        public Boolean getObject()
361                        {
362                                return IModel.this.getObject() != null;
363                        }
364
365                        @Override
366                        public void detach()
367                        {
368                                IModel.this.detach();
369                        }
370                };
371        }
372
373        /**
374         * Returns an IModel, returning the object typed as {@code R} if it is an instance of that type,
375         * otherwise {@code null}.
376         *
377         * @param <R> the type the object should be an instance of
378         * @param clazz the {@code Class} the current model object should be an instance of
379         * @return a new IModel
380         */
381        default <R extends T> IModel<R> as(Class<R> clazz) {
382                Args.notNull(clazz, "clazz");
383                return filter(clazz::isInstance).map(clazz::cast);
384        }
385
386        /**
387         * Suppresses generics warning when casting model types.
388         *
389         * @param <T>
390         * @param model
391         * @return cast <code>model</code>
392         */
393        @SuppressWarnings("unchecked")
394        static <T> IModel<T> of(IModel<?> model)
395        {
396                return (IModel<T>)model;
397        }
398}