diff --git a/bean/src/main/java/io/github/mmm/entity/property/link/LinkProperty.java b/bean/src/main/java/io/github/mmm/entity/property/link/LinkProperty.java index e0a9898..1f1a042 100644 --- a/bean/src/main/java/io/github/mmm/entity/property/link/LinkProperty.java +++ b/bean/src/main/java/io/github/mmm/entity/property/link/LinkProperty.java @@ -1,293 +1,340 @@ -/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 - * http://www.apache.org/licenses/LICENSE-2.0 */ -package io.github.mmm.entity.property.link; - -import java.util.function.Function; - -import io.github.mmm.entity.Entity; -import io.github.mmm.entity.bean.EntityBean; -import io.github.mmm.entity.id.Id; -import io.github.mmm.entity.id.IdMarshalling; -import io.github.mmm.entity.link.IdLink; -import io.github.mmm.entity.link.Link; -import io.github.mmm.entity.link.LinkMapper; -import io.github.mmm.entity.property.id.IdProperty; -import io.github.mmm.marshall.StructuredReader; -import io.github.mmm.marshall.StructuredWriter; -import io.github.mmm.property.PropertyMetadata; -import io.github.mmm.property.ReadableProperty; -import io.github.mmm.property.criteria.CriteriaPredicate; -import io.github.mmm.property.criteria.PredicateOperator; -import io.github.mmm.property.object.ObjectProperty; -import io.github.mmm.value.PropertyPath; -import io.github.mmm.value.ReadableValue; -import io.github.mmm.value.converter.TypeMapper; - -/** - * {@link ObjectProperty} with {@link Link} {@link #getValue() value} {@link Link#getTarget() pointing to} an - * {@link io.github.mmm.entity.bean.EntityBean entity}. - * - * @param the generic type of the {@link Link#getTarget() linked} {@link io.github.mmm.entity.bean.EntityBean - * entity}. - * - * @since 1.0.0 - */ -public class LinkProperty extends ObjectProperty> { - - private Class entityClass; - - private Function, E> resolver; - - private LinkMapper typeMapper; - - /** - * The constructor. - * - * @param name the {@link #getName() name}. - * @param entityClass the optional {@link Class} of the linked entity. - * @param metadata the {@link #getMetadata() metadata}. - */ - public LinkProperty(String name, Class entityClass, PropertyMetadata> metadata) { - - this(name, entityClass, metadata, null); - } - - /** - * The constructor. - * - * @param name the {@link #getName() name}. - * @param entityClass the optional {@link Class} of the linked entity. - * @param metadata the {@link #getMetadata() metadata}. - * @param resolver the optional {@link IdLink#of(Id, Function) resolver function}. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public LinkProperty(String name, Class entityClass, PropertyMetadata> metadata, - Function, E> resolver) { - - super(name, (Class) Link.class, metadata); - this.entityClass = entityClass; - this.resolver = resolver; - } - - /** - * The constructor. - * - * @param name the {@link #getName() name}. - * @param value the (initial) {@link #get() value}. - * @param metadata the {@link #getMetadata() metadata}. - */ - public LinkProperty(String name, Link value, PropertyMetadata> metadata) { - - this(name, value, metadata, null); - } - - /** - * The constructor. - * - * @param name the {@link #getName() name}. - * @param value the (initial) {@link #get() value}. - * @param metadata the {@link #getMetadata() metadata}. - * @param resolver the optional {@link IdLink#of(Id, Function) resolver function}. - */ - public LinkProperty(String name, Link value, PropertyMetadata> metadata, Function, E> resolver) { - - super(name, value, metadata); - this.entityClass = value.getId().getEntityClass(); - this.resolver = resolver; - } - - @Override - protected void doSet(Link newValue) { - - if (newValue != null) { - Id id = newValue.getId(); - if (id != null) { - if (this.entityClass == null) { - this.entityClass = id.getEntityClass(); - } else { - Class idEntityType = id.getEntityClass(); - if (idEntityType == null) { - if (newValue instanceof IdLink) { - newValue = ((IdLink) newValue).withType(this.entityClass); - } - } else if (!this.entityClass.isAssignableFrom(idEntityType)) { - throw new IllegalArgumentException("Cannot set link of type " + idEntityType.getName() + " to property " - + getName() + " with incompatible entity type " + this.entityClass.getName()); - } - } - } - } - super.doSet(newValue); - } - - /** - * @return the linked {@link EntityBean entity}. - * @see Link#getTarget() - */ - public E getEntity() { - - Link link = get(); - if (link == null) { - return null; - } - return link.getTarget(); - } - - /** - * @return the {@link Id#getEntityClass() entity class}. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Class getEntityClass() { - - if (this.entityClass == null) { - Link link = get(); - if (link != null) { - Id id = link.getId(); - if (id != null) { - this.entityClass = id.getEntityClass(); - } else if (link.isResolved()) { - E target = link.getTarget(); - this.entityClass = (Class) ((EntityBean) target).getType().getJavaClass(); - } - } - } - return this.entityClass; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - public TypeMapper, Id> getTypeMapper() { - - if (this.typeMapper == null) { - this.typeMapper = new LinkMapper(this.resolver); - } - return (TypeMapper) this.typeMapper; - } - - @Override - protected void readValue(StructuredReader reader) { - - Id id = IdMarshalling.get().readObject(reader, this.entityClass); - IdLink link = IdLink.of(id, this.resolver); - setValue(link); - } - - @Override - public void write(StructuredWriter writer) { - - Id id = null; - Link link = getValue(); - if (link != null) { - id = link.getId(); - } - IdMarshalling.get().writeObject(writer, id); - } - - /** - * @see #eq(Object) - * @param other the literal {@link Id} to compare with using {@link PredicateOperator#EQ = (equal)}. - * @return the resulting {@link CriteriaPredicate}. - */ - public CriteriaPredicate eq(Id other) { - - return eq(Link.of(other)); - } - - /** - * @see #eq(Object) - * @param other the {@link IdProperty} to compare with using {@link PredicateOperator#EQ = (equal)}. - * @return the resulting {@link CriteriaPredicate}. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public CriteriaPredicate eq(IdProperty other) { - - return predicate((ReadableProperty) this, PredicateOperator.EQ, (PropertyPath) other); - } - - /** - * @see #eq(Object) - * @param other the literal {@link Entity} whose {@link Entity#getId() ID} to compare with using - * {@link PredicateOperator#EQ = (equal)}. - * @return the resulting {@link CriteriaPredicate}. - */ - public CriteriaPredicate eq(E other) { - - if ((other != null) && (other.getId() == null)) { - return eq(other.Id()); - } - return eq(Link.of(other)); - } - - /** - * @see #neq(Object) - * @param other the literal {@link Id} to compare with using {@link PredicateOperator#NEQ != (not-equal)}. - * @return the resulting {@link CriteriaPredicate}. - */ - public CriteriaPredicate neq(Id other) { - - return neq(Link.of(other)); - } - - /** - * @see #neq(Object) - * @param other the literal {@link Entity} whose {@link Entity#getId() ID} to compare with using - * {@link PredicateOperator#NEQ != (not-equal)}. - * @return the resulting {@link CriteriaPredicate}. - */ - public CriteriaPredicate neq(E other) { - - if ((other != null) && (other.getId() == null)) { - return neq(other.Id()); - } - return neq(Link.of(other)); - } - - /** - * @see #neq(Object) - * @param other the {@link IdProperty} to compare with using {@link PredicateOperator#NEQ != (not-equal)}. - * @return the resulting {@link CriteriaPredicate}. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public CriteriaPredicate neq(IdProperty other) { - - return predicate((ReadableProperty) this, PredicateOperator.NEQ, (PropertyPath) other); - } - - private CriteriaPredicate predicate(ReadableProperty property1, PredicateOperator op, - PropertyPath property2) { - - return CriteriaPredicate.of(property1, op, property2); - } - - /** - * ATTENTION:
- * This is an internal method for framework code. - * - * @param resolver new resolver {@link Function}. - * @see IdLink#setResolver(Function) - */ - @SuppressWarnings("unchecked") - public void setResolver(Function, E> resolver) { - - // TODO: checks? - this.resolver = resolver; - this.typeMapper = null; - Link link = doGet(); - if ((link != null) && !link.isResolved()) { - if (link instanceof IdLink idLink) { - idLink.setResolver(resolver); - } - } - } - - @Override - public void copyValue(ReadableValue> other) { - - Link link = other.get(); - if ((link != null) && link.isResolved()) { - link = Link.of(link.getId()); - } - set(link); - } - -} +/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 */ +package io.github.mmm.entity.property.link; + +import java.util.function.Function; +import java.util.function.Supplier; + +import io.github.mmm.bean.WritableBean; +import io.github.mmm.entity.Entity; +import io.github.mmm.entity.bean.EntityBean; +import io.github.mmm.entity.id.Id; +import io.github.mmm.entity.id.IdMarshalling; +import io.github.mmm.entity.link.AbstractLink; +import io.github.mmm.entity.link.IdLink; +import io.github.mmm.entity.link.Link; +import io.github.mmm.entity.link.LinkMapper; +import io.github.mmm.entity.property.id.IdProperty; +import io.github.mmm.marshall.StructuredReader; +import io.github.mmm.marshall.StructuredWriter; +import io.github.mmm.property.PropertyMetadata; +import io.github.mmm.property.ReadableProperty; +import io.github.mmm.property.criteria.CriteriaPredicate; +import io.github.mmm.property.criteria.PredicateOperator; +import io.github.mmm.property.object.ObjectProperty; +import io.github.mmm.value.PropertyPath; +import io.github.mmm.value.ReadableValue; +import io.github.mmm.value.converter.TypeMapper; + +/** + * {@link ObjectProperty} with {@link Link} {@link #getValue() value} {@link Link#getTarget() pointing to} an + * {@link io.github.mmm.entity.bean.EntityBean entity}. + * + * @param the generic type of the {@link Link#getTarget() linked} {@link io.github.mmm.entity.bean.EntityBean + * entity}. + * + * @since 1.0.0 + */ +public class LinkProperty extends ObjectProperty> { + + private Class entityClass; + + private Function, E> resolver; + + private LinkMapper typeMapper; + + /** + * The constructor. + * + * @param name the {@link #getName() name}. + * @param entityClass the optional {@link Class} of the linked entity. + * @param metadata the {@link #getMetadata() metadata}. + */ + public LinkProperty(String name, Class entityClass, PropertyMetadata> metadata) { + + this(name, entityClass, metadata, null); + } + + /** + * The constructor. + * + * @param name the {@link #getName() name}. + * @param entityClass the optional {@link Class} of the linked entity. + * @param metadata the {@link #getMetadata() metadata}. + * @param resolver the optional {@link IdLink#of(Id, Function) resolver function}. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public LinkProperty(String name, Class entityClass, PropertyMetadata> metadata, + Function, E> resolver) { + + super(name, (Class) Link.class, metadata); + this.entityClass = entityClass; + this.resolver = resolver; + } + + /** + * The constructor. + * + * @param name the {@link #getName() name}. + * @param value the (initial) {@link #get() value}. + * @param metadata the {@link #getMetadata() metadata}. + */ + public LinkProperty(String name, Link value, PropertyMetadata> metadata) { + + this(name, value, metadata, null); + } + + /** + * The constructor. + * + * @param name the {@link #getName() name}. + * @param value the (initial) {@link #get() value}. + * @param metadata the {@link #getMetadata() metadata}. + * @param resolver the optional {@link IdLink#of(Id, Function) resolver function}. + */ + public LinkProperty(String name, Link value, PropertyMetadata> metadata, Function, E> resolver) { + + super(name, value, metadata); + this.entityClass = value.getId().getEntityClass(); + this.resolver = resolver; + } + + @Override + protected void doSet(Link newValue) { + + if (newValue != null) { + Id id = newValue.getId(); + if (id != null) { + if (this.entityClass == null) { + this.entityClass = id.getEntityClass(); + } else { + Class idEntityType = id.getEntityClass(); + if (idEntityType == null) { + if (newValue instanceof IdLink) { + newValue = ((IdLink) newValue).withType(this.entityClass); + } + } else if (!this.entityClass.isAssignableFrom(idEntityType)) { + throw new IllegalArgumentException("Cannot set link of type " + idEntityType.getName() + " to property " + + getName() + " with incompatible entity type " + this.entityClass.getName()); + } + } + } + } + super.doSet(newValue); + } + + /** + * @return the linked {@link EntityBean entity}. + * @see Link#getTarget() + */ + public E getEntity() { + + Link link = get(); + if (link == null) { + return null; + } + return link.getTarget(); + } + + /** + * @return the {@link Id#getEntityClass() entity class}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Class getEntityClass() { + + if (this.entityClass == null) { + Link link = get(); + if (link != null) { + Id id = link.getId(); + if (id != null) { + this.entityClass = id.getEntityClass(); + } else if (link.isResolved()) { + E target = link.getTarget(); + this.entityClass = (Class) ((EntityBean) target).getType().getJavaClass(); + } + } + } + return this.entityClass; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public TypeMapper, Id> getTypeMapper() { + + if (this.typeMapper == null) { + this.typeMapper = new LinkMapper(this.resolver); + } + return (TypeMapper) this.typeMapper; + } + + @Override + public boolean isValueMutable() { + + return true; // IdLink without resolver function is actually immutable but lets keep it simple + } + + @Override + protected Supplier> createReadOnlyExpression() { + + final ReadOnlyLink readOnlyLink = new ReadOnlyLink(); + return () -> { + Link link = get(); + if (link == null) { + return null; + } + return readOnlyLink; + }; + } + + @Override + protected void readValue(StructuredReader reader) { + + Id id = IdMarshalling.get().readObject(reader, this.entityClass); + IdLink link = IdLink.of(id, this.resolver); + setValue(link); + } + + @Override + public void write(StructuredWriter writer) { + + Id id = null; + Link link = getValue(); + if (link != null) { + id = link.getId(); + } + IdMarshalling.get().writeObject(writer, id); + } + + /** + * @see #eq(Object) + * @param other the literal {@link Id} to compare with using {@link PredicateOperator#EQ = (equal)}. + * @return the resulting {@link CriteriaPredicate}. + */ + public CriteriaPredicate eq(Id other) { + + return eq(Link.of(other)); + } + + /** + * @see #eq(Object) + * @param other the {@link IdProperty} to compare with using {@link PredicateOperator#EQ = (equal)}. + * @return the resulting {@link CriteriaPredicate}. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public CriteriaPredicate eq(IdProperty other) { + + return predicate((ReadableProperty) this, PredicateOperator.EQ, (PropertyPath) other); + } + + /** + * @see #eq(Object) + * @param other the literal {@link Entity} whose {@link Entity#getId() ID} to compare with using + * {@link PredicateOperator#EQ = (equal)}. + * @return the resulting {@link CriteriaPredicate}. + */ + public CriteriaPredicate eq(E other) { + + if ((other != null) && (other.getId() == null)) { + return eq(other.Id()); + } + return eq(Link.of(other)); + } + + /** + * @see #neq(Object) + * @param other the literal {@link Id} to compare with using {@link PredicateOperator#NEQ != (not-equal)}. + * @return the resulting {@link CriteriaPredicate}. + */ + public CriteriaPredicate neq(Id other) { + + return neq(Link.of(other)); + } + + /** + * @see #neq(Object) + * @param other the literal {@link Entity} whose {@link Entity#getId() ID} to compare with using + * {@link PredicateOperator#NEQ != (not-equal)}. + * @return the resulting {@link CriteriaPredicate}. + */ + public CriteriaPredicate neq(E other) { + + if ((other != null) && (other.getId() == null)) { + return neq(other.Id()); + } + return neq(Link.of(other)); + } + + /** + * @see #neq(Object) + * @param other the {@link IdProperty} to compare with using {@link PredicateOperator#NEQ != (not-equal)}. + * @return the resulting {@link CriteriaPredicate}. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public CriteriaPredicate neq(IdProperty other) { + + return predicate((ReadableProperty) this, PredicateOperator.NEQ, (PropertyPath) other); + } + + private CriteriaPredicate predicate(ReadableProperty property1, PredicateOperator op, + PropertyPath property2) { + + return CriteriaPredicate.of(property1, op, property2); + } + + /** + * ATTENTION:
+ * This is an internal method for framework code. + * + * @param resolver new resolver {@link Function}. + * @see IdLink#setResolver(Function) + */ + @SuppressWarnings("unchecked") + public void setResolver(Function, E> resolver) { + + // TODO: checks? + this.resolver = resolver; + this.typeMapper = null; + Link link = doGet(); + if ((link != null) && !link.isResolved()) { + if (link instanceof IdLink idLink) { + idLink.setResolver(resolver); + } + } + } + + @Override + public void copyValue(ReadableValue> other) { + + Link link = other.get(); + if ((link != null) && link.isResolved()) { + link = Link.of(link.getId()); + } + set(link); + } + + private class ReadOnlyLink extends AbstractLink { + @Override + public Id getId() { + + return get().getId(); + } + + @Override + public E getTarget() { + + E target = get().getTarget(); + if (target != null) { + target = WritableBean.getReadOnly(target); + } + return target; + } + + @Override + public boolean isResolved() { + + return get().isResolved(); + } + + } + +} diff --git a/bean/src/test/java/io/github/mmm/entity/property/PropertyTest.java b/bean/src/test/java/io/github/mmm/entity/bean/PropertyTest.java similarity index 96% rename from bean/src/test/java/io/github/mmm/entity/property/PropertyTest.java rename to bean/src/test/java/io/github/mmm/entity/bean/PropertyTest.java index 5708d60..18b683c 100644 --- a/bean/src/test/java/io/github/mmm/entity/property/PropertyTest.java +++ b/bean/src/test/java/io/github/mmm/entity/bean/PropertyTest.java @@ -1,4 +1,4 @@ -package io.github.mmm.entity.property; +package io.github.mmm.entity.bean; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/bean/src/test/java/io/github/mmm/entity/property/link/LinkPropertyTest.java b/bean/src/test/java/io/github/mmm/entity/property/link/LinkPropertyTest.java index 2ea5dbe..808eca1 100644 --- a/bean/src/test/java/io/github/mmm/entity/property/link/LinkPropertyTest.java +++ b/bean/src/test/java/io/github/mmm/entity/property/link/LinkPropertyTest.java @@ -1,12 +1,15 @@ package io.github.mmm.entity.property.link; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; +import io.github.mmm.base.exception.ReadOnlyException; import io.github.mmm.bean.BeanFactory; +import io.github.mmm.entity.bean.PropertyTest; import io.github.mmm.entity.id.Id; import io.github.mmm.entity.id.LongVersionId; import io.github.mmm.entity.link.Link; -import io.github.mmm.entity.property.PropertyTest; import io.github.mmm.property.WritableProperty; /** @@ -46,9 +49,16 @@ public void testReadOnly() { assertThat((Object) readOnlyLinkProperty.get()).isNull(); linkProperty.set(LINK); Link readOnlyLink = readOnlyLinkProperty.get(); - readOnlyLink.getTarget().setId(id2); - // assertThat(readOnlyLink).isNotNull().isNotSameAs(LINK); + assertThrows(ReadOnlyException.class, () -> { + readOnlyLink.getTarget().setId(id2); + }); + assertThat(readOnlyLink).isNotNull().isNotSameAs(LINK); + } + + @Override + protected void verifyReadOnlyValue(LinkProperty readOnly) { + assertThat(readOnly.get()).isNotNull().isNotSameAs(LINK); } }