/* * Copyright 2015 Ben Manes. All Rights Reserved. * * 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 com.github.benmanes.caffeine.jcache.copy; import static java.util.Objects.requireNonNull; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.net.URI; import java.net.URL; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Function; import java.util.regex.Pattern; /** * A skeleton implementation where subclasses provide the serialization strategy. Serialization is * not performed if the type is a known immutable, an array of known immutable types, or specially * handled by a known cloning strategy. * * @author ben.manes@gmail.com (Ben Manes) */ public abstract class AbstractCopier<A> implements Copier { private static final Map<Class<?>, Function<Object, Object>> JAVA_DEEP_COPY; private static final Set<Class<?>> JAVA_IMMUTABLE; static { JAVA_IMMUTABLE = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(Boolean.class, Byte.class, Character.class, Double.class, Float.class, Short.class, Integer.class, Long.class, BigInteger.class, BigDecimal.class, String.class, Class.class, UUID.class, URL.class, URI.class, Pattern.class, Inet4Address.class, Inet6Address.class, InetSocketAddress.class, LocalDate.class, LocalTime.class, LocalDateTime.class, Instant.class, Duration.class))); Map<Class<?>, Function<Object, Object>> strategies = new HashMap<>(); strategies.put(Calendar.class, o -> ((Calendar) o).clone()); strategies.put(Date.class, o -> ((Date) o).clone()); JAVA_DEEP_COPY = Collections.unmodifiableMap(strategies); } private final Set<Class<?>> immutableClasses; private final Map<Class<?>, Function<Object, Object>> deepCopyStrategies; public AbstractCopier() { this(javaImmutableClasses(), javaDeepCopyStrategies()); } public AbstractCopier(Set<Class<?>> immutableClasses, Map<Class<?>, Function<Object, Object>> deepCopyStrategies) { this.immutableClasses = requireNonNull(immutableClasses); this.deepCopyStrategies = requireNonNull(deepCopyStrategies); } /** @return the set of Java native classes that are immutable */ public static Set<Class<?>> javaImmutableClasses() { return JAVA_IMMUTABLE; } /** @return the set of Java native classes that are deeply copied. */ public static Map<Class<?>, Function<Object, Object>> javaDeepCopyStrategies() { return JAVA_DEEP_COPY; } @Override public <T> T copy(T object, ClassLoader classLoader) { requireNonNull(object); requireNonNull(classLoader); if (isImmutable(object.getClass())) { return object; } else if (canDeeplyCopy(object.getClass())) { return deepCopy(object); } else if (isArrayOfImmutableTypes(object.getClass())) { return arrayCopy(object); } return roundtrip(object, classLoader); } /** * Returns if the class is an immutable type and does not need to be copied. * * @param clazz the class of the object being copied * @return if the class is an immutable type and does not need to be copied */ protected boolean isImmutable(Class<?> clazz) { return immutableClasses.contains(clazz) || clazz.isEnum(); } /** * Returns if the class has a known deep copy strategy. * * @param clazz the class of the object being copied * @return if the class has a known deep copy strategy */ protected boolean canDeeplyCopy(Class<?> clazz) { return deepCopyStrategies.containsKey(clazz); } /** @return if the class represents an array of immutable values. */ private boolean isArrayOfImmutableTypes(Class<?> clazz) { if (!clazz.isArray()) { return false; } Class<?> component = clazz.getComponentType(); return component.isPrimitive() || isImmutable(component); } /** @return a shallow copy of the array. */ private <T> T arrayCopy(T object) { int length = Array.getLength(object); @SuppressWarnings("unchecked") T copy = (T) Array.newInstance(object.getClass().getComponentType(), length); System.arraycopy(object, 0, copy, 0, length); return copy; } /** @return a deep copy of the object. */ private <T> T deepCopy(T object) { @SuppressWarnings("unchecked") T copy = (T) deepCopyStrategies.get(object.getClass()).apply(object); return copy; } /** * Performs the serialization and deserialization, returning the copied object. * * @param object the object to serialize * @param classLoader the classloader to create the instance with * @param <T> the type of object being copied * @return the deserialized object */ protected <T> T roundtrip(T object, ClassLoader classLoader) { A data = serialize(object); @SuppressWarnings("unchecked") T copy = (T) deserialize(data, classLoader); return copy; } /** * Serializes the object. * * @param object the object to serialize * @return the serialized bytes */ protected abstract A serialize(Object object); /** * Deserializes the data using the provided classloader. * * @param data the serialized bytes * @param classLoader the classloader to create the instance with * @return the deserialized object */ protected abstract Object deserialize(A data, ClassLoader classLoader); }