PropertyValue.java
/*
* Copyright © 2014 - 2021 Leipzig University (Database Research Group)
*
* 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 org.gradoop.common.model.impl.properties;
import org.apache.flink.core.memory.DataInputView;
import org.apache.flink.core.memory.DataOutputView;
import org.apache.flink.types.Value;
import org.gradoop.common.exceptions.UnsupportedTypeException;
import org.gradoop.common.model.api.strategies.PropertyValueStrategy;
import org.gradoop.common.model.impl.id.GradoopId;
import org.gradoop.common.model.impl.properties.strategies.PropertyValueStrategyFactory;
import org.gradoop.common.util.GradoopConstants;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Represents a single property value in the EPGM.
*
* A property value wraps a value that implements a supported data type.
*/
public class PropertyValue implements Value, Serializable, Comparable<PropertyValue> {
/**
* Represents a property value that is {@code null}.
*/
public static final PropertyValue NULL_VALUE = PropertyValue.create(null);
/**
* Value offset in byte
*/
public static final transient byte OFFSET = 0x01;
/**
* Bit flag indicating a "large" property. The length of the byte representation will be stored
* as an {@code int} instead.
*
* @see #write(DataOutputView)
*/
public static final transient byte FLAG_LARGE = (byte) 0x80;
/**
* If the length of the byte representation is larger than this value, the length will be
* stored as an {@code int} instead of a {@code short}.
*
* @see #write(DataOutputView)
*/
public static final transient int LARGE_PROPERTY_THRESHOLD = Short.MAX_VALUE;
/**
* Class version for serialization.
*/
private static final long serialVersionUID = 1L;
/**
* Stores the object representation of the value
*/
private Object value;
/**
* Default constructor.
*/
public PropertyValue() { }
/**
* Creates a new property value from the given value.
*
* If the given object type is not supported, an
* {@link UnsupportedTypeException} will be thrown.
*
* @param value value with supported type
*/
private PropertyValue(Object value) {
setObject(value);
}
/**
* Creates a new property value from the given byte array.
*
* @param bytes byte array
*/
private PropertyValue(byte[] bytes) {
setBytes(bytes);
}
/**
* Creates a new Property Value from the given object.
*
* If the given object type is not supported, an
* {@link UnsupportedTypeException} will be thrown.
*
* @param value value with supported type
* @return property value
*/
public static PropertyValue create(Object value) {
return new PropertyValue(value);
}
/**
* Create a {@link PropertyValue} that wraps a byte array.
*
* @param rawBytes array to wrap
* @return new instance of {@link PropertyValue}
*/
public static PropertyValue fromRawBytes(byte[] rawBytes) {
return new PropertyValue(rawBytes);
}
/**
* Creates a deep copy of the property value.
*
* @return property value
*/
public PropertyValue copy() {
return new PropertyValue(getRawBytes());
}
//----------------------------------------------------------------------------
// Type checking
//----------------------------------------------------------------------------
/**
* Check if the property value type is an instance of a certain class.
*
* @param clazz class to check against
* @return true if the attribute {@code value} is an object of the provided class, false
* otherwise
*/
public boolean is(Class clazz) {
return PropertyValueStrategyFactory.get(clazz).is(value);
}
/**
* True, if the value represents {@code null}.
*
* @return true, if {@code null} value
*/
public boolean isNull() {
return getRawBytes()[0] == Type.NULL.getTypeByte();
}
/**
* True, if the wrapped value is of type {@code boolean}.
*
* @return true, if {@code boolean} value
*/
public boolean isBoolean() {
return is(Boolean.class);
}
/**
* True, if the wrapped value is of type {@code short}.
*
* @return true, if {@code short} value
*/
public boolean isShort() {
return is(Short.class);
}
/**
* True, if the wrapped value is of type {@code int}.
*
* @return true, if {@code int} value
*/
public boolean isInt() {
return is(Integer.class);
}
/**
* True, if the wrapped value is of type {@code long}.
*
* @return true, if {@code long} value
*/
public boolean isLong() {
return is(Long.class);
}
/**
* True, if the wrapped value is of type {@code float}.
*
* @return true, if {@code float} value
*/
public boolean isFloat() {
return is(Float.class);
}
/**
* True, if the wrapped value is of type {@code double}.
*
* @return true, if {@code double} value
*/
public boolean isDouble() {
return is(Double.class);
}
/**
* True, if the wrapped value is of type {@link String}.
*
* @return true, if {@link String} value
*/
public boolean isString() {
return is(String.class);
}
/**
* True, if the wrapped value is of type {@link BigDecimal}.
*
* @return true, if {@link BigDecimal} value
* @see BigDecimal
*/
public boolean isBigDecimal() {
return is(BigDecimal.class);
}
/**
* True, if the wrapped value is of type {@link GradoopId}.
*
* @return true, if {@link GradoopId} value
*/
public boolean isGradoopId() {
return is(GradoopId.class);
}
/**
* True, if the wrapped value is of type {@link Map}.
*
* @return true, if {@link Map} value
*/
public boolean isMap() {
return is(Map.class);
}
/**
* True, if the wrapped value is of type {@link List}.
*
* @return true, if {@link List} value
*/
public boolean isList() {
return is(List.class);
}
/**
* True, if the wrapped value is of type {@link LocalDate}.
*
* @return true, if {@link LocalDate} value
*/
public boolean isDate() {
return is(LocalDate.class);
}
/**
* True, if the wrapped value is of type {@link LocalTime}.
*
* @return true, if {@link LocalTime} value
*/
public boolean isTime() {
return is(LocalTime.class);
}
/**
* True, if the wrapped value is of type {@link LocalDateTime}.
*
* @return true, if {@link LocalDateTime} value
*/
public boolean isDateTime() {
return is(LocalDateTime.class);
}
/**
* True, if the wrapped value is of type {@link Set}.
*
* @return true, if {@link Set} value
*/
public boolean isSet() {
return is(Set.class);
}
/**
* True, if the wrapped value is a subtype of {@link Number}.
*
* @return true, if {@link Number} value
*/
public boolean isNumber() {
return !isNull() && Number.class.isAssignableFrom(getType());
}
//----------------------------------------------------------------------------
// Getter
//----------------------------------------------------------------------------
/**
* Returns the value as the specified type. If it is already of the same type as requested, we
* just return the value. If not, an {@link UnsupportedOperationException} is thrown.
*
* @param clazz the requested value type
* @param <T> PropertyValue supported type
* @return value
* @throws UnsupportedOperationException when trying to get the wrong type
*/
@SuppressWarnings("unchecked")
public <T> T get(Class<T> clazz) throws UnsupportedOperationException {
PropertyValueStrategy strategy = PropertyValueStrategyFactory.get(clazz);
if (strategy.is(value)) {
return (T) value;
}
String type = value == null ? "null" : value.getClass().getSimpleName();
throw new UnsupportedOperationException("Value '" + value + "' of type " + type +
", cannot be accessed as " + clazz.getSimpleName());
}
/**
* Returns the wrapped value as object.
*
* @return value or {@code null} if the value is empty
*/
public Object getObject() {
Object obj = null;
if (value != null) {
obj = get(value.getClass());
}
return obj;
}
/**
* Returns the wrapped value as {@code boolean}.
*
* @return {@code boolean} value
*/
public boolean getBoolean() {
return get(Boolean.class);
}
/**
* Returns the wrapped value as {@code short}.
*
* @return {@code short} value
*/
public short getShort() {
return get(Short.class);
}
/**
* Returns the wrapped value as {@code int}.
*
* @return {@code int} value
*/
public int getInt() {
return get(Integer.class);
}
/**
* Returns the wrapped value as {@code long}.
*
* @return {@code long} value
*/
public long getLong() {
return get(Long.class);
}
/**
* Returns the wrapped value as {@code float}.
*
* @return {@code float} value
*/
public float getFloat() {
return get(Float.class);
}
/**
* Returns the wrapped value as {@code double}.
*
* @return {@code double} value
*/
public double getDouble() {
return get(Double.class);
}
/**
* Returns the wrapped value as {@link String}.
*
* @return {@link String} value
*/
public String getString() {
return get(String.class);
}
/**
* Returns the wrapped value as {@link BigDecimal}.
*
* @return {@link BigDecimal} value
* @see BigDecimal
*/
public BigDecimal getBigDecimal() {
return get(BigDecimal.class);
}
/**
* Returns the wrapped value as {@link GradoopId}.
*
* @return {@link GradoopId} value
*/
public GradoopId getGradoopId() {
return get(GradoopId.class);
}
/**
* Returns the wrapped Map as {@code Map<PropertyValue, PropertyValue>}.
*
* @return {@code Map<PropertyValue, PropertyValue>} value
*/
public Map<PropertyValue, PropertyValue> getMap() {
return get(Map.class);
}
/**
* Returns the wrapped List as {@code List<PropertyValue>}.
*
* @return {@code List<PropertyValue>} value
*/
public List<PropertyValue> getList() {
return get(List.class);
}
/**
* Returns the wrapped List as {@link LocalDate}.
*
* @return {@link LocalDate} value
*/
public LocalDate getDate() {
return get(LocalDate.class);
}
/**
* Returns the wrapped List as {@link LocalTime}.
*
* @return {@link LocalTime} value
*/
public LocalTime getTime() {
return get(LocalTime.class);
}
/**
* Returns the wrapped List as {@link LocalDateTime}.
*
* @return {@link LocalDateTime} value
*/
public LocalDateTime getDateTime() {
return get(LocalDateTime.class);
}
/**
* Returns the wrapped Set as {@code Set<PropertyValue>}.
*
* @return {@code Set<PropertyValue>} value
*/
public Set<PropertyValue> getSet() {
return get(Set.class);
}
//----------------------------------------------------------------------------
// Setter
//----------------------------------------------------------------------------
/**
* Sets the given value as internal value if it has a supported type.
*
* @param value value
* @throws UnsupportedTypeException if the type of the Object is not supported
*/
public void setObject(Object value) {
if (value != null && !PropertyValueStrategyFactory.get(value.getClass()).is(value)) {
throw new UnsupportedTypeException(value.getClass());
}
this.value = value;
}
/**
* Sets the wrapped value as {@code boolean} value.
*
* @param booleanValue value
*/
public void setBoolean(boolean booleanValue) {
setObject(booleanValue);
}
/**
* Sets the wrapped value as {@code short} value.
*
* @param shortValue value
*/
public void setShort(short shortValue) {
setObject(shortValue);
}
/**
* Sets the wrapped value as {@code int} value.
*
* @param intValue intValue
*/
public void setInt(int intValue) {
setObject(intValue);
}
/**
* Sets the wrapped value as {@code long} value.
*
* @param longValue value
*/
public void setLong(long longValue) {
setObject(longValue);
}
/**
* Sets the wrapped value as {@code float} value.
*
* @param floatValue value
*/
public void setFloat(float floatValue) {
setObject(floatValue);
}
/**
* Sets the wrapped value as {@code double} value.
*
* @param doubleValue value
*/
public void setDouble(double doubleValue) {
setObject(doubleValue);
}
/**
* Sets the wrapped value as {@link String} value.
*
* @param stringValue value
*/
public void setString(String stringValue) {
setObject(stringValue);
}
/**
* Sets the wrapped value as {@link BigDecimal} value.
*
* @param bigDecimalValue value
*/
public void setBigDecimal(BigDecimal bigDecimalValue) {
setObject(bigDecimalValue);
}
/**
* Sets the wrapped value as {@link GradoopId} value.
*
* @param gradoopIdValue value
*/
public void setGradoopId(GradoopId gradoopIdValue) {
setObject(gradoopIdValue);
}
/**
* Sets the wrapped value as {@link Map} value.
*
* @param map value
*/
public void setMap(Map<PropertyValue, PropertyValue> map) {
setObject(map);
}
/**
* Sets the wrapped value as {@link List} value.
*
* @param list value
*/
public void setList(List<PropertyValue> list) {
setObject(list);
}
/**
* Sets the wrapped value as {@link LocalDate} value.
*
* @param date value
*/
public void setDate(LocalDate date) {
setObject(date);
}
/**
* Sets the wrapped value as {@link LocalTime} value.
*
* @param time value
*/
public void setTime(LocalTime time) {
setObject(time);
}
/**
* Sets the wrapped value as {@code LocalDateTime} value.
*
* @param dateTime value
*/
public void setDateTime(LocalDateTime dateTime) {
setObject(dateTime);
}
/**
* Sets the wrapped value as {@code Set} value.
*
* @param set value
*/
public void setSet(Set<PropertyValue> set) {
setObject(set);
}
//----------------------------------------------------------------------------
// Util
//----------------------------------------------------------------------------
/**
* Get the data type as class object according to the first position of the rawBytes[] array.
*
* @return Class object
*/
public Class<?> getType() {
Class<?> clazz = null;
if (value != null) {
clazz = PropertyValueStrategyFactory.get(value.getClass()).getType();
}
return clazz;
}
public int getByteSize() {
return getRawBytes().length;
}
public byte[] getRawBytes() {
return PropertyValueStrategyFactory.getRawBytes(value);
}
/**
* Set internal byte representation.
*
* @param bytes array
*/
public void setBytes(byte[] bytes) {
value = PropertyValueStrategyFactory.fromRawBytes(bytes);
}
@Override
public boolean equals(Object object) {
return object instanceof PropertyValue && Objects.equals(value, ((PropertyValue) object).value);
}
@Override
public int hashCode() {
return Arrays.hashCode(PropertyValueStrategyFactory.getRawBytes(value));
}
/**
* Compares this to another {@link PropertyValue}. See {@link PropertyValueStrategyFactory#compare}.
*
* @see PropertyValueStrategyFactory#compare
* @param other the property value to be compared.
* @return a negative integer, zero, or a positive integer as this object is less than, equal to,
* or greater than the specified object.
*/
@Override
public int compareTo(PropertyValue other) {
return PropertyValueStrategyFactory.compare(value, other.value);
}
/**
* Returns the byte size of the properties internal representation.
*
* @return byte size
*/
public int byteSize() {
byte[] rawBytes = PropertyValueStrategyFactory.getRawBytes(value);
return rawBytes.length;
}
/**
* Byte representation:
*
* byte 1 : type info
*
* for dynamic length types (e.g. String and BigDecimal)
* byte 2 : length (short)
* byte 3 : length (short)
* byte 4 - end : value bytes
*
* If the size of the internal byte representation if larger than
* {@link #LARGE_PROPERTY_THRESHOLD} (i.e. if a {@code short} is too small to store the length),
* then the {@link #FLAG_LARGE} bit will be set in the first byte and the byte representation
* will be:
* byte 2 ; length (int)
* byte 3 : length (int)
* byte 4 : length (int)
* byte 5 : length (int)
* byte 6 - end : value bytes
*
* for fixed length types (e.g. int, long, float, ...)
* byte 2 - end : value bytes
*
* @param outputView data output to write data to
* @throws IOException if write to output view fails.
*/
@Override
public void write(DataOutputView outputView) throws IOException {
PropertyValueStrategyFactory.get(value).write(value, outputView);
}
@Override
public void read(DataInputView inputView) throws IOException {
// type
byte typeByte = inputView.readByte();
// Apply bitmask to get the actual type.
byte type = (byte) (~PropertyValue.FLAG_LARGE & typeByte);
PropertyValueStrategy strategy = PropertyValueStrategyFactory.get(type);
if (strategy == null) {
throw new UnsupportedTypeException("No strategy for type byte from input view found");
} else {
value = strategy.read(inputView, typeByte);
}
}
@Override
public String toString() {
return getObject() != null ?
getObject().toString() :
GradoopConstants.NULL_STRING;
}
}