Created
October 6, 2015 07:59
-
-
Save dashorst/da876be2f01770e862a5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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.lambda; | |
import java.io.Serializable; | |
import java.util.Objects; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.function.Supplier; | |
import org.apache.wicket.model.IModel; | |
import org.apache.wicket.model.LoadableDetachableModel; | |
/** | |
* | |
*/ | |
public class Lambdas | |
{ | |
/** | |
* Creates a chained model that uses a model and a getter. This model is null safe, such that | |
* when the base model returns {@code null} the resulting expression is {@code null}. Example | |
* usage: | |
* | |
* <pre> | |
* IModel<Person> personModel = ...; | |
* Lambdas.of(personModel, Person::getName); | |
* </pre> | |
* | |
* @param base | |
* the model that is used as a base to evaluate the getter against | |
* @param getter | |
* a function that gets a {@code <T>} | |
* @return a model that takes the base model and applies the getter to get the model object | |
*/ | |
public static <T, X, F extends Function<X, T> & Serializable> IModel<T> of(IModel<X> base, | |
F getter) | |
{ | |
return new IDefaultModel<T>() | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
public T getObject() | |
{ | |
X object = base.getObject(); | |
if (object != null) | |
{ | |
return getter.apply(object); | |
} | |
return null; | |
} | |
@Override | |
public void detach() | |
{ | |
base.detach(); | |
} | |
}; | |
} | |
@SuppressWarnings("javadoc") | |
public static <T, X, Y, XY extends Function<X, Y> & Serializable, YT extends Function<Y, T> & Serializable> IModel<T> of( | |
IModel<X> base, XY xToY, YT yToT) | |
{ | |
return new IDefaultModel<T>() | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
public T getObject() | |
{ | |
X object = base.getObject(); | |
if (object != null) | |
{ | |
Y y = xToY.apply(object); | |
if (y != null) | |
{ | |
return yToT.apply(y); | |
} | |
} | |
return null; | |
} | |
@Override | |
public void detach() | |
{ | |
base.detach(); | |
} | |
}; | |
} | |
/** | |
* Creates a model that uses the getter and setter. Calls to the getter (and setter) are not | |
* cached but directly executed on each {@code getObject()} and {@code setObject()}. Example | |
* usage: | |
* | |
* <pre> | |
* Lambdas.of(person::getName, person::setName); | |
* </pre> | |
* | |
* @param getter | |
* a supplier that gets a {@code <T>} | |
* @param setter | |
* a consumer that sets a {@code <T>} | |
* @return a model that uses the consumer and supplier to get and set the model object | |
*/ | |
public static <T, G extends Serializable & Supplier<T>, S extends Serializable & Consumer<T>> IModel<T> of( | |
G getter, S setter) | |
{ | |
return new LambdaModel<>(getter, setter); | |
} | |
/** | |
* A read-only model that caches the call to the {@code getter()} until the model is detached. | |
* | |
* @param getter | |
* a supplier that gets a {@code <T>} | |
* @return the read-only model that gets a {@code <T>} and caches the result until the model is | |
* detached. | |
*/ | |
public static <T, S extends Serializable & Supplier<T>> IModel<T> cachedReadOnly(S getter) | |
{ | |
return new LoadableDetachableModel<T>() | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
protected T load() | |
{ | |
return getter.get(); | |
} | |
}; | |
} | |
/** | |
* Creates a read-only model using a getter. Example usage: | |
* | |
* <pre> | |
* Lambdas.readOnly(person::getName); | |
* </pre> | |
* | |
* @param getter | |
* the getter function (also known as supplier) | |
* @return a read-only model | |
*/ | |
public static <T, S extends Serializable & Supplier<T>> IModel<T> readOnly(S getter) | |
{ | |
return new ReadOnlyModel<>(getter); | |
} | |
/** | |
* Creates a write-only model using a getter. Example usage: | |
* | |
* <pre> | |
* Lambdas.writeOnly(person::setName); | |
* </pre> | |
* | |
* @param setter | |
* the setter function (also known as consumer) | |
* @return a write-only model | |
* @throws UnsupportedOperationException | |
* when {@link IModel#getObject()} is called | |
*/ | |
public static <T, S extends Serializable & Consumer<T>> IModel<T> writeOnly(S setter) | |
{ | |
return new WriteOnlyModel<>(setter); | |
} | |
interface IDefaultModel<T> extends IModel<T> | |
{ | |
@Override | |
default T getObject() | |
{ | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
default void setObject(T object) | |
{ | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
default void detach() | |
{ | |
} | |
} | |
static class ReadOnlyModel<T, G extends Serializable & Supplier<T>> implements IDefaultModel<T> | |
{ | |
private static final long serialVersionUID = 1L; | |
private G getter; | |
public ReadOnlyModel(G getter) | |
{ | |
this.getter = getter; | |
} | |
@Override | |
public T getObject() | |
{ | |
return getter.get(); | |
} | |
@Override | |
public int hashCode() | |
{ | |
return org.apache.wicket.util.lang.Objects.hashCode(getter); | |
} | |
@Override | |
public boolean equals(Object obj) | |
{ | |
if (obj == null) | |
{ | |
return false; | |
} | |
if (getClass() != obj.getClass()) | |
{ | |
return false; | |
} | |
final ReadOnlyModel<?, ?> other = (ReadOnlyModel<?, ?>)obj; | |
if (!Objects.equals(this.getter, other.getter)) | |
{ | |
return false; | |
} | |
return true; | |
} | |
} | |
static class WriteOnlyModel<T, S extends Serializable & Consumer<T>> implements IDefaultModel<T> | |
{ | |
private static final long serialVersionUID = 1L; | |
private S setter; | |
public WriteOnlyModel(S consumer) | |
{ | |
this.setter = consumer; | |
} | |
@Override | |
public void setObject(T object) | |
{ | |
setter.accept(object); | |
} | |
@Override | |
public int hashCode() | |
{ | |
return org.apache.wicket.util.lang.Objects.hashCode(setter); | |
} | |
@Override | |
public boolean equals(Object obj) | |
{ | |
if (obj == null) | |
{ | |
return false; | |
} | |
if (getClass() != obj.getClass()) | |
{ | |
return false; | |
} | |
final WriteOnlyModel<?, ?> other = (WriteOnlyModel<?, ?>)obj; | |
if (!Objects.equals(this.setter, other.setter)) | |
{ | |
return false; | |
} | |
return true; | |
} | |
} | |
static class LambdaModel<T, S extends Serializable & Supplier<T>, C extends Serializable & Consumer<T>> | |
implements | |
IModel<T> | |
{ | |
private static final long serialVersionUID = 1L; | |
private S supplier; | |
private C consumer; | |
public LambdaModel(S supplier, C consumer) | |
{ | |
this.supplier = supplier; | |
this.consumer = consumer; | |
} | |
@Override | |
public void detach() | |
{ | |
} | |
@Override | |
public T getObject() | |
{ | |
return supplier.get(); | |
} | |
@Override | |
public void setObject(T object) | |
{ | |
consumer.accept(object); | |
} | |
@Override | |
public int hashCode() | |
{ | |
return org.apache.wicket.util.lang.Objects.hashCode(supplier, consumer); | |
} | |
@Override | |
public boolean equals(Object obj) | |
{ | |
if (obj == null) | |
{ | |
return false; | |
} | |
if (getClass() != obj.getClass()) | |
{ | |
return false; | |
} | |
final LambdaModel<?, ?, ?> other = (LambdaModel<?, ?, ?>)obj; | |
if (!Objects.equals(this.supplier, other.supplier)) | |
{ | |
return false; | |
} | |
if (!Objects.equals(this.consumer, other.consumer)) | |
{ | |
return false; | |
} | |
return true; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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.lambda; | |
import static org.hamcrest.Matchers.instanceOf; | |
import static org.hamcrest.Matchers.is; | |
import static org.hamcrest.Matchers.nullValue; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertNull; | |
import static org.junit.Assert.assertThat; | |
import org.apache.wicket.core.util.lang.WicketObjects; | |
import org.apache.wicket.model.IModel; | |
import org.junit.Test; | |
public class LambdasTest | |
{ | |
@Test | |
public void methodReference() | |
{ | |
Person person = new Person(); | |
IModel<String> personNameModel = Lambdas.of(person::getName, person::setName); | |
check(personNameModel); | |
} | |
@Test | |
public void explicitLambdas() | |
{ | |
Person person = new Person(); | |
IModel<String> personNameModel = Lambdas.of( // | |
() -> person.getName(), (name) -> person.setName(name)); | |
check(personNameModel); | |
} | |
@Test | |
public void equality() | |
{ | |
Person person = new Person(); | |
final WicketSupplier<String> getName = person::getName; | |
final WicketConsumer<String> setName = person::setName; | |
IModel<String> personNameModel1 = Lambdas.of(getName, setName); | |
IModel<String> personNameModel2 = Lambdas.of(getName, setName); | |
assertEquals(personNameModel1, personNameModel2); | |
} | |
@Test | |
public void hashcode() | |
{ | |
Person person = new Person(); | |
final WicketSupplier<String> getName = person::getName; | |
final WicketConsumer<String> setName = person::setName; | |
IModel<String> personNameModel1 = Lambdas.of(getName, setName); | |
IModel<String> personNameModel2 = Lambdas.of(getName, setName); | |
assertEquals(personNameModel1.hashCode(), personNameModel2.hashCode()); | |
} | |
@Test | |
public void chainedLambda() | |
{ | |
IModel<NonSerializablePerson> ldm = Lambdas.cachedReadOnly(NonSerializablePerson::new); | |
IModel<String> nameModel = Lambdas.of(ldm, NonSerializablePerson::getName); | |
ldm.getObject().setName("New name"); | |
assertEquals("New name", nameModel.getObject()); | |
// after a detach, we have a new object, so the name model should return null | |
ldm.detach(); | |
assertNull(nameModel.getObject()); | |
} | |
@Test | |
public void chainedLambdas() | |
{ | |
IModel<NonSerializablePerson> person = Lambdas.cachedReadOnly(NonSerializablePerson::new); | |
IModel<NonSerializableOrganization> organization = Lambdas.of(person, NonSerializablePerson::getOrganization); | |
person.getObject().getOrganization().setName("New name"); | |
WicketFunction<NonSerializableOrganization, String> organizationGetName = NonSerializableOrganization::getName; | |
IModel<?> nameModel1 = Lambdas.of(person, p -> p.getOrganization(), organizationGetName); | |
IModel<?> nameModel2 = Lambdas.of(organization, NonSerializableOrganization::getName); | |
IModel<?> nameModel3 = Lambdas.of(organization, organizationGetName); | |
assertEquals("New name", nameModel1.getObject()); | |
assertEquals("New name", nameModel2.getObject()); | |
assertEquals("New name", nameModel3.getObject()); | |
} | |
@Test | |
public void chainedLambdasAreNullSafe() | |
{ | |
IModel<NonSerializablePerson> person = Lambdas.readOnly(() -> (NonSerializablePerson)null); | |
IModel<NonSerializableOrganization> organization = Lambdas.of(person, NonSerializablePerson::getOrganization); | |
WicketFunction<NonSerializableOrganization, String> organizationGetName = NonSerializableOrganization::getName; | |
IModel<?> nameModel1 = Lambdas.of(person, p -> p.getOrganization(), organizationGetName); | |
IModel<?> nameModel2 = Lambdas.of(organization, NonSerializableOrganization::getName); | |
IModel<?> nameModel3 = Lambdas.of(organization, organizationGetName); | |
assertNull(nameModel1.getObject()); | |
assertNull(nameModel2.getObject()); | |
assertNull(nameModel3.getObject()); | |
} | |
private void check(IModel<String> personNameModel) | |
{ | |
assertThat(personNameModel.getObject(), is(nullValue())); | |
final String personName = "new name"; | |
personNameModel.setObject(personName); | |
assertThat(personNameModel.getObject(), is(personName)); | |
serialize(personNameModel, personName); | |
} | |
private void serialize(IModel<String> personNameModel, String personName) | |
{ | |
final IModel<String> clone = WicketObjects.cloneObject(personNameModel); | |
assertThat(clone, is(instanceOf(Lambdas.LambdaModel.class))); | |
assertThat(clone.getObject(), is(personName)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.apache.wicket.model.lambda; | |
class NonSerializablePerson | |
{ | |
private String name; | |
private NonSerializableOrganization organization = new NonSerializableOrganization(); | |
public String getName() | |
{ | |
return name; | |
} | |
public void setName(String name) | |
{ | |
this.name = name; | |
} | |
public NonSerializableOrganization getOrganization() | |
{ | |
return organization; | |
} | |
public void setOrganization(NonSerializableOrganization organization) | |
{ | |
this.organization = organization; | |
} | |
} | |
class NonSerializableOrganization | |
{ | |
private String name; | |
public String getName() | |
{ | |
return name; | |
} | |
public void setName(String name) | |
{ | |
this.name = name; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Seems to me one important combination is missing: