/* | |
* Copyright 2011 gitblit.com. | |
* | |
* 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.gitblit.utils; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.ObjectInputStream; | |
import java.io.ObjectOutputStream; | |
import java.io.PipedInputStream; | |
import java.io.PipedOutputStream; | |
public class DeepCopier { | |
/** | |
* Produce a deep copy of the given object. Serializes the entire object to | |
* a byte array in memory. Recommended for relatively small objects. | |
*/ | |
@SuppressWarnings("unchecked") | |
public static <T> T copy(T original) { | |
T o = null; | |
try { | |
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(byteOut); | |
oos.writeObject(original); | |
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); | |
ObjectInputStream ois = new ObjectInputStream(byteIn); | |
try { | |
o = (T) ois.readObject(); | |
} catch (ClassNotFoundException cex) { | |
// actually can not happen in this instance | |
} | |
} catch (IOException iox) { | |
// doesn't seem likely to happen as these streams are in memory | |
throw new RuntimeException(iox); | |
} | |
return o; | |
} | |
/** | |
* This conserves heap memory!!!!! Produce a deep copy of the given object. | |
* Serializes the object through a pipe between two threads. Recommended for | |
* very large objects. The current thread is used for serializing the | |
* original object in order to respect any synchronization the caller may | |
* have around it, and a new thread is used for deserializing the copy. | |
* | |
*/ | |
public static <T> T copyParallel(T original) { | |
try { | |
PipedOutputStream outputStream = new PipedOutputStream(); | |
PipedInputStream inputStream = new PipedInputStream(outputStream); | |
ObjectOutputStream ois = new ObjectOutputStream(outputStream); | |
Receiver<T> receiver = new Receiver<T>(inputStream); | |
try { | |
ois.writeObject(original); | |
} finally { | |
ois.close(); | |
} | |
return receiver.getResult(); | |
} catch (IOException iox) { | |
// doesn't seem likely to happen as these streams are in memory | |
throw new RuntimeException(iox); | |
} | |
} | |
private static class Receiver<T> extends Thread { | |
private final InputStream inputStream; | |
private volatile T result; | |
private volatile Throwable throwable; | |
public Receiver(InputStream inputStream) { | |
this.inputStream = inputStream; | |
start(); | |
} | |
@Override | |
@SuppressWarnings("unchecked") | |
public void run() { | |
try { | |
ObjectInputStream ois = new ObjectInputStream(inputStream); | |
try { | |
result = (T) ois.readObject(); | |
try { | |
// Some serializers may write more than they actually | |
// need to deserialize the object, but if we don't | |
// read it all the PipedOutputStream will choke. | |
while (inputStream.read() != -1) { | |
} | |
} catch (IOException e) { | |
// The object has been successfully deserialized, so | |
// ignore problems at this point (for example, the | |
// serializer may have explicitly closed the inputStream | |
// itself, causing this read to fail). | |
} | |
} finally { | |
ois.close(); | |
} | |
} catch (Throwable t) { | |
throwable = t; | |
} | |
} | |
public T getResult() throws IOException { | |
try { | |
join(); | |
} catch (InterruptedException e) { | |
throw new RuntimeException("Unexpected InterruptedException", e); | |
} | |
// join() guarantees that all shared memory is synchronized between | |
// the two threads | |
if (throwable != null) { | |
if (throwable instanceof ClassNotFoundException) { | |
// actually can not happen in this instance | |
} | |
throw new RuntimeException(throwable); | |
} | |
return result; | |
} | |
} | |
} |