/* * Copyright 2017 requery.io * * Licensed 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 io.requery.proxy; import io.requery.meta.Attribute; import io.requery.meta.Type; import io.requery.util.Objects; import java.util.LinkedHashMap; /** * Proxy object for a data entity containing various properties that can be read or written and the * {@link Attribute} meta data associated with them. * * @param <E> entity type * * @author Nikhil Purushe */ public class EntityProxy<E> implements Gettable<E>, Settable<E>, EntityStateListener { private final Type<E> type; private final E entity; private final boolean stateless; private PropertyLoader<E> loader; private CompositeEntityStateListener<E> listeners; private Object key; private boolean regenerateKey; /** * Create a new {@link EntityProxy} instance for a given entity object that can proxy it's * getters and setters. * * @param entity the entity that is being proxied * @param type the entity meta type instance */ public EntityProxy(E entity, Type<E> type) { this.entity = entity; this.type = type; this.stateless = type.isStateless(); } private PropertyState loadProperty(Attribute<E, ?> attribute) { if (!stateless) { PropertyState state = getState(attribute); if (state == PropertyState.FETCH && loader != null) { // lazy loaded, get from store then set the value loader.load(entity, this, attribute); } return state; } return null; } @Override public <V> V get(Attribute<E, V> attribute) { return get(attribute, true); } @Override public <V> V get(Attribute<E, V> attribute, boolean fetch) { PropertyState state = fetch ? loadProperty(attribute) : getState(attribute); V value = attribute.getProperty().get(entity); if (value == null && (state == PropertyState.FETCH || stateless) && attribute.getInitializer() != null) { value = attribute.getInitializer().initialize(this, attribute); set(attribute, value, PropertyState.FETCH); } return value; } @Override public int getInt(Attribute<E, Integer> attribute) { IntProperty<E> property = (IntProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getInt(entity); } @Override public long getLong(Attribute<E, Long> attribute) { LongProperty<E> property = (LongProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getLong(entity); } @Override public short getShort(Attribute<E, Short> attribute) { ShortProperty<E> property = (ShortProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getShort(entity); } @Override public byte getByte(Attribute<E, Byte> attribute) { ByteProperty<E> property = (ByteProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getByte(entity); } @Override public float getFloat(Attribute<E, Float> attribute) { FloatProperty<E> property = (FloatProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getFloat(entity); } @Override public double getDouble(Attribute<E, Double> attribute) { DoubleProperty<E> property = (DoubleProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getDouble(entity); } @Override public boolean getBoolean(Attribute<E, Boolean> attribute) { BooleanProperty<E> property = (BooleanProperty<E>) attribute.getProperty(); loadProperty(attribute); return property.getBoolean(entity); } @Override public <V> void set(Attribute<E, V> attribute, V value) { set(attribute, value, PropertyState.MODIFIED); } @Override public <V> void set(Attribute<E, V> attribute, V value, PropertyState state) { attribute.getProperty().set(entity, value); setState(attribute, state); checkRegenerateKey(attribute); } @Override public void setObject(Attribute<E, ?> attribute, Object value, PropertyState state) { @SuppressWarnings("unchecked") Property<E, Object> property = (Property<E, Object>) attribute.getProperty(); property.set(entity, value); setState(attribute, state); checkRegenerateKey(attribute); } @Override public void setInt(Attribute<E, Integer> attribute, int value, PropertyState state) { IntProperty<E> property = (IntProperty<E>) attribute.getProperty(); property.setInt(entity, value); setState(attribute, state); checkRegenerateKey(attribute); } @Override public void setLong(Attribute<E, Long> attribute, long value, PropertyState state) { LongProperty<E> property = (LongProperty<E>) attribute.getProperty(); property.setLong(entity, value); setState(attribute, state); checkRegenerateKey(attribute); } @Override public void setShort(Attribute<E, Short> attribute, short value, PropertyState state) { ShortProperty<E> property = (ShortProperty<E>) attribute.getProperty(); property.setShort(entity, value); setState(attribute, state); } @Override public void setByte(Attribute<E, Byte> attribute, byte value, PropertyState state) { ByteProperty<E> property = (ByteProperty<E>) attribute.getProperty(); property.setByte(entity, value); setState(attribute, state); } @Override public void setFloat(Attribute<E, Float> attribute, float value, PropertyState state) { FloatProperty<E> property = (FloatProperty<E>) attribute.getProperty(); property.setFloat(entity, value); setState(attribute, state); } @Override public void setDouble(Attribute<E, Double> attribute, double value, PropertyState state) { DoubleProperty<E> property = (DoubleProperty<E>) attribute.getProperty(); property.setDouble(entity, value); setState(attribute, state); } @Override public void setBoolean(Attribute<E, Boolean> attribute, boolean value, PropertyState state) { BooleanProperty<E> property = (BooleanProperty<E>) attribute.getProperty(); property.setBoolean(entity, value); setState(attribute, state); } private void checkRegenerateKey(Attribute<E, ?> attribute) { if (attribute.isKey()) { regenerateKey = true; } } /** * Sets the current {@link PropertyState} of a given {@link Attribute}. * * @param attribute to set * @param state state to set */ public void setState(Attribute<E, ?> attribute, PropertyState state) { if (!stateless) { attribute.getPropertyState().set(entity, state); } } /** * Gets the current {@link PropertyState} of a given {@link Attribute}. * * @param attribute to get * @return the state of the attribute */ public PropertyState getState(Attribute<E, ?> attribute) { if (stateless) { return null; } PropertyState state = attribute.getPropertyState().get(entity); return state == null ? PropertyState.FETCH : state; } /** * @return the key value for this proxy instance which is used to uniquely identify it. */ public Object key() { if (regenerateKey || key == null) { if (type.getSingleKeyAttribute() != null) { key = getKey(type.getSingleKeyAttribute()); // typical case one key attribute } else if (type.getKeyAttributes().size() > 1) { LinkedHashMap<Attribute<E, ?>, Object> keys = new LinkedHashMap<>(type.getKeyAttributes().size()); for (Attribute<E, ?> attribute : type.getKeyAttributes()) { keys.put(attribute, getKey(attribute)); } key = new CompositeKey<>(keys); } else { key = this; } } return key; } @SuppressWarnings("unchecked") public Object getKey(Attribute<E, ?> attribute) { if (attribute.isAssociation()) { Attribute referenced = attribute.getReferencedAttribute().get(); Object association = get(attribute, false); if (association != null) { Type<Object> type = referenced.getDeclaringType(); EntityProxy proxy = type.getProxyProvider().apply(association); return proxy == null ? null : proxy.get(referenced, false); } else { return null; } } return get(attribute, false); } /** * @return true if linked to a {@link PropertyLoader} instance that will retrieve not loaded * property values. */ public boolean isLinked() { synchronized (syncObject()) { return this.loader != null; } } /** * link the proxy to a {@link PropertyLoader} instance that will retrieve not loaded property * values. * * @param loader instance */ public void link(PropertyLoader<E> loader) { synchronized (syncObject()) { this.loader = loader; } } public void unlink() { synchronized (syncObject()) { this.loader = null; } } public E copy() { E copy = type.getFactory().get(); EntityProxy<E> proxy = type.getProxyProvider().apply(copy); proxy.link(loader); for (Attribute<E, ?> attribute : type.getAttributes()) { if (!attribute.isAssociation()) { PropertyState state = getState(attribute); if (state == PropertyState.LOADED || state == PropertyState.MODIFIED) { Object value = get(attribute, false); @SuppressWarnings("unchecked") Attribute<E, Object> a = (Attribute<E, Object>) attribute; proxy.set(a, value, state); } } } return copy; } public Type<E> type() { return type; } public Object syncObject() { return this; } // should only be called from the constructor of the entity public EntityStateEventListenable<E> modifyListeners() { if (listeners == null) { listeners = new CompositeEntityStateListener<>(entity); } return listeners; } private EntityStateListener stateListener() { // if no listeners were ever added return static empty version to avoid overhead of // creating listener collection elements return listeners == null ? EntityStateListener.EMPTY : listeners; } @Override public void preUpdate() { stateListener().preUpdate(); } @Override public void postUpdate() { stateListener().postUpdate(); } @Override public void preInsert() { stateListener().preInsert(); } @Override public void postInsert() { stateListener().postInsert(); } @Override public void preDelete() { stateListener().preDelete(); } @Override public void postDelete() { stateListener().postDelete(); } @Override public void postLoad() { stateListener().postLoad(); } @Override public boolean equals(Object obj) { if (obj instanceof EntityProxy) { EntityProxy other = (EntityProxy) obj; if (other.entity.getClass().equals(entity.getClass())) { for (Attribute<E, ?> attribute : type.getAttributes()) { // comparing only the non-associative properties for now if (!attribute.isAssociation()) { Object value = get(attribute, false); @SuppressWarnings("unchecked") Object otherValue = other.get(attribute, false); if (!Objects.equals(value, otherValue)) { return false; } } } return true; } } return false; } @Override public int hashCode() { int hash = 31; for (Attribute<E, ?> attribute : type.getAttributes()) { if (!attribute.isAssociation()) { hash = 31 * hash + Objects.hashCode(get(attribute, false)); } } return hash; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(type.getName()); sb.append(" ["); int index = 0; for (Attribute<E, ?> attribute : type.getAttributes()) { if (index > 0) { sb.append(", "); } Object value = get(attribute, false); sb.append(value == null ? "null" : value.toString()); index++; } sb.append("]"); return sb.toString(); } }