From 8809341248c62b15b78d7e6d8e06ab2ec3793c8e Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 28 Mar 2007 22:00:24 +0000 Subject: Merged 1.2-dev to trunk. --- .../amazon/carbonado/spi/AbstractRepository.java | 368 +++++++++++++++++++++ .../spi/AbstractSequenceValueProducer.java | 79 ----- .../carbonado/spi/BelatedStorageCreator.java | 4 + .../com/amazon/carbonado/spi/CodeBuilderUtil.java | 172 +++++++++- .../java/com/amazon/carbonado/spi/LobEngine.java | 31 +- .../carbonado/spi/MasterStorableGenerator.java | 57 +--- .../com/amazon/carbonado/spi/MasterSupport.java | 1 + .../carbonado/spi/SequenceValueGenerator.java | 290 ---------------- .../carbonado/spi/SequenceValueProducer.java | 87 ----- .../amazon/carbonado/spi/StorableGenerator.java | 208 +++++++++--- .../com/amazon/carbonado/spi/StorableIndexSet.java | 20 ++ .../amazon/carbonado/spi/StorageCollection.java | 128 ------- .../java/com/amazon/carbonado/spi/StoragePool.java | 59 ++++ .../java/com/amazon/carbonado/spi/StoredLob.java | 3 - .../com/amazon/carbonado/spi/WrappedStorage.java | 4 + 15 files changed, 827 insertions(+), 684 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/spi/AbstractRepository.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/StorageCollection.java create mode 100644 src/main/java/com/amazon/carbonado/spi/StoragePool.java (limited to 'src/main/java/com/amazon/carbonado/spi') diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java new file mode 100644 index 0000000..d1c1843 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java @@ -0,0 +1,368 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. 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.amazon.carbonado.spi; + +import java.lang.ref.WeakReference; + +import java.util.Collection; +import java.util.Map; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.logging.Log; + +import org.cojen.util.WeakIdentityMap; + +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Transaction; + +import com.amazon.carbonado.capability.Capability; +import com.amazon.carbonado.capability.ShutdownCapability; + +import com.amazon.carbonado.sequence.SequenceCapability; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.sequence.SequenceValueProducerPool; + +/** + * Implements basic functionality required by a core Repository. + * + * @param Transaction type + * @author Brian S O'Neill + */ +public abstract class AbstractRepository + implements Repository, ShutdownCapability, SequenceCapability +{ + private final String mName; + private final ReadWriteLock mShutdownLock; + + private final ThreadLocal> mCurrentTxnMgr; + + // Weakly tracks all TransactionManager instances for shutdown hook. + private final Map, ?> mAllTxnMgrs; + + private final StoragePool mStoragePool; + + private final SequenceValueProducerPool mSequencePool; + + private ShutdownHook mShutdownHook; + volatile boolean mHasShutdown; + + protected AbstractRepository(String name) { + if (name == null) { + throw new IllegalArgumentException("Repository name cannot be null"); + } + mName = name; + mShutdownLock = new ReentrantReadWriteLock(); + mCurrentTxnMgr = new ThreadLocal>(); + mAllTxnMgrs = new WeakIdentityMap(); + + mStoragePool = new StoragePool() { + protected Storage createStorage(Class type) + throws RepositoryException + { + AbstractRepository.this.lockoutShutdown(); + try { + return AbstractRepository.this.createStorage(type); + } finally { + AbstractRepository.this.unlockoutShutdown(); + } + } + }; + + mSequencePool = new SequenceValueProducerPool() { + protected SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException + { + AbstractRepository.this.lockoutShutdown(); + try { + return AbstractRepository.this.createSequenceValueProducer(name); + } finally { + AbstractRepository.this.unlockoutShutdown(); + } + } + }; + } + + public String getName() { + return mName; + } + + public Storage storageFor(Class type) + throws SupportException, RepositoryException + { + return mStoragePool.get(type); + } + + public Transaction enterTransaction() { + return localTransactionManager().enter(null); + } + + public Transaction enterTransaction(IsolationLevel level) { + return localTransactionManager().enter(level); + } + + public Transaction enterTopTransaction(IsolationLevel level) { + return localTransactionManager().enterTop(level); + } + + public IsolationLevel getTransactionIsolationLevel() { + return localTransactionManager().getIsolationLevel(); + } + + /** + * Default implementation checks if Repository implements Capability + * interface, and if so, returns the Repository. + */ + @SuppressWarnings("unchecked") + public C getCapability(Class capabilityType) { + if (capabilityType.isInstance(this)) { + return (C) this; + } + return null; + } + + public void close() { + shutdown(false); + } + + // Required by ShutdownCapability. + public boolean isAutoShutdownEnabled() { + return mShutdownHook != null; + } + + // Required by ShutdownCapability. + public void setAutoShutdownEnabled(boolean enabled) { + if (mShutdownHook == null) { + if (enabled) { + mShutdownHook = new ShutdownHook(this); + try { + Runtime.getRuntime().addShutdownHook(mShutdownHook); + } catch (IllegalStateException e) { + // Shutdown in progress, so immediately run hook. + mShutdownHook.run(); + } + } + } else { + if (!enabled) { + try { + Runtime.getRuntime().removeShutdownHook(mShutdownHook); + } catch (IllegalStateException e) { + // Shutdown in progress, hook is running. + } + mShutdownHook = null; + } + } + } + + // Required by ShutdownCapability. + public void shutdown() { + shutdown(true); + } + + private void shutdown(boolean suspendThreads) { + if (!mHasShutdown) { + // Since this repository is being closed before system shutdown, + // remove shutdown hook and run it now. + ShutdownHook hook = mShutdownHook; + if (hook != null) { + try { + Runtime.getRuntime().removeShutdownHook(hook); + } catch (IllegalStateException e) { + // Shutdown in progress, hook is running. + hook = null; + } + } else { + // If hook is null, auto-shutdown was disabled. Make a new + // instance to use, but don't register it. + hook = new ShutdownHook(this); + } + if (hook != null) { + hook.run(suspendThreads); + } + mHasShutdown = true; + } + } + + // Required by SequenceCapability. + public SequenceValueProducer getSequenceValueProducer(String name) throws RepositoryException { + return mSequencePool.get(name); + } + + /** + * Returns the thread-local TransactionManager, creating it if needed. + */ + protected TransactionManager localTransactionManager() { + TransactionManager txnMgr = mCurrentTxnMgr.get(); + if (txnMgr == null) { + lockoutShutdown(); + try { + txnMgr = createTransactionManager(); + mCurrentTxnMgr.set(txnMgr); + mAllTxnMgrs.put(txnMgr, null); + } finally { + unlockoutShutdown(); + } + } + return txnMgr; + } + + /** + * Call to prevent shutdown hook from running. Be sure to call + * unlockoutShutdown afterwards. + */ + protected void lockoutShutdown() { + mShutdownLock.readLock().lock(); + } + + /** + * Only call this to release lockoutShutdown. + */ + protected void unlockoutShutdown() { + mShutdownLock.readLock().unlock(); + } + + /** + * Only to be called by shutdown hook itself. + */ + void lockForShutdown() { + mShutdownLock.writeLock().lock(); + } + + /** + * Only to be called by shutdown hook itself. + */ + void unlockForShutdown() { + mShutdownLock.writeLock().unlock(); + } + + /** + * Returns all available Storage instances. + */ + protected Collection allStorage() { + return mStoragePool.values(); + } + + /** + * Install custom shutdown logic by overriding this method. By default it + * does nothing. + */ + protected void shutdownHook() { + } + + /** + * Return the main Log object for this Repository. If none provided, then + * no messages are logged by AbstractRepository. + */ + protected abstract Log getLog(); + + /** + * Called upon to create a new thread-local TransactionManager instance. + */ + protected abstract TransactionManager createTransactionManager(); + + /** + * Called upon to create a new Storage instance. + */ + protected abstract Storage createStorage(Class type) + throws RepositoryException; + + /** + * Called upon to create a new SequenceValueProducer instance. + */ + protected abstract SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException; + + void info(String message) { + Log log = getLog(); + if (log != null) { + log.info(message); + } + } + + void error(String message, Throwable e) { + Log log = getLog(); + if (log != null) { + log.error(message, e); + } + } + + private static class ShutdownHook extends Thread { + private final WeakReference> mRepository; + + ShutdownHook(AbstractRepository repository) { + super(repository.getClass().getSimpleName() + " shutdown (" + + repository.getName() + ')'); + mRepository = new WeakReference>(repository); + } + + public void run() { + run(true); + } + + public void run(boolean suspendThreads) { + AbstractRepository repository = mRepository.get(); + if (repository == null) { + return; + } + + repository.info("Closing repository \"" + repository.getName() + '"'); + + try { + doShutdown(repository, suspendThreads); + } finally { + repository.mHasShutdown = true; + mRepository.clear(); + repository.info("Finished closing repository \"" + repository.getName() + '"'); + } + } + + private void doShutdown(AbstractRepository repository, boolean suspendThreads) { + repository.lockForShutdown(); + try { + // Return unused sequence values. + repository.mSequencePool.returnReservedValues(null); + + // Close transactions and cursors. + for (TransactionManager txnMgr : repository.mAllTxnMgrs.keySet()) { + if (suspendThreads) { + // Lock transaction manager but don't release it. This + // prevents other threads from beginning work during + // shutdown, which will likely fail along the way. + txnMgr.getLock().lock(); + } + try { + txnMgr.close(); + } catch (Throwable e) { + repository.error("Failed to close TransactionManager", e); + } + } + + repository.shutdownHook(); + } finally { + repository.unlockForShutdown(); + } + } + } +} diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java b/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java deleted file mode 100644 index 5badf0f..0000000 --- a/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. 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.amazon.carbonado.spi; - -import java.math.BigInteger; - -import com.amazon.carbonado.PersistException; - -/** - * - * - * @author Brian S O'Neill - */ -public abstract class AbstractSequenceValueProducer implements SequenceValueProducer { - protected AbstractSequenceValueProducer() { - } - - public int nextIntValue() throws PersistException { - return (int) nextLongValue(); - } - - public String nextDecimalValue() throws PersistException { - return nextNumericalValue(10, 0); - } - - public String nextNumericalValue(int radix, int minLength) throws PersistException { - long next = nextLongValue(); - String str; - - if (next >= 0) { - str = Long.toString(next, radix); - } else { - // Use BigInteger to print negative values as positive by expanding - // precision to 72 bits - - byte[] bytes = new byte[9]; - bytes[8] = (byte) (next & 0xff); - bytes[7] = (byte) ((next >>= 8) & 0xff); - bytes[6] = (byte) ((next >>= 8) & 0xff); - bytes[5] = (byte) ((next >>= 8) & 0xff); - bytes[4] = (byte) ((next >>= 8) & 0xff); - bytes[3] = (byte) ((next >>= 8) & 0xff); - bytes[2] = (byte) ((next >>= 8) & 0xff); - bytes[1] = (byte) ((next >>= 8) & 0xff); - //bytes[0] = 0; - - str = new BigInteger(bytes).toString(radix); - } - - int pad = minLength - str.length(); - - if (pad > 0) { - StringBuilder b = new StringBuilder(minLength); - while (--pad >= 0) { - b.append('0'); - } - b.append(str); - str = b.toString(); - } - - return str; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java index ed37df0..e0ea552 100644 --- a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java +++ b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java @@ -120,6 +120,10 @@ public class BelatedStorageCreator return mRepo; } + public void truncate() { + throw error(); + } + public boolean addTrigger(Trigger trigger) { throw error(); } diff --git a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java index e3b24bd..0623f94 100644 --- a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java +++ b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java @@ -21,7 +21,9 @@ import java.util.HashSet; import java.util.Set; import java.util.Map; import java.util.HashMap; + import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import org.cojen.classfile.ClassFile; import org.cojen.classfile.Modifiers; @@ -31,9 +33,11 @@ import org.cojen.classfile.Label; import org.cojen.classfile.TypeDesc; import org.cojen.classfile.LocalVariable; import org.cojen.classfile.MethodDesc; +import org.cojen.classfile.Opcode; import org.cojen.util.ClassInjector; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.SupportException; import static com.amazon.carbonado.spi.CommonMethodNames.*; @@ -133,7 +137,7 @@ public class CodeBuilderUtil { public static void defineCopyBridges(ClassFile cf, Class leaf) { for (Class c : gatherAllBridgeTypes(new HashSet(), leaf)) { if (c != Object.class) { - defineCopyBridge(cf, c); + defineCopyBridge(cf, leaf, c); } } } @@ -144,11 +148,17 @@ public class CodeBuilderUtil { * return it as the correct type. * * @param cf file to which to add the copy bridge + * @param leaf leaf class * @param returnClass type returned from generated bridge method */ - public static void defineCopyBridge(ClassFile cf, Class returnClass) { + private static void defineCopyBridge(ClassFile cf, Class leaf, Class returnClass) { TypeDesc returnType = TypeDesc.forClass(returnClass); + if (isPublicMethodFinal(leaf, COPY_METHOD_NAME, returnType, null)) { + // Cannot override. + return; + } + MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toBridge(true), COPY_METHOD_NAME, returnType, null); CodeBuilder b = new CodeBuilder(mi); @@ -185,6 +195,38 @@ public class CodeBuilderUtil { } } + /** + * Returns true if a public final method exists which matches the given + * specification. + */ + public static boolean isPublicMethodFinal(Class clazz, String name, + TypeDesc retType, TypeDesc[] params) + { + if (!clazz.isInterface()) { + Class[] paramClasses; + if (params == null || params.length == 0) { + paramClasses = null; + } else { + paramClasses = new Class[params.length]; + for (int i=0; i @@ -381,6 +423,78 @@ public class CodeBuilderUtil { } } + /** + * Generates code to push an initial version property value on the stack. + * + * @throws SupportException if version type is not supported + */ + public static void initialVersion(CodeBuilder b, TypeDesc type, int value) + throws SupportException + { + adjustVersion(b, type, value, false); + } + + /** + * Generates code to increment a version property value, already on the stack. + * + * @throws SupportException if version type is not supported + */ + public static void incrementVersion(CodeBuilder b, TypeDesc type) + throws SupportException + { + adjustVersion(b, type, 0, true); + } + + private static void adjustVersion(CodeBuilder b, TypeDesc type, int value, boolean increment) + throws SupportException + { + TypeDesc primitiveType = type.toPrimitiveType(); + supportCheck: { + if (primitiveType != null) { + switch (primitiveType.getTypeCode()) { + case TypeDesc.INT_CODE: + case TypeDesc.LONG_CODE: + break supportCheck; + } + } + throw new SupportException("Unsupported version type: " + type.getFullName()); + } + + if (!increment) { + if (primitiveType == TypeDesc.LONG) { + b.loadConstant((long) value); + } else { + b.loadConstant(value); + } + } else { + Label setVersion = b.createLabel(); + if (!type.isPrimitive()) { + b.dup(); + Label versionNotNull = b.createLabel(); + b.ifNullBranch(versionNotNull, false); + b.pop(); + if (primitiveType == TypeDesc.LONG) { + b.loadConstant(1L); + } else { + b.loadConstant(1); + } + b.branch(setVersion); + versionNotNull.setLocation(); + b.convert(type, primitiveType); + } + if (primitiveType == TypeDesc.LONG) { + b.loadConstant(1L); + b.math(Opcode.LADD); + } else { + b.loadConstant(1); + b.math(Opcode.IADD); + } + setVersion.setLocation(); + } + + b.convert(primitiveType, type); + } + /** * Determines which overloaded "with" method on Query should be bound to. */ @@ -403,4 +517,58 @@ public class CodeBuilderUtil { } return TypeDesc.OBJECT; } + + /** + * Appends a String to a StringBuilder. A StringBuilder and String must be + * on the stack, and a StringBuilder is left on the stack after the call. + */ + public static void callStringBuilderAppendString(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.STRING}); + } + + /** + * Appends a char to a StringBuilder. A StringBuilder and char must be on + * the stack, and a StringBuilder is left on the stack after the call. + */ + public static void callStringBuilderAppendChar(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.CHAR}); + } + + /** + * Calls length on a StringBuilder on the stack, leaving an int on the stack. + */ + public static void callStringBuilderLength(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "length", TypeDesc.INT, null); + } + + /** + * Calls setLength on a StringBuilder. A StringBuilder and int must be on + * the stack, and both are consumed after the call. + */ + public static void callStringBuilderSetLength(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "setLength", null, new TypeDesc[] {TypeDesc.INT}); + } + + /** + * Calls toString on a StringBuilder. A StringBuilder must be on the stack, + * and a String is left on the stack after the call. + */ + public static void callStringBuilderToString(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "toString", TypeDesc.STRING, null); + } } diff --git a/src/main/java/com/amazon/carbonado/spi/LobEngine.java b/src/main/java/com/amazon/carbonado/spi/LobEngine.java index a897cac..d91ce5e 100644 --- a/src/main/java/com/amazon/carbonado/spi/LobEngine.java +++ b/src/main/java/com/amazon/carbonado/spi/LobEngine.java @@ -55,6 +55,9 @@ import com.amazon.carbonado.lob.BlobClob; import com.amazon.carbonado.lob.Clob; import com.amazon.carbonado.lob.Lob; +import com.amazon.carbonado.sequence.SequenceValueGenerator; +import com.amazon.carbonado.sequence.SequenceValueProducer; + /** * Complete Lob support for repositories, although repository is responsible * for binding Lob properties to this engine. Lobs are referenced by locators, @@ -83,16 +86,32 @@ public class LobEngine { final Repository mRepo; final Storage mLobStorage; final Storage mLobBlockStorage; + final SequenceValueProducer mLocatorSequence; private Map mTriggers; /** - * @param repo storage for Lobs + * @param lobRepo storage for Lobs - should not be replicated + * @param locatorRepo storage for producing unique values for Lob locators + * - should be root repository + */ + public LobEngine(Repository lobRepo, Repository locatorRepo) throws RepositoryException { + // Cannot reliably use sequences provided by Lob repository, since + // LobEngine is used internally by repositories. + this(lobRepo, new SequenceValueGenerator(locatorRepo, StoredLob.class.getName())); + } + + /** + * @param lobRepo storage for Lobs - should not be replicated + * @param locatorSequenceProducer source of unique values for Lob locators */ - public LobEngine(Repository repo) throws RepositoryException { - mRepo = repo; - mLobStorage = repo.storageFor(StoredLob.class); - mLobBlockStorage = repo.storageFor(StoredLob.Block.class); + public LobEngine(Repository lobRepo, SequenceValueProducer locatorSequenceProducer) + throws RepositoryException + { + mRepo = lobRepo; + mLobStorage = lobRepo.storageFor(StoredLob.class); + mLobBlockStorage = lobRepo.storageFor(StoredLob.Block.class); + mLocatorSequence = locatorSequenceProducer; } /** @@ -103,6 +122,7 @@ public class LobEngine { */ public Blob createNewBlob(int blockSize) throws PersistException { StoredLob lob = mLobStorage.prepare(); + lob.setLocator(mLocatorSequence.nextLongValue()); lob.setBlockSize(blockSize); lob.setLength(0); lob.insert(); @@ -117,6 +137,7 @@ public class LobEngine { */ public Clob createNewClob(int blockSize) throws PersistException { StoredLob lob = mLobStorage.prepare(); + lob.setLocator(mLocatorSequence.nextLongValue()); lob.setBlockSize(blockSize); lob.setLength(0); lob.insert(); diff --git a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java index ead6c3c..3de8fb5 100644 --- a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java @@ -47,6 +47,8 @@ import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; +import com.amazon.carbonado.sequence.SequenceValueProducer; + import static com.amazon.carbonado.spi.CommonMethodNames.*; /** @@ -409,7 +411,8 @@ public final class MasterStorableGenerator { ordinal++; if (property.isJoin() || property.isPrimaryKeyMember() - || property.isNullable()) + || property.isNullable() + || property.isAutomatic() || property.isVersion()) { continue; } @@ -808,63 +811,25 @@ public final class MasterStorableGenerator { int value) throws SupportException { - StorableProperty versionProperty = mInfo.getVersionProperty(); - - TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); - TypeDesc versionPrimitiveType = versionType.toPrimitiveType(); - supportCheck: { - if (versionPrimitiveType != null) { - switch (versionPrimitiveType.getTypeCode()) { - case TypeDesc.INT_CODE: - case TypeDesc.LONG_CODE: - break supportCheck; - } - } - throw new SupportException - ("Unsupported version type: " + versionType.getFullName()); - } - + // Push storable to stack in preparation for calling set method below. if (storableVar == null) { b.loadThis(); } else { b.loadLocal(storableVar); } + StorableProperty versionProperty = mInfo.getVersionProperty(); + TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); + if (value >= 0) { - if (versionPrimitiveType == TypeDesc.LONG) { - b.loadConstant((long) value); - } else { - b.loadConstant(value); - } + CodeBuilderUtil.initialVersion(b, versionType, value); } else { + // Load current property value. b.dup(); b.invoke(versionProperty.getReadMethod()); - Label setVersion = b.createLabel(); - if (!versionType.isPrimitive()) { - b.dup(); - Label versionNotNull = b.createLabel(); - b.ifNullBranch(versionNotNull, false); - b.pop(); - if (versionPrimitiveType == TypeDesc.LONG) { - b.loadConstant(1L); - } else { - b.loadConstant(1); - } - b.branch(setVersion); - versionNotNull.setLocation(); - b.convert(versionType, versionPrimitiveType); - } - if (versionPrimitiveType == TypeDesc.LONG) { - b.loadConstant(1L); - b.math(Opcode.LADD); - } else { - b.loadConstant(1); - b.math(Opcode.IADD); - } - setVersion.setLocation(); + CodeBuilderUtil.incrementVersion(b, versionType); } - b.convert(versionPrimitiveType, versionType); b.invoke(versionProperty.getWriteMethod()); } } diff --git a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java index de1e521..dd48194 100644 --- a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java +++ b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java @@ -20,6 +20,7 @@ package com.amazon.carbonado.spi; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.sequence.SequenceValueProducer; /** * Provides runtime support for Storable classes generated by {@link MasterStorableGenerator}. diff --git a/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java b/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java deleted file mode 100644 index efe8ebd..0000000 --- a/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. 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.amazon.carbonado.spi; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Repository; -import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.Transaction; - -/** - * General purpose implementation of a sequence value generator. - * - * @author Brian S O'Neill - * @see com.amazon.carbonado.Sequence - * @see StoredSequence - */ -public class SequenceValueGenerator extends AbstractSequenceValueProducer { - private static final int DEFAULT_RESERVE_AMOUNT = 100; - private static final int DEFAULT_INITIAL_VALUE = 1; - private static final int DEFAULT_INCREMENT = 1; - - private final Repository mRepository; - private final Storage mStorage; - private final StoredSequence mStoredSequence; - private final int mIncrement; - private final int mReserveAmount; - - private boolean mHasReservedValues; - private long mNextValue; - - /** - * Construct a new SequenceValueGenerator which might create persistent - * sequence data if it does not exist. The initial sequence value is one, - * and the increment is one. - * - * @param repo repository to persist sequence data - * @param name name of sequence - */ - public SequenceValueGenerator(Repository repo, String name) - throws RepositoryException - { - this(repo, name, DEFAULT_INITIAL_VALUE, DEFAULT_INCREMENT); - } - - /** - * Construct a new SequenceValueGenerator which might create persistent - * sequence data if it does not exist. - * - * @param repo repository to persist sequence data - * @param name name of sequence - * @param initialValue initial sequence value, if sequence needs to be created - * @param increment amount to increment sequence by - */ - public SequenceValueGenerator(Repository repo, String name, long initialValue, int increment) - throws RepositoryException - { - this(repo, name, initialValue, increment, DEFAULT_RESERVE_AMOUNT); - } - - /** - * Construct a new SequenceValueGenerator which might create persistent - * sequence data if it does not exist. - * - * @param repo repository to persist sequence data - * @param name name of sequence - * @param initialValue initial sequence value, if sequence needs to be created - * @param increment amount to increment sequence by - * @param reserveAmount amount of sequence values to reserve - */ - public SequenceValueGenerator(Repository repo, String name, - long initialValue, int increment, int reserveAmount) - throws RepositoryException - { - if (repo == null || name == null || increment < 1 || reserveAmount < 1) { - throw new IllegalArgumentException(); - } - - mRepository = repo; - - mIncrement = increment; - mReserveAmount = reserveAmount; - - mStorage = repo.storageFor(StoredSequence.class); - - mStoredSequence = mStorage.prepare(); - mStoredSequence.setName(name); - - Transaction txn = repo.enterTopTransaction(null); - txn.setForUpdate(true); - try { - if (!mStoredSequence.tryLoad()) { - mStoredSequence.setInitialValue(initialValue); - // Start as small as possible to allow signed long comparisons to work. - mStoredSequence.setNextValue(Long.MIN_VALUE); - if (!mStoredSequence.tryInsert()) { - mStoredSequence.load(); - } - } - txn.commit(); - } finally { - txn.exit(); - } - } - - /** - * Reset the sequence. - * - * @param initialValue first value produced by sequence - */ - public void reset(int initialValue) throws FetchException, PersistException { - synchronized (mStoredSequence) { - Transaction txn = mRepository.enterTopTransaction(null); - txn.setForUpdate(true); - try { - boolean doUpdate = mStoredSequence.tryLoad(); - mStoredSequence.setInitialValue(initialValue); - // Start as small as possible to allow signed long comparisons to work. - mStoredSequence.setNextValue(Long.MIN_VALUE); - if (doUpdate) { - mStoredSequence.update(); - } else { - mStoredSequence.insert(); - } - txn.commit(); - mHasReservedValues = false; - } finally { - txn.exit(); - } - } - } - - /** - * Returns the next value from the sequence, which may wrap negative if all - * positive values are exhausted. When sequence wraps back to initial - * value, the sequence is fully exhausted, and an exception is thrown to - * indicate this. - * - *

Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - * - * @throws PersistException for fetch/persist failure or if sequence is exhausted. - */ - public long nextLongValue() throws PersistException { - try { - synchronized (mStoredSequence) { - return nextUnadjustedValue() + Long.MIN_VALUE + mStoredSequence.getInitialValue(); - } - } catch (FetchException e) { - throw e.toPersistException(); - } - } - - /** - * Returns the next value from the sequence, which may wrap negative if all - * positive values are exhausted. When sequence wraps back to initial - * value, the sequence is fully exhausted, and an exception is thrown to - * indicate this. - * - *

Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - * - * @throws PersistException for fetch/persist failure or if sequence is - * exhausted for int values. - */ - public int nextIntValue() throws PersistException { - try { - synchronized (mStoredSequence) { - long initial = mStoredSequence.getInitialValue(); - if (initial >= 0x100000000L) { - throw new PersistException - ("Sequence initial value too large to support 32-bit ints: " + - mStoredSequence.getName() + ", initial: " + initial); - } - long next = nextUnadjustedValue(); - if (next >= Long.MIN_VALUE + 0x100000000L) { - // Everytime we throw this exception, a long sequence value - // has been lost. This seems fairly benign. - throw new PersistException - ("Sequence exhausted for 32-bit ints: " + mStoredSequence.getName() + - ", next: " + (next + Long.MIN_VALUE + initial)); - } - return (int) (next + Long.MIN_VALUE + initial); - } - } catch (FetchException e) { - throw e.toPersistException(); - } - } - - /** - * Allow any unused reserved values to be returned for re-use. If the - * repository is shared by other processes, then reserved values might not - * be returnable. - * - *

This method should be called during the shutdown process of a - * repository, although calling it does not invalidate this - * SequenceValueGenerator. If getNextValue is called again, it will reserve - * values again. - * - * @return true if reserved values were returned - */ - public boolean returnReservedValues() throws FetchException, PersistException { - synchronized (mStoredSequence) { - if (mHasReservedValues) { - Transaction txn = mRepository.enterTopTransaction(null); - txn.setForUpdate(true); - try { - // Compare known StoredSequence with current persistent - // one. If same, then reserved values can be returned. - StoredSequence current = mStorage.prepare(); - current.setName(mStoredSequence.getName()); - if (current.tryLoad() && current.equals(mStoredSequence)) { - mStoredSequence.setNextValue(mNextValue + mIncrement); - mStoredSequence.update(); - txn.commit(); - mHasReservedValues = false; - return true; - } - } finally { - txn.exit(); - } - } - } - return false; - } - - // Assumes caller has synchronized on mStoredSequence - private long nextUnadjustedValue() throws FetchException, PersistException { - if (mHasReservedValues) { - long next = mNextValue + mIncrement; - mNextValue = next; - if (next < mStoredSequence.getNextValue()) { - return next; - } - mHasReservedValues = false; - } - - Transaction txn = mRepository.enterTopTransaction(null); - txn.setForUpdate(true); - try { - // Assume that StoredSequence is stale, so reload. - mStoredSequence.load(); - long next = mStoredSequence.getNextValue(); - long nextStored = next + mReserveAmount * mIncrement; - - if (next >= 0 && nextStored < 0) { - // Wrapped around. There might be just a few values left. - long avail = (Long.MAX_VALUE - next) / mIncrement; - if (avail > 0) { - nextStored = next + avail * mIncrement; - } else { - // Throw a PersistException since sequences are applied during - // insert operations, and inserts can only throw PersistExceptions. - throw new PersistException - ("Sequence exhausted: " + mStoredSequence.getName()); - } - } - - mStoredSequence.setNextValue(nextStored); - mStoredSequence.update(); - - txn.commit(); - - mNextValue = next; - mHasReservedValues = true; - return next; - } finally { - txn.exit(); - } - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java b/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java deleted file mode 100644 index 659de89..0000000 --- a/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. 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.amazon.carbonado.spi; - -import com.amazon.carbonado.PersistException; - -/** - * Produces values for sequences. - * - * @author Brian S O'Neill - * @see com.amazon.carbonado.Sequence - */ -public interface SequenceValueProducer { - /** - * Returns the next value from the sequence, which may wrap negative if all - * positive values are exhausted. When sequence wraps back to initial - * value, the sequence is fully exhausted, and an exception is thrown to - * indicate this. - * - *

Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - * - * @throws PersistException for fetch/persist failure or if sequence is exhausted. - */ - public long nextLongValue() throws PersistException; - - /** - * Returns the next value from the sequence, which may wrap negative if all - * positive values are exhausted. When sequence wraps back to initial - * value, the sequence is fully exhausted, and an exception is thrown to - * indicate this. - * - *

Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - * - * @throws PersistException for fetch/persist failure or if sequence is - * exhausted for int values. - */ - public int nextIntValue() throws PersistException; - - /** - * Returns the next decimal string value from the sequence, which remains - * positive. When sequence wraps back to initial value, the sequence is - * fully exhausted, and an exception is thrown to indicate this. - * - *

Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - * - * @throws PersistException for fetch/persist failure or if sequence is exhausted. - */ - public String nextDecimalValue() throws PersistException; - - /** - * Returns the next numerical string value from the sequence, which remains - * positive. When sequence wraps back to initial value, the sequence is - * fully exhausted, and an exception is thrown to indicate this. - * - *

Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - * - * @param radix use 2 for binary, 10 for decimal, 16 for hex. Max is 36. - * @param minLength ensure string is at least this long (padded with zeros if - * necessary) to ensure proper string sort - * @throws PersistException for fetch/persist failure or if sequence is exhausted. - */ - public String nextNumericalValue(int radix, int minLength) throws PersistException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java index 93f9b38..0c2d50d 100644 --- a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java @@ -24,6 +24,7 @@ import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.math.BigInteger; @@ -583,8 +584,11 @@ public final class StorableGenerator { requireStateField = true; } else if (mGenMode == GEN_ABSTRACT) { // Only define regular property fields if abstract - // class. Wrapped class doesn't reference them. - mClassFile.addField(Modifiers.PROTECTED, name, type); + // class. Wrapped class doesn't reference them. Double + // words are volatile to prevent word tearing without + // explicit synchronization. + mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()), + name, type); requireStateField = true; } @@ -617,11 +621,9 @@ public final class StorableGenerator { } } - if (mGenMode == GEN_ABSTRACT && (type.isDoubleWord() || property.isJoin())) { - // Even if read method just reads a field, - // synchronization is needed if type is a double - // word. Synchronization is also required for join - // property accessors, as they may alter bit masks. + if (mGenMode == GEN_ABSTRACT && property.isJoin()) { + // Synchronization is required for join property + // accessors, as they may alter bit masks. mi.setModifiers(mi.getModifiers().toSynchronized(true)); } @@ -1061,12 +1063,6 @@ public final class StorableGenerator { (Modifiers.PROTECTED, readName, toType, null); mi.markSynthetic(); - if (type.isDoubleWord()) { - // Even if read method just reads a field, - // synchronization is needed if type is a double word. - mi.setModifiers(mi.getModifiers().toSynchronized(true)); - } - // Now add code that actually gets the property value and // then invokes adapt method. CodeBuilder b = new CodeBuilder(mi); @@ -1130,9 +1126,14 @@ public final class StorableGenerator { // Add tryLoad method which delegates to abstract doTryLoad method. addTryLoad: { // Define the tryLoad method. - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryLoad; + } + mi.addException(TypeDesc.forClass(FetchException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1269,9 +1270,14 @@ public final class StorableGenerator { // Add load method which calls tryLoad. addLoad: { // Define the load method. - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), LOAD_METHOD_NAME, null, null); + + if (mi == null) { + break addLoad; + } + mi.addException(TypeDesc.forClass(FetchException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1370,8 +1376,13 @@ public final class StorableGenerator { // Add insert method which calls insert(forTry = false) addInsert: { - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, INSERT_METHOD_NAME, null, null); + + if (mi == null) { + break addInsert; + } + mi.addException(TypeDesc.forClass(PersistException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1391,8 +1402,13 @@ public final class StorableGenerator { // Add tryInsert method which calls insert(forTry = true) addTryInsert: { - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryInsert; + } + mi.addException(TypeDesc.forClass(PersistException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1534,8 +1550,13 @@ public final class StorableGenerator { // Add update method which calls update(forTry = false) addUpdate: { - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, UPDATE_METHOD_NAME, null, null); + + if (mi == null) { + break addUpdate; + } + mi.addException(TypeDesc.forClass(PersistException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1555,8 +1576,13 @@ public final class StorableGenerator { // Add tryUpdate method which calls update(forTry = true) addTryUpdate: { - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryUpdate; + } + mi.addException(TypeDesc.forClass(PersistException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1642,8 +1668,13 @@ public final class StorableGenerator { // Add delete method which calls delete(forTry = false) addDelete: { // Define the delete method. - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, DELETE_METHOD_NAME, null, null); + + if (mi == null) { + break addDelete; + } + mi.addException(TypeDesc.forClass(PersistException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1664,8 +1695,13 @@ public final class StorableGenerator { // Add tryDelete method which calls delete(forTry = true) addTryDelete: { // Define the delete method. - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryDelete; + } + mi.addException(TypeDesc.forClass(PersistException.class)); if (mGenMode == GEN_WRAPPED) { @@ -1683,24 +1719,34 @@ public final class StorableGenerator { } // Add storableType method - { + addStorableType: { final TypeDesc type = TypeDesc.forClass(mStorableType); final TypeDesc storableClassType = TypeDesc.forClass(Class.class); - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, STORABLE_TYPE_METHOD_NAME, storableClassType, null); + + if (mi == null) { + break addStorableType; + } + CodeBuilder b = new CodeBuilder(mi); b.loadConstant(type); b.returnValue(storableClassType); } - // Add copy methods. - { + // Add copy method. + addCopy: { TypeDesc type = TypeDesc.forClass(mInfo.getStorableType()); // Add copy method. - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), COPY_METHOD_NAME, mClassFile.getType(), null); + + if (mi == null) { + break addCopy; + } + CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.invokeVirtual(CLONE_METHOD_NAME, TypeDesc.OBJECT, null); @@ -1739,10 +1785,12 @@ public final class StorableGenerator { } b.returnValue(type); - - CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType()); } + // Part of properly defining copy method, except needs to be added even + // if copy method was not added because it is inherited and final. + CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType()); + // Create all the property copier methods. // Boolean params: pkProperties, versionProperty, dataProperties, unequalOnly, dirtyOnly addCopyPropertiesMethod(COPY_ALL_PROPERTIES, @@ -1758,9 +1806,13 @@ public final class StorableGenerator { // Define hasDirtyProperties method. addHasDirtyProps: { - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, HAS_DIRTY_PROPERTIES, TypeDesc.BOOLEAN, null); + if (mi == null) { + break addHasDirtyProps; + } + if (mGenMode == GEN_WRAPPED) { callWrappedStorable(mi); break addHasDirtyProps; @@ -1783,10 +1835,15 @@ public final class StorableGenerator { addPropertyStateCheckMethod(IS_PROPERTY_CLEAN, PROPERTY_STATE_CLEAN); // Define isPropertySupported method. - { - MethodInfo mi = mClassFile.addMethod + addIsPropertySupported: { + MethodInfo mi = addMethodIfNotFinal (Modifiers.PUBLIC, IS_PROPERTY_SUPPORTED, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); + + if (mi == null) { + break addIsPropertySupported; + } + CodeBuilder b = new CodeBuilder(mi); b.loadThis(); @@ -2027,12 +2084,16 @@ public final class StorableGenerator { TypeDesc[] param = { TypeDesc.forClass(Storable.class) }; TypeDesc storableTypeDesc = TypeDesc.forClass(mStorableType); - MethodInfo mi= mClassFile.addMethod + MethodInfo mi= addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), methodName, null, param); + if (mi == null) { + return; + } + if (mGenMode == GEN_WRAPPED) { callWrappedStorable(mi); return; @@ -2298,7 +2359,13 @@ public final class StorableGenerator { } private void addMarkCleanMethod(String name) { - MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, null, null); + MethodInfo mi = + addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(true), name, null, null); + + if (mi == null) { + return; + } + CodeBuilder b = new CodeBuilder(mi); if (mGenMode == GEN_WRAPPED) { @@ -2353,7 +2420,13 @@ public final class StorableGenerator { } private void addMarkDirtyMethod(String name) { - MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, null, null); + MethodInfo mi = + addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null); + + if (mi == null) { + return; + } + CodeBuilder b = new CodeBuilder(mi); if (mGenMode == GEN_WRAPPED) { @@ -2506,6 +2579,22 @@ public final class StorableGenerator { private void addIsInitializedMethod (String name, Map> properties) { + // Don't check Automatic properties. + { + boolean cloned = false; + for (StorableProperty prop : properties.values()) { + if (prop.isAutomatic() || prop.isVersion()) { + if (!cloned) { + properties = new LinkedHashMap>(properties); + cloned = true; + } + // This isn't concurrent modification since the loop is + // still operating on the original properties map. + properties.remove(prop.getName()); + } + } + } + MethodInfo mi = mClassFile.addMethod(Modifiers.PROTECTED, name, TypeDesc.BOOLEAN, null); CodeBuilder b = new CodeBuilder(mi); @@ -2624,7 +2713,7 @@ public final class StorableGenerator { CodeBuilder b = new CodeBuilder(mi); // Generate big switch statement that operates on Strings. See also - // cojen.util.BeanPropertyAccessor, which also generates this kind of + // org.cojen.util.BeanPropertyAccessor, which also generates this kind of // switch. // For switch case count, obtain a prime number, at least twice as @@ -2792,8 +2881,13 @@ public final class StorableGenerator { * @param state property state to check */ private void addPropertyStateCheckMethod(String name, int state) { - MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, - TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); + MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, name, + TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); + + if (mi == null) { + return; + } + CodeBuilder b = new CodeBuilder(mi); if (mGenMode == GEN_WRAPPED) { @@ -2825,7 +2919,11 @@ public final class StorableGenerator { */ private void addHashCodeMethod() { Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); - MethodInfo mi = mClassFile.addMethod(modifiers, "hashCode", TypeDesc.INT, null); + MethodInfo mi = addMethodIfNotFinal(modifiers, "hashCode", TypeDesc.INT, null); + + if (mi == null) { + return; + } if (mGenMode == GEN_WRAPPED) { callWrappedStorable(mi); @@ -2968,9 +3066,13 @@ public final class StorableGenerator { } Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); - MethodInfo mi = mClassFile.addMethod + MethodInfo mi = addMethodIfNotFinal (modifiers, equalsMethodName, TypeDesc.BOOLEAN, objectParam); + if (mi == null) { + return; + } + if (mGenMode == GEN_WRAPPED && equalityType != EQUAL_FULL) { callWrappedStorable(mi); return; @@ -3051,11 +3153,15 @@ public final class StorableGenerator { TypeDesc[] charParam = {TypeDesc.CHAR}; Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); - MethodInfo mi = mClassFile.addMethod(modifiers, - keyOnly ? - TO_STRING_KEY_ONLY_METHOD_NAME : - TO_STRING_METHOD_NAME, - TypeDesc.STRING, null); + MethodInfo mi = addMethodIfNotFinal(modifiers, + keyOnly ? + TO_STRING_KEY_ONLY_METHOD_NAME : + TO_STRING_METHOD_NAME, + TypeDesc.STRING, null); + + if (mi == null) { + return; + } if (mGenMode == GEN_WRAPPED) { callWrappedStorable(mi); @@ -3548,4 +3654,18 @@ public final class StorableGenerator { new TypeDesc[] {threadType, TypeDesc.forClass(Throwable.class)}); b.returnVoid(); } + + /** + * @return MethodInfo for completing definition or null if superclass + * already implements method as final. + */ + private MethodInfo addMethodIfNotFinal(Modifiers modifiers, String name, + TypeDesc retType, TypeDesc[] params) + { + if (CodeBuilderUtil.isPublicMethodFinal(mStorableType, name, retType, params)) { + return null; + } + + return mClassFile.addMethod(modifiers, name, retType, params); + } } diff --git a/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java b/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java index 965880e..2f943ea 100644 --- a/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java +++ b/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java @@ -200,6 +200,26 @@ public class StorableIndexSet extends TreeSet, StorableIndex> replacements = null; + for (StorableIndex index : this) { + StorableIndex replacement = index.clustered(clustered); + if (replacement != index) { + if (replacements == null) { + replacements = new HashMap, StorableIndex>(); + } + replacements.put(index, replacement); + } + } + replaceEntries(replacements); + } + /** * Augment non-unique indexes with primary key properties, thus making them * unique. diff --git a/src/main/java/com/amazon/carbonado/spi/StorageCollection.java b/src/main/java/com/amazon/carbonado/spi/StorageCollection.java deleted file mode 100644 index c75f350..0000000 --- a/src/main/java/com/amazon/carbonado/spi/StorageCollection.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. 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.amazon.carbonado.spi; - -import java.util.IdentityHashMap; - -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentHashMap; - -import com.amazon.carbonado.MalformedTypeException; -import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.info.StorableIntrospector; - -/** - * Thread-safe container of Storage instances which creates Storage - * on-demand. If multiple threads are requesting the same Storage concurrently, - * only one thread actually creates the Storage. The other waits for it to - * become available. - * - * @author Brian S O'Neill - */ -public abstract class StorageCollection { - private final ConcurrentMap, Storage> mStorageMap; - private final ConcurrentMap, Object> mStorableTypeLockMap; - - public StorageCollection() { - mStorageMap = new ConcurrentHashMap, Storage>(); - mStorableTypeLockMap = new ConcurrentHashMap, Object>(); - } - - public Storage storageFor(Class type) - throws MalformedTypeException, SupportException, RepositoryException - { - Storage storage = mStorageMap.get(type); - if (storage != null) { - return storage; - } - - getLock: while (true) { - Object lock; - boolean doCreate; - - { - Object newLock = new Object(); - Object existingLock = mStorableTypeLockMap.putIfAbsent(type, newLock); - - if (existingLock == null) { - lock = newLock; - doCreate = true; - } else { - lock = existingLock; - doCreate = false; - } - } - - if (Thread.holdsLock(lock)) { - throw new IllegalStateException - ("Recursively trying to create storage for type: " + type); - } - - synchronized (lock) { - try { - // Check storage map again before creating new storage. - checkLock: while (true) { - storage = mStorageMap.get(type); - if (storage != null) { - return storage; - } - if (mStorableTypeLockMap.get(type) != lock) { - // Lock has changed, so get a new lock. - continue getLock; - } - if (doCreate) { - break checkLock; - } - try { - // Wait with a timeout to handle rare race condition. - lock.wait(100); - } catch (InterruptedException e) { - throw new RepositoryException("Interrupted"); - } - } - - // Examine and throw exception early if there is a problem. - StorableIntrospector.examine(type); - - storage = createStorage(type); - - mStorageMap.put(type, storage); - break getLock; - } finally { - // Storable type lock no longer needed. - mStorableTypeLockMap.remove(type, lock); - lock.notifyAll(); - } - } - } - - return storage; - } - - public Iterable allStorage() { - return mStorageMap.values(); - } - - protected abstract Storage createStorage(Class type) - throws SupportException, RepositoryException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/StoragePool.java b/src/main/java/com/amazon/carbonado/spi/StoragePool.java new file mode 100644 index 0000000..9382723 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/spi/StoragePool.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. 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.amazon.carbonado.spi; + +import com.amazon.carbonado.MalformedTypeException; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; + +import com.amazon.carbonado.util.AbstractPool; + +/** + * A concurrent pool of strongly referenced Storage instances mapped by + * Storable type. Storage instances are lazily created and pooled. + * + * @author Brian S O'Neill + */ +public abstract class StoragePool + extends AbstractPool +{ + public StoragePool() { + } + + /** + * Returns a Storage instance for the given Storable type, which is lazily + * created and pooled. If multiple threads are requesting upon the same type + * concurrently, at most one thread attempts to lazily create the + * Storage. The others wait for it to become available. + */ + public Storage getStorage(Class type) + throws MalformedTypeException, SupportException, RepositoryException + { + return (Storage) super.get(type); + } + + protected final Storage create(Class type) throws SupportException, RepositoryException { + return createStorage(type); + } + + protected abstract Storage createStorage(Class type) + throws SupportException, RepositoryException; +} diff --git a/src/main/java/com/amazon/carbonado/spi/StoredLob.java b/src/main/java/com/amazon/carbonado/spi/StoredLob.java index 2435cb1..e409e60 100644 --- a/src/main/java/com/amazon/carbonado/spi/StoredLob.java +++ b/src/main/java/com/amazon/carbonado/spi/StoredLob.java @@ -19,10 +19,8 @@ package com.amazon.carbonado.spi; import com.amazon.carbonado.Alias; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Independent; import com.amazon.carbonado.PrimaryKey; -import com.amazon.carbonado.Sequence; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Version; @@ -38,7 +36,6 @@ import com.amazon.carbonado.constraint.IntegerConstraint; @Independent @Alias("CARBONADO_LOB") public abstract class StoredLob implements Storable { - @Sequence("com.amazon.carbonado.spi.StoredLob") public abstract long getLocator(); public abstract void setLocator(long locator); diff --git a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java index 17db288..71f6fbc 100644 --- a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java +++ b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java @@ -78,6 +78,10 @@ public abstract class WrappedStorage implements Storage { return wrap(mStorage.query(filter)); } + public void truncate() throws PersistException { + mStorage.truncate(); + } + public boolean addTrigger(Trigger trigger) { return mTriggerManager.addTrigger(trigger); } -- cgit v1.2.3