Added: jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TransactionalMapWrapper.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TransactionalMapWrapper.java?view=auto&rev=555807 ============================================================================== --- jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TransactionalMapWrapper.java (added) +++ jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TransactionalMapWrapper.java Thu Jul 12 15:57:44 2007 @@ -0,0 +1,443 @@ +/* + * 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.commons.transaction.memory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.transaction.AbstractTransactionManager; +import org.apache.commons.transaction.Status; +import org.apache.commons.transaction.TransactionManager; +import org.apache.commons.transaction.TxContext; + +/** + * Wrapper that adds transactional control to all kinds of maps that implement the [EMAIL PROTECTED] Map} interface. + * This wrapper has rather weak isolation, but is simply, neven blocks and commits will never fail for logical + * reasons. + * <br> + * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then perform the normal actions on the map and + * finally either call [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or [EMAIL PROTECTED] #rollbackTransaction()} to + * undo them. + * <br> + * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED] #get(Object)} as this will circumvent the transactional mechanism. + * Rather clone the value or copy it in a way you see fit and store it back using [EMAIL PROTECTED] #put(Object, Object)}. + * <br> + * <em>Note:</em> This wrapper guarantees isolation level <code>READ COMMITTED</code> only. I.e. as soon a value + * is committed in one transaction it will be immediately visible in all other concurrent transactions. + * + * @version $Id: TransactionalMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $ + * @see OptimisticMapWrapper + * @see PessimisticMapWrapper + */ +public class TransactionalMapWrapper extends AbstractTransactionManager<TransactionalMapWrapper.MapTxContext> implements Map, TransactionManager { + + /** The map wrapped. */ + protected Map wrapped; + + /** + * Creates a new transactional map wrapper. Temporary maps and sets to store transactional + * data will be instances of [EMAIL PROTECTED] java.util.HashMap} and [EMAIL PROTECTED] java.util.HashSet}. + * + * @param wrapped map to be wrapped + */ + public TransactionalMapWrapper(Map wrapped) { + this.wrapped = Collections.synchronizedMap(wrapped); + } + + // + // Map methods + // + + /** + * @see Map#clear() + */ + public void clear() { + MapTxContext txContext = getActiveTx(); + if (txContext != null) { + txContext.clear(); + } else { + wrapped.clear(); + } + } + + /** + * @see Map#size() + */ + public int size() { + MapTxContext txContext = getActiveTx(); + if (txContext != null) { + return txContext.size(); + } else { + return wrapped.size(); + } + } + + /** + * @see Map#isEmpty() + */ + public boolean isEmpty() { + MapTxContext txContext = getActiveTx(); + if (txContext == null) { + return wrapped.isEmpty(); + } else { + return txContext.isEmpty(); + } + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + public boolean containsKey(Object key) { + return keySet().contains(key); + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + public boolean containsValue(Object value) { + TxContext txContext = getActiveTx(); + + if (txContext == null) { + return wrapped.containsValue(value); + } else { + return values().contains(value); + } + } + + /** + * @see Map#values() + */ + public Collection values() { + + TxContext txContext = getActiveTx(); + + if (txContext == null) { + return wrapped.values(); + } else { + // XXX expensive :( + Collection values = new ArrayList(); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + Object value = get(key); + // XXX we have no isolation, so get entry might have been deleted in the meantime + if (value != null) { + values.add(value); + } + } + return values; + } + } + + /** + * @see Map#putAll(java.util.Map) + */ + public void putAll(Map map) { + MapTxContext txContext = getActiveTx(); + + if (txContext == null) { + wrapped.putAll(map); + } else { + for (Iterator it = map.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + txContext.put(entry.getKey(), entry.getValue()); + } + } + } + + /** + * @see Map#entrySet() + */ + public Set entrySet() { + TxContext txContext = getActiveTx(); + if (txContext == null) { + return wrapped.entrySet(); + } else { + Set entrySet = new HashSet(); + // XXX expensive :( + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + Object value = get(key); + // XXX we have no isolation, so get entry might have been deleted in the meantime + if (value != null) { + entrySet.add(new HashEntry(key, value)); + } + } + return entrySet; + } + } + + /** + * @see Map#keySet() + */ + public Set keySet() { + MapTxContext txContext = getActiveTx(); + + if (txContext == null) { + return wrapped.keySet(); + } else { + return txContext.keys(); + } + } + + /** + * @see Map#get(java.lang.Object) + */ + public Object get(Object key) { + MapTxContext txContext = getActiveTx(); + + if (txContext != null) { + return txContext.get(key); + } else { + return wrapped.get(key); + } + } + + /** + * @see Map#remove(java.lang.Object) + */ + public Object remove(Object key) { + MapTxContext txContext = getActiveTx(); + + if (txContext == null) { + return wrapped.remove(key); + } else { + Object oldValue = get(key); + txContext.remove(key); + return oldValue; + } + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + public Object put(Object key, Object value) { + MapTxContext txContext = getActiveTx(); + + if (txContext == null) { + return wrapped.put(key, value); + } else { + Object oldValue = get(key); + txContext.put(key, value); + return oldValue; + } + + } + + // mostly copied from org.apache.commons.collections.map.AbstractHashedMap + protected static class HashEntry implements Map.Entry { + /** The key */ + protected Object key; + /** The value */ + protected Object value; + + protected HashEntry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object value) { + Object old = this.value; + this.value = value; + return old; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Map.Entry)) { + return false; + } + Map.Entry other = (Map.Entry) obj; + return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) + && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); + } + + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); + } + + public String toString() { + return new StringBuffer().append(getKey()).append('=').append(getValue()).toString(); + } + } + + public class MapTxContext implements TxContext { + protected Set deletes; + protected Map changes; + protected Map adds; + protected Status status; + protected boolean cleared; + protected boolean readOnly; + + protected MapTxContext() { + deletes = new HashSet(); + changes = new HashMap(); + adds = new HashMap(); + status = Status.ACTIVE; + cleared = false; + readOnly = true; + } + + protected Set keys() { + Set keySet = new HashSet(); + if (!cleared) { + keySet.addAll(wrapped.keySet()); + keySet.removeAll(deletes); + } + keySet.addAll(adds.keySet()); + return keySet; + } + + protected Object get(Object key) { + + if (deletes.contains(key)) { + // reflects that entry has been deleted in this tx + return null; + } + + if(changes.containsKey(key)){ + return changes.get(key); + } + + if(adds.containsKey(key)){ + return adds.get(key); + } + + if (cleared) { + return null; + } else { + // not modified in this tx + return wrapped.get(key); + } + } + + protected void put(Object key, Object value) { + try { + readOnly = false; + deletes.remove(key); + if (wrapped.containsKey(key)) { + changes.put(key, value); + } else { + adds.put(key, value); + } + } catch (RuntimeException e) { + status = Status.MARKED_ROLLBACK; + throw e; + } catch (Error e) { + status = Status.MARKED_ROLLBACK; + throw e; + } + } + + protected void remove(Object key) { + + try { + readOnly = false; + changes.remove(key); + adds.remove(key); + if (wrapped.containsKey(key) && !cleared) { + deletes.add(key); + } + } catch (RuntimeException e) { + status = Status.MARKED_ROLLBACK; + throw e; + } catch (Error e) { + status = Status.MARKED_ROLLBACK; + throw e; + } + } + + protected int size() { + int size = (cleared ? 0 : wrapped.size()); + + size -= deletes.size(); + size += adds.size(); + + return size; + } + + protected void clear() { + readOnly = false; + cleared = true; + deletes.clear(); + changes.clear(); + adds.clear(); + } + + protected boolean isEmpty() { + return (size() == 0); + } + + public void commit() { + if (!readOnly) { + + if (cleared) { + wrapped.clear(); + } + + wrapped.putAll(changes); + wrapped.putAll(adds); + + for (Iterator it = deletes.iterator(); it.hasNext();) { + Object key = it.next(); + wrapped.remove(key); + } + } + } + + public void dispose() { + status = Status.NO_TRANSACTION; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public boolean isReadOnly() { + return readOnly; + } + + public boolean prepare() { + // we have nothing to prepare + return true; + } + } + + @Override + protected MapTxContext createContext() { + return new MapTxContext(); + } +}
Copied: jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/ComboInputStreamMulticaster.java (from r555614, jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/ComboInputStreamMulticaster.java) URL: http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/ComboInputStreamMulticaster.java?view=diff&rev=555807&p1=jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/ComboInputStreamMulticaster.java&r1=555614&p2=jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/ComboInputStreamMulticaster.java&r2=555807 ============================================================================== --- jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/ComboInputStreamMulticaster.java (original) +++ jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/ComboInputStreamMulticaster.java Thu Jul 12 15:57:44 2007 @@ -1,4 +1,4 @@ -package org.apache.commons.transaction.file; +package org.apache.commons.transaction.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; Modified: jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/FileHelper.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/FileHelper.java?view=diff&rev=555807&r1=555806&r2=555807 ============================================================================== --- jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/FileHelper.java (original) +++ jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/FileHelper.java Thu Jul 12 15:57:44 2007 @@ -22,23 +22,26 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.channels.FileChannel; /** - * Helper methods for file manipulation. - * All methods are <em>thread safe</em>. + * Helper methods for file manipulation. All methods are <em>thread safe</em>. * * @version $Id: FileHelper.java 493628 2007-01-07 01:42:48Z joerg $ */ public final class FileHelper { private static int BUF_SIZE = 50000; + private static byte[] BUF = new byte[BUF_SIZE]; /** * Deletes a file specified by a path. - * - * @param path path of file to be deleted - * @return <code>true</code> if file has been deleted, <code>false</code> otherwise + * + * @param path + * path of file to be deleted + * @return <code>true</code> if file has been deleted, <code>false</code> + * otherwise */ public static boolean deleteFile(String path) { File file = new File(path); @@ -47,9 +50,11 @@ /** * Checks if a file specified by a path exits. - * - * @param path path of file to be checked - * @return <code>true</code> if file exists, <code>false</code> otherwise + * + * @param path + * path of file to be checked + * @return <code>true</code> if file exists, <code>false</code> + * otherwise */ public static boolean fileExists(String path) { File file = new File(path); @@ -57,12 +62,15 @@ } /** - * Creates a file specified by a path. All necessary directories will be created. + * Creates a file specified by a path. All necessary directories will be + * created. * - * @param path path of file to be created - * @return <code>true</code> if file has been created, <code>false</code> if the file already exists - * @throws IOException - * If an I/O error occurred + * @param path + * path of file to be created + * @return <code>true</code> if file has been created, <code>false</code> + * if the file already exists + * @throws IOException + * If an I/O error occurred */ public static boolean createFile(String path) throws IOException { File file = new File(path); @@ -70,17 +78,19 @@ return file.mkdirs(); } else { File dir = file.getParentFile(); - // do not check if this worked, as it may also return false, when all neccessary dirs are present + // do not check if this worked, as it may also return false, when + // all neccessary dirs are present dir.mkdirs(); return file.createNewFile(); } } /** - * Removes a file. If the specified file is a directory all contained files will - * be removed recursively as well. + * Removes a file. If the specified file is a directory all contained files + * will be removed recursively as well. * - * @param toRemove file to be removed + * @param toRemove + * file to be removed */ public static void removeRec(File toRemove) { if (toRemove.isDirectory()) { @@ -95,9 +105,12 @@ /** * Moves one directory or file to another. Existing files will be replaced. * - * @param source file to move from - * @param target file to move to - * @throws IOException if an I/O error occurs (may result in partially done work) + * @param source + * file to move from + * @param target + * file to move to + * @throws IOException + * if an I/O error occurs (may result in partially done work) */ public static void moveRec(File source, File target) throws IOException { byte[] sharedBuffer = new byte[BUF_SIZE]; @@ -142,13 +155,48 @@ } } } + public static void copyUsingNIO(File sourceFile, File destinationFile) throws IOException { + // try again using NIO copy + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(sourceFile); + fos = new FileOutputStream(destinationFile); + FileChannel srcChannel = fis.getChannel(); + FileChannel dstChannel = fos.getChannel(); + dstChannel.transferFrom(srcChannel, 0, srcChannel.size()); + srcChannel.close(); + dstChannel.close(); + } finally { + try { + fis.close(); + } finally { + fos.close(); + } + } + } + + public static boolean moveFileUsingNIO(File sourceFile, File destinationFile) throws IOException { + // try fast file-system-level move/rename first + boolean success = sourceFile.renameTo(destinationFile); + + if (!success) { + copyUsingNIO(sourceFile, destinationFile); + success = sourceFile.delete(); + } + + return success; + } /** * Copies one directory or file to another. Existing files will be replaced. * - * @param source directory or file to copy from - * @param target directory or file to copy to - * @throws IOException if an I/O error occurs (may result in partially done work) + * @param source + * directory or file to copy from + * @param target + * directory or file to copy to + * @throws IOException + * if an I/O error occurs (may result in partially done work) */ public static void copyRec(File source, File target) throws IOException { byte[] sharedBuffer = new byte[BUF_SIZE]; @@ -181,7 +229,7 @@ if (!target.isDirectory()) { if (!target.exists()) { File dir = target.getParentFile(); - if(!dir.exists() && !dir.mkdirs()) { + if (!dir.exists() && !dir.mkdirs()) { throw new IOException("Could not create target directory: " + dir); } if (!target.createNewFile()) { @@ -223,11 +271,15 @@ /** * Copies one file to another using the supplied buffer. * - * @param input source file - * @param output destination file - * @param copyBuffer buffer used for copying + * @param input + * source file + * @param output + * destination file + * @param copyBuffer + * buffer used for copying * @return the number of bytes copied - * @throws IOException if an I/O error occurs (may result in partially done work) + * @throws IOException + * if an I/O error occurs (may result in partially done work) * @see #copy(InputStream, OutputStream) */ public static long copy(File input, File output, byte[] copyBuffer) throws IOException { @@ -254,12 +306,16 @@ } /** - * Copies an <code>InputStream</code> to a file using [EMAIL PROTECTED] #copy(InputStream, OutputStream)}. + * Copies an <code>InputStream</code> to a file using + * [EMAIL PROTECTED] #copy(InputStream, OutputStream)}. * - * @param in stream to copy from - * @param outputFile file to copy to + * @param in + * stream to copy from + * @param outputFile + * file to copy to * @return the number of bytes copied - * @throws IOException if an I/O error occurs (may result in partially done work) + * @throws IOException + * if an I/O error occurs (may result in partially done work) * @see #copy(InputStream, OutputStream) */ public static long copy(InputStream in, File outputFile) throws IOException { @@ -278,14 +334,19 @@ } /** - * Copies an <code>InputStream</code> to an <code>OutputStream</code> using a local internal buffer for performance. - * Compared to [EMAIL PROTECTED] #globalBufferCopy(InputStream, OutputStream)} this method allows for better - * concurrency, but each time it is called generates a buffer which will be garbage. + * Copies an <code>InputStream</code> to an <code>OutputStream</code> + * using a local internal buffer for performance. Compared to + * [EMAIL PROTECTED] #globalBufferCopy(InputStream, OutputStream)} this method allows + * for better concurrency, but each time it is called generates a buffer + * which will be garbage. * - * @param in stream to copy from - * @param out stream to copy to + * @param in + * stream to copy from + * @param out + * stream to copy to * @return the number of bytes copied - * @throws IOException if an I/O error occurs (may result in partially done work) + * @throws IOException + * if an I/O error occurs (may result in partially done work) * @see #globalBufferCopy(InputStream, OutputStream) */ public static long copy(InputStream in, OutputStream out) throws IOException { @@ -295,14 +356,18 @@ } /** - * Copies an <code>InputStream</code> to an <code>OutputStream</code> using a global internal buffer for performance. - * Compared to [EMAIL PROTECTED] #copy(InputStream, OutputStream)} this method generated no garbage, - * but decreases concurrency. + * Copies an <code>InputStream</code> to an <code>OutputStream</code> + * using a global internal buffer for performance. Compared to + * [EMAIL PROTECTED] #copy(InputStream, OutputStream)} this method generated no + * garbage, but decreases concurrency. * - * @param in stream to copy from - * @param out stream to copy to + * @param in + * stream to copy from + * @param out + * stream to copy to * @return the number of bytes copied - * @throws IOException if an I/O error occurs (may result in partially done work) + * @throws IOException + * if an I/O error occurs (may result in partially done work) * @see #copy(InputStream, OutputStream) */ public static long globalBufferCopy(InputStream in, OutputStream out) throws IOException { @@ -312,13 +377,18 @@ } /** - * Copies an <code>InputStream</code> to an <code>OutputStream</code> using the specified buffer. + * Copies an <code>InputStream</code> to an <code>OutputStream</code> + * using the specified buffer. * - * @param in stream to copy from - * @param out stream to copy to - * @param copyBuffer buffer used for copying + * @param in + * stream to copy from + * @param out + * stream to copy to + * @param copyBuffer + * buffer used for copying * @return the number of bytes copied - * @throws IOException if an I/O error occurs (may result in partially done work) + * @throws IOException + * if an I/O error occurs (may result in partially done work) * @see #globalBufferCopy(InputStream, OutputStream) * @see #copy(InputStream, OutputStream) */ Copied: jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/InputStreamMulticaster.java (from r555614, jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/InputStreamMulticaster.java) URL: http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/InputStreamMulticaster.java?view=diff&rev=555807&p1=jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/InputStreamMulticaster.java&r1=555614&p2=jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/InputStreamMulticaster.java&r2=555807 ============================================================================== --- jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/InputStreamMulticaster.java (original) +++ jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/util/InputStreamMulticaster.java Thu Jul 12 15:57:44 2007 @@ -1,4 +1,4 @@ -package org.apache.commons.transaction.file; +package org.apache.commons.transaction.util; import java.io.IOException; import java.io.InputStream; Modified: jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/ComboInputStreamMulticasterTest.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/ComboInputStreamMulticasterTest.java?view=diff&rev=555807&r1=555806&r2=555807 ============================================================================== --- jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/ComboInputStreamMulticasterTest.java (original) +++ jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/ComboInputStreamMulticasterTest.java Thu Jul 12 15:57:44 2007 @@ -6,7 +6,7 @@ import junit.framework.JUnit4TestAdapter; -import org.apache.commons.transaction.file.ComboInputStreamMulticaster; +import org.apache.commons.transaction.util.ComboInputStreamMulticaster; import org.junit.After; import org.junit.Before; import org.junit.Test; --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]