PropertyValueStrategyFactory.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.strategies;
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.PropertyValue;
import org.gradoop.common.model.impl.properties.Type;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Factory class responsible for instantiating strategy classes that manage every kind of access to
* the current {@link PropertyValue} value.
*/
public class PropertyValueStrategyFactory {
/**
* {@link PropertyValueStrategyFactory} instance
*/
private static PropertyValueStrategyFactory INSTANCE = new PropertyValueStrategyFactory();
/**
* Map which links a data type to a strategy class
*/
private final Map<Class, PropertyValueStrategy> classStrategyMap;
/**
* Array which links a type byte (as index) to a strategy class
*/
private final PropertyValueStrategy[] byteStrategyMap;
/**
* Strategy for {@code null}-value properties
*/
private final NullStrategy nullStrategy;
/**
* Constructs an {@link PropertyValueStrategyFactory} with type - strategy mappings as defined in
* {@code initClassStrategyMap}.
* Only one instance of this class is needed.
*/
private PropertyValueStrategyFactory() {
nullStrategy = new NullStrategy();
classStrategyMap = initClassStrategyMap();
byteStrategyMap = initByteStrategyMap();
}
/**
* Returns value which is represented by the the provided byte array. Assumes that the value is
* serialized according to the {@link PropertyValue} standard.
*
* @param bytes byte array of raw bytes.
* @return Object which is the result of the deserialization.
*/
public static Object fromRawBytes(byte[] bytes) {
PropertyValueStrategy strategy = INSTANCE.byteStrategyMap[bytes[0]];
try {
return strategy == null ? null : strategy.get(bytes);
} catch (IOException e) {
throw new RuntimeException("Error while deserializing object.", e);
}
}
/**
* Compares two values.<br>
* {@link PropertyValue#NULL_VALUE} is considered to be less than all other properties.
* <p>
* If {@code other} is not comparable to {@code value}, the used {@link PropertyValueStrategy} will throw an
* {@code IllegalArgumentException}. This behavior violates the requirements of
* {@link Comparable#compareTo}.
*
* @param value first value.
* @param other second value.
* @return a negative integer, zero, or a positive integer as {@code value} is less than, equal
* to, or greater than {@code other}.
*/
public static int compare(Object value, Object other) {
if (value == null || other == null) {
return INSTANCE.nullStrategy.compare(value, other);
} else {
PropertyValueStrategy strategy = get(value.getClass());
return strategy.compare(value, other);
}
}
/**
* Get byte array representation of the provided object. The object is serialized according to the
* {@link PropertyValue} standard.
* If the given type is not supported, an {@code UnsupportedTypeException} will be thrown.
*
* @param value to be serialized.
* @return byte array representation of the provided object.
*/
public static byte[] getRawBytes(Object value) {
if (value != null) {
try {
return get(value.getClass()).getRawBytes(value);
} catch (IOException e) {
throw new RuntimeException("Error while serializing object.", e);
}
}
return new byte[] {Type.NULL.getTypeByte()};
}
/**
* Returns strategy mapping to the provided byte value.
*
* @param value representing a data type.
* @return strategy class.
* @throws UnsupportedTypeException when there is no matching strategy for a given type byte.
*/
public static PropertyValueStrategy get(byte value) throws UnsupportedTypeException {
PropertyValueStrategy strategy = INSTANCE.byteStrategyMap[value];
if (strategy == null) {
throw new UnsupportedTypeException("No strategy for type byte " + value);
}
return strategy;
}
/**
* Get a strategy which corresponds the provided class. If there is no mapping for the provided
* class in the class-strategy map, or the value of the parameter is {@code null}, an instance of
* {@link NullStrategy} is returned.
*
* @param clazz some class
* @return strategy class which is able to handle the provided type.
* @throws UnsupportedTypeException when there is no matching strategy for the given class.
*/
public static PropertyValueStrategy get(Class clazz) throws UnsupportedTypeException {
if (clazz == null) {
return INSTANCE.nullStrategy;
}
PropertyValueStrategy strategy = INSTANCE.classStrategyMap.get(clazz);
// class could be some implementation of List/Map/Set that we don't register in the class-
// strategy map, so we need to check for that.
if (strategy == null) {
if (Map.class.isAssignableFrom(clazz)) {
strategy = INSTANCE.classStrategyMap.get(Map.class);
} else if (Set.class.isAssignableFrom(clazz)) {
strategy = INSTANCE.classStrategyMap.get(Set.class);
} else if (List.class.isAssignableFrom(clazz)) {
strategy = INSTANCE.classStrategyMap.get(List.class);
}
}
if (strategy == null) {
throw new UnsupportedTypeException("No strategy for class " + clazz);
}
return strategy;
}
/**
* Get strategy by object.
*
* @param value some object.
* @return strategy that handles operations on the provided object type. If value is {@code null},
* {@link NullStrategy} is returned.
* @throws UnsupportedTypeException when there is no matching strategy for a given object.
*/
public static PropertyValueStrategy get(Object value) throws UnsupportedTypeException {
if (value != null) {
PropertyValueStrategy strategy = get(value.getClass());
if (strategy == null) {
throw new UnsupportedTypeException("No strategy for class " + value.getClass());
}
return get(value.getClass());
}
return INSTANCE.nullStrategy;
}
/**
* Initializes class-strategy mapping.
*
* @return Map of supported class-strategy associations.
*/
private Map<Class, PropertyValueStrategy> initClassStrategyMap() {
Map<Class, PropertyValueStrategy> classMapping = new HashMap<>();
classMapping.put(Boolean.class, new BooleanStrategy());
classMapping.put(Set.class, new SetStrategy());
classMapping.put(Integer.class, new IntegerStrategy());
classMapping.put(Long.class, new LongStrategy());
classMapping.put(Float.class, new FloatStrategy());
classMapping.put(Double.class, new DoubleStrategy());
classMapping.put(Short.class, new ShortStrategy());
classMapping.put(BigDecimal.class, new BigDecimalStrategy());
classMapping.put(LocalDate.class, new DateStrategy());
classMapping.put(LocalTime.class, new TimeStrategy());
classMapping.put(LocalDateTime.class, new DateTimeStrategy());
classMapping.put(GradoopId.class, new GradoopIdStrategy());
classMapping.put(String.class, new StringStrategy());
classMapping.put(List.class, new ListStrategy());
classMapping.put(Map.class, new MapStrategy());
return Collections.unmodifiableMap(classMapping);
}
/**
* Initializes byte-strategy mapping.
*
* @return Array containing PropertyValueStrategies with their respective type byte as index.
*/
private PropertyValueStrategy[] initByteStrategyMap() {
PropertyValueStrategy[] byteMapping = new PropertyValueStrategy[classStrategyMap.size() + 1];
for (PropertyValueStrategy strategy : classStrategyMap.values()) {
byteMapping[strategy.getRawType()] = strategy;
}
byteMapping[nullStrategy.getRawType()] = nullStrategy;
return byteMapping;
}
}