
com.gemstone.gemfire.CopyHelper Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.gemstone.gemfire;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.UUID;
import com.gemstone.gemfire.internal.HeapDataOutputStream;
import com.gemstone.gemfire.internal.Version;
import com.gemstone.gemfire.internal.cache.CachedDeserializable;
import com.gemstone.gemfire.internal.cache.Token;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.pdx.PdxInstance;
import com.gemstone.gemfire.pdx.WritablePdxInstance;
import com.gemstone.gemfire.pdx.internal.PdxUnreadData;
/**
* A static helper for optimally creating copies. Creating copies
* of cache values provides improved concurrency as well as isolation.
* For transactions, creating a copy is the guaranteed way to enforce
* "Read Committed" isolation on changes to cache
* Entries
.
* Here is a simple example of how to use CopyHelper.copy
*
* Object o = r.get("stringBuf");
* StringBuffer s = (StringBuffer) CopyHelper.copy(o);
* s.append("... and they lived happily ever after. The End.");
* r.put("stringBuf", s);
*
*
* @see Cloneable
* @see Serializable
* @see DataSerializer
* @see com.gemstone.gemfire.cache.Cache#setCopyOnRead
* @see com.gemstone.gemfire.cache.CacheTransactionManager
*
* @author Mitch Thomas
* @since 4.0
*/
public final class CopyHelper {
// no instances allowed
private CopyHelper() {
}
/**
* Return true if the given object is an instance of a well known
* immutable class.
* The well known classes are:
*
* - String
*
- Byte
*
- Character
*
- Short
*
- Integer
*
- Long
*
- Float
*
- Double
*
- BigInteger
*
- BigDecimal
*
- UUID
*
- PdxInstance but not WritablePdxInstance
*
* @param o the object to check
* @return true if o is an instance of a well known immutable class.
* @since 6.6.2
*/
public static boolean isWellKnownImmutableInstance(Object o) {
if (o instanceof String) {
return true;
}
if (o instanceof Number) {
if (o instanceof Integer) return true;
if (o instanceof Long) return true;
if (o instanceof Byte) return true;
if (o instanceof Short) return true;
if (o instanceof Float) return true;
if (o instanceof Double) return true;
// subclasses of non-final classes may be mutable
if (o.getClass().equals(BigInteger.class)) return true;
if (o.getClass().equals(BigDecimal.class)) return true;
}
if (o instanceof PdxInstance && !(o instanceof WritablePdxInstance)) {
// no need to copy since it is immutable
return true;
}
if (o instanceof Character) return true;
if (o instanceof UUID) return true;
return false;
}
/**
* Makes a copy of the specified object. The object returned is not guaranteed
* to be a deep copy of the original object, as explained below.
*
*
Copies can only be made if the original is a Cloneable or serializable by
* GemFire.
* If o is a {@link #isWellKnownImmutableInstance(Object) well known immutable instance}
* then it will be returned without copying it.
*
*
If the argument o is an instance of {@link java.lang.Cloneable}, a copy is
* made by invoking clone on it. Note that not all implementations of clone
* make deep copies (e.g. {@link java.util.HashMap#clone HashMap.clone}). Otherwise, if the
* argument is not an instance of Cloneable, a copy is made using serialization: if
* GemFire serialization is implemented, it is used; otherwise, java serialization is used.
*
*
The difference between this method and {@link #deepCopy(Object) deepCopy}, is that
* this method uses clone if available, whereas deepCopy does not. As a
* result, for Cloneable objects copied using this method, how deep a copy the
* returned object is depends on its implementation of clone.
*
* @param o the original object that a copy is needed of
* @return the new instance that is a copy of of the original
* @throws CopyException if copying fails because a class could not
* be found or could not be serialized.
* @see #deepCopy(Object)
* @since 4.0
*/
@SuppressWarnings("unchecked")
public static T copy(T o) {
T copy = null;
try {
if (o == null) {
return null;
} else if (o instanceof Token) {
return o;
} else {
if (isWellKnownImmutableInstance(o)) return o;
if (o instanceof Cloneable) {
try {
// Note that Object.clone is protected so we need to use reflection
// to call clone even though this guy implements Cloneable
Class> c = o.getClass();
// By convention, the user should make the clone method public.
// But even if they don't, let's go ahead and use it.
// The other problem is that if the class is private, we still
// need to make the method accessible even if the method is public,
// because Object.clone is protected.
Method m = c.getDeclaredMethod("clone", new Class[0]);
m.setAccessible(true);
copy = (T)m.invoke(o, new Object[0]);
return copy;
} catch (NoSuchMethodException ignore) {
// try using Serialization
} catch (IllegalAccessException ignore) {
// try using Serialization
} catch (SecurityException ignore) {
// try using Serialization
} catch (InvocationTargetException ex) {
Throwable cause = ex.getTargetException();
if (cause instanceof CloneNotSupportedException) {
// try using Serialization
} else {
throw new CopyException(LocalizedStrings.CopyHelper_CLONE_FAILED.toLocalizedString(), cause != null ? cause : ex);
}
}
} else if (o instanceof CachedDeserializable) {
copy = (T) ((CachedDeserializable) o).getDeserializedWritableCopy(null, null);
return copy;
} else if (o.getClass().isArray() && o.getClass().getComponentType().isPrimitive()) {
if (o instanceof byte[]) {
byte[] a = (byte[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof boolean[]) {
boolean[] a = (boolean[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof char[]) {
char[] a = (char[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof int[]) {
int[] a = (int[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof long[]) {
long[] a = (long[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof short[]) {
short[] a = (short[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof float[]) {
float[] a = (float[])o;
copy = (T) Arrays.copyOf(a, a.length);
} else if (o instanceof double[]) {
double[] a = (double[])o;
copy = (T) Arrays.copyOf(a, a.length);
}
return copy;
}
// Copy using serialization
copy = doDeepCopy(o);
return copy;
}
} finally {
if (copy != null) {
PdxUnreadData.copy(o, copy);
}
}
}
/**
* Makes a deep copy of the specified object o using serialization, so the object
* has to be serializable by GemFire.
*
* If o is a {@link #isWellKnownImmutableInstance(Object) well known immutable
* instance} then it will be returned without copying it.
*
*
The passed in object is serialized in memory, and then deserialized into
* a new instance, which is returned. If GemFire serialization is implemented
* for the object, it is used; otherwise, java serialization is used.
*
* @param o the original object to be copied
* @return the new instance that is a copy of the original
* @throws CopyException if copying fails because a class could not
* be found or could not be serialized
* @see #copy(Object)
*/
public static T deepCopy(T o) {
T copy = null;
try {
if (o == null) {
return null;
} else if (o instanceof Token || isWellKnownImmutableInstance(o)) {
return o;
} else {
copy = doDeepCopy(o);
return copy;
}
} finally {
if (copy != null) {
PdxUnreadData.copy(o, copy);
}
}
}
@SuppressWarnings("unchecked")
private static T doDeepCopy(T o) {
try {
HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT);
DataSerializer.writeObject(o, hdos);
return (T)DataSerializer.readObject(new DataInputStream(hdos.getInputStream()));
} catch (ClassNotFoundException ex) {
throw new CopyException(LocalizedStrings.CopyHelper_COPY_FAILED_ON_INSTANCE_OF_0.toLocalizedString(o.getClass()), ex);
} catch (IOException ex) {
throw new CopyException(LocalizedStrings.CopyHelper_COPY_FAILED_ON_INSTANCE_OF_0.toLocalizedString(o.getClass()), ex);
}
}
}