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("someId", 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}