blob: 822d09dedf2e4772fa83302acf5f84cce0f012d2 [file] [log] [blame]
/*
* 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 org.apache.geode;
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.UUID;
import org.apache.geode.internal.HeapDataOutputStream;
import org.apache.geode.internal.Version;
import org.apache.geode.internal.cache.CachedDeserializable;
import org.apache.geode.internal.cache.Token;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.pdx.WritablePdxInstance;
import org.apache.geode.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 <code>Entries</code>.
*
* <p>
* Here is a simple example of how to use <code>CopyHelper.copy</code>
*
* <pre>
* 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);
* </pre>
*
* @see Cloneable
* @see Serializable
* @see DataSerializer
* @see org.apache.geode.cache.Cache#setCopyOnRead
* @see org.apache.geode.cache.CacheTransactionManager
*
* @since GemFire 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:
* <ul>
* <li>String
* <li>Byte
* <li>Character
* <li>Short
* <li>Integer
* <li>Long
* <li>Float
* <li>Double
* <li>BigInteger
* <li>BigDecimal
* <li>UUID
* <li>PdxInstance but not WritablePdxInstance
* </ul>
*
* @param o the object to check
* @return true if o is an instance of a well known immutable class.
* @since GemFire 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;
}
/**
* <p>
* 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.
*
* <p>
* Copies can only be made if the original is a <tt>Cloneable</tt> or serializable by GemFire. If
* o is a {@link #isWellKnownImmutableInstance(Object) well known immutable instance} then it will
* be returned without copying it.
*
* <p>
* If the argument o is an instance of {@link java.lang.Cloneable}, a copy is made by invoking
* <tt>clone</tt> on it. Note that not all implementations of <tt>clone</tt> make deep copies
* (e.g. {@link java.util.HashMap#clone HashMap.clone}). Otherwise, if the argument is not an
* instance of <tt>Cloneable</tt>, a copy is made using serialization: if GemFire serialization is
* implemented, it is used; otherwise, java serialization is used.
*
* <p>
* The difference between this method and {@link #deepCopy(Object) deepCopy}, is that this method
* uses <tt>clone</tt> if available, whereas <tt>deepCopy</tt> does not. As a result, for
* <tt>Cloneable</tt> objects copied using this method, how deep a copy the returned object is
* depends on its implementation of <tt>clone</tt>.
*
* @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 GemFire 4.0
*/
@SuppressWarnings("unchecked")
public static <T> 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 object 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 | IllegalAccessException | SecurityException ignore) {
// try using Serialization
} catch (InvocationTargetException ex) {
Throwable cause = ex.getTargetException();
if (cause instanceof CloneNotSupportedException) {
// try using Serialization
} else {
throw new CopyException("Clone failed.",
cause != null ? cause : ex);
}
}
} else if (o instanceof CachedDeserializable) {
copy = (T) ((CachedDeserializable) o).getDeserializedWritableCopy(null, null);
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.
*
* <p>
* If o is a {@link #isWellKnownImmutableInstance(Object) well known immutable instance} then it
* will be returned without copying it.
*
* <p>
* 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> 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> 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(
String.format("Copy failed on instance of %s", o.getClass()),
ex);
} catch (IOException ex) {
throw new CopyException(
String.format("Copy failed on instance of %s", o.getClass()),
ex);
}
}
}