summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2010-03-14 20:13:11 +0000
committerBrian S. O'Neill <bronee@gmail.com>2010-03-14 20:13:11 +0000
commit10b8b1179e4b1c90db455e68ff81c89e4cb3f015 (patch)
tree89e9d8e5e809b9488ae55efe854a0058f5f51c0e
parent8d5c1796c151375ac8978ded048514bdc8922c1e (diff)
Allow storable types to differ between client and server.
-rw-r--r--src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java18
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableCopier.java311
-rw-r--r--src/main/java/com/amazon/carbonado/layout/Layout.java39
-rw-r--r--src/main/java/com/amazon/carbonado/layout/LayoutFactory.java48
4 files changed, 403 insertions, 13 deletions
diff --git a/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java b/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java
index 1676bae..c7ad156 100644
--- a/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java
+++ b/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java
@@ -37,17 +37,17 @@ import org.cojen.util.ThrowUnchecked;
* @author Brian S O'Neill
* @since 1.2.2
*/
-public class DetachedStorableFactory {
- private DetachedStorableFactory() {
- }
+public class DetachedStorableFactory<S extends Storable> {
+ private final NoSupport.Factory mFactory;
- public static <S extends Storable> S create(Class<S> type)
- throws SupportException
- {
- return (S) QuickConstructorGenerator
+ public DetachedStorableFactory(Class<S> type) throws SupportException {
+ mFactory = QuickConstructorGenerator
.getInstance(DelegateStorableGenerator.getDelegateClass(type, null),
- NoSupport.Factory.class)
- .newInstance(NoSupport.THE);
+ NoSupport.Factory.class);
+ }
+
+ public <S extends Storable> S newInstance() {
+ return (S) mFactory.newInstance(NoSupport.THE);
}
private static class NoSupport implements DelegateSupport<Storable> {
diff --git a/src/main/java/com/amazon/carbonado/gen/StorableCopier.java b/src/main/java/com/amazon/carbonado/gen/StorableCopier.java
new file mode 100644
index 0000000..7152113
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/gen/StorableCopier.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2010 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.gen;
+
+import java.lang.reflect.Constructor;
+
+import java.util.WeakHashMap;
+
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.info.StorableInfo;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+
+import org.cojen.util.ClassInjector;
+
+/**
+ * Copies properties between otherwise incompatible Storables. Only matched
+ * properties are copied, and primitive types are converted.
+ *
+ * @author Brian S O'Neill
+ * @since 1.2.2
+ */
+public abstract class StorableCopier<S extends Storable, T extends Storable> {
+ private static final WeakHashMap<Class, Object> cClassKeyCache;
+ private static final WeakHashMap<Object, From> cFromCache;
+
+ static {
+ cClassKeyCache = new WeakHashMap<Class, Object>();
+ cFromCache = new WeakHashMap<Object, From>();
+ }
+
+ static synchronized Object classKey(Class clazz) {
+ Object key = cClassKeyCache.get(clazz);
+ if (key == null) {
+ key = new Object();
+ cClassKeyCache.put(clazz, key);
+ }
+ return key;
+ }
+
+ public static synchronized <S extends Storable> From<S> from(Class<S> source) {
+ Object key = classKey(source);
+ From<S> from = (From<S>) cFromCache.get(key);
+ if (from == null) {
+ from = new From<S>(source);
+ cFromCache.put(key, from);
+ }
+ return from;
+ }
+
+ public static class From<S extends Storable> {
+ private final Class<S> mSource;
+ private final WeakHashMap<Object, StorableCopier> mCopierCache;
+
+ From(Class<S> source) {
+ mSource = source;
+ mCopierCache = new WeakHashMap<Object, StorableCopier>();
+ }
+
+ public synchronized <T extends Storable> StorableCopier<S, T> to(Class<T> target) {
+ Object key = classKey(target);
+ StorableCopier<S, T> copier = (StorableCopier<S, T>) mCopierCache.get(key);
+ if (copier == null) {
+ if (mSource == target) {
+ copier = (StorableCopier<S, T>) Direct.THE;
+ } else {
+ copier = new Wrapped<S, T>(new Wrapper<S, T>(mSource, target).generate());
+ }
+ mCopierCache.put(key, copier);
+ }
+ return copier;
+ }
+ }
+
+ protected StorableCopier() {
+ }
+
+ public abstract void copyAllProperties(S source, T target);
+
+ public abstract void copyPrimaryKeyProperties(S source, T target);
+
+ public abstract void copyVersionProperty(S source, T target);
+
+ public abstract void copyUnequalProperties(S source, T target);
+
+ public abstract void copyDirtyProperties(S source, T target);
+
+ private static class Wrapped<S extends Storable, T extends Storable>
+ extends StorableCopier<S, T>
+ {
+ private final Constructor<? extends S> mWrapperCtor;
+
+ private Wrapped(Constructor<? extends S> ctor) {
+ mWrapperCtor = ctor;
+ }
+
+ public void copyAllProperties(S source, T target) {
+ source.copyAllProperties(wrap(target));
+ }
+
+ public void copyPrimaryKeyProperties(S source, T target) {
+ source.copyPrimaryKeyProperties(wrap(target));
+ }
+
+ public void copyVersionProperty(S source, T target) {
+ source.copyVersionProperty(wrap(target));
+ }
+
+ public void copyUnequalProperties(S source, T target) {
+ source.copyUnequalProperties(wrap(target));
+ }
+
+ public void copyDirtyProperties(S source, T target) {
+ source.copyDirtyProperties(wrap(target));
+ }
+
+ private S wrap(T target) {
+ try {
+ return mWrapperCtor.newInstance(target);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private static class Direct<S extends Storable> extends StorableCopier<S, S> {
+ static final Direct THE = new Direct();
+
+ private Direct() {
+ }
+
+ public void copyAllProperties(S source, S target) {
+ source.copyAllProperties(target);
+ }
+
+ public void copyPrimaryKeyProperties(S source, S target) {
+ source.copyPrimaryKeyProperties(target);
+ }
+
+ public void copyVersionProperty(S source, S target) {
+ source.copyVersionProperty(target);
+ }
+
+ public void copyUnequalProperties(S source, S target) {
+ source.copyUnequalProperties(target);
+ }
+
+ public void copyDirtyProperties(S source, S target) {
+ source.copyDirtyProperties(target);
+ }
+ }
+
+ private static class Wrapper<W extends Storable, D extends Storable> {
+ private final StorableInfo<W> mWrapperInfo;
+ private final StorableInfo<D> mDelegateInfo;
+ private final ClassInjector mClassInjector;
+ private final ClassFile mClassFile;
+
+ Wrapper(Class<W> wrapper, Class<D> delegate) {
+ mWrapperInfo = StorableIntrospector.examine(wrapper);
+ mDelegateInfo = StorableIntrospector.examine(delegate);
+
+ mClassInjector = ClassInjector.create(wrapper.getName(), wrapper.getClassLoader());
+
+ mClassFile = CodeBuilderUtil.createStorableClassFile
+ (mClassInjector, mWrapperInfo.getStorableType(),
+ false, StorableCopier.class.getName());
+ }
+
+ Constructor<? extends W> generate() {
+ TypeDesc wrapperType = TypeDesc.forClass(mWrapperInfo.getStorableType());
+ TypeDesc delegateType = TypeDesc.forClass(mDelegateInfo.getStorableType());
+ TypeDesc classType = TypeDesc.forClass(Class.class);
+
+ mClassFile.addField(Modifiers.PRIVATE.toFinal(true), "delegate", delegateType);
+
+ MethodInfo mi = mClassFile.addConstructor
+ (Modifiers.PUBLIC, new TypeDesc[] {delegateType});
+ CodeBuilder b = new CodeBuilder(mi);
+ b.loadThis();
+ b.invokeSuperConstructor(null);
+ b.loadThis();
+ b.loadLocal(b.getParameter(0));
+ b.storeField("delegate", delegateType);
+ b.returnVoid();
+
+ // Implement property access methods.
+ for (StorableProperty<W> wrapperProp : mWrapperInfo.getAllProperties().values()) {
+ if (wrapperProp.isDerived()) {
+ continue;
+ }
+
+ TypeDesc wrapperPropType = TypeDesc.forClass(wrapperProp.getType());
+
+ StorableProperty<D> delegateProp =
+ mDelegateInfo.getAllProperties().get(wrapperProp.getName());
+
+ if (delegateProp == null || delegateProp.isDerived()) {
+ addUnmatchedProperty(wrapperProp, wrapperPropType);
+ continue;
+ }
+
+ TypeDesc delegatePropType = TypeDesc.forClass(delegateProp.getType());
+
+ if (wrapperPropType.equals(delegatePropType)) {
+ // No conversion required.
+ b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getReadMethod()));
+ b.loadThis();
+ b.loadField("delegate", delegateType);
+ b.invoke(delegateProp.getReadMethod());
+ b.returnValue(wrapperPropType);
+
+ b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getWriteMethod()));
+ b.loadThis();
+ b.loadField("delegate", delegateType);
+ b.loadLocal(b.getParameter(0));
+ b.invoke(delegateProp.getWriteMethod());
+ b.returnVoid();
+
+ continue;
+ }
+
+ TypeDesc wrapperPrimPropType = wrapperPropType.toPrimitiveType();
+ TypeDesc delegatePrimPropType = delegatePropType.toPrimitiveType();
+
+ if (wrapperPrimPropType == null || delegatePrimPropType == null) {
+ addUnmatchedProperty(wrapperProp, wrapperPropType);
+ continue;
+ }
+
+ // Convert primitive or boxed type.
+
+ b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getReadMethod()));
+ b.loadThis();
+ b.loadField("delegate", delegateType);
+ b.invoke(delegateProp.getReadMethod());
+ if (wrapperPropType.isPrimitive() && !delegatePropType.isPrimitive()) {
+ // Check for null.
+ b.dup();
+ Label notNull = b.createLabel();
+ b.ifNullBranch(notNull, false);
+ CodeBuilderUtil.blankValue(b, wrapperPropType);
+ b.returnValue(wrapperPropType);
+ notNull.setLocation();
+ }
+ b.convert(delegatePropType, wrapperPropType);
+ b.returnValue(wrapperPropType);
+
+ b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getWriteMethod()));
+ b.loadThis();
+ b.loadField("delegate", delegateType);
+ b.loadLocal(b.getParameter(0));
+ if (!wrapperPropType.isPrimitive() && delegatePropType.isPrimitive()) {
+ // Check for null.
+ Label notNull = b.createLabel();
+ b.ifNullBranch(notNull, false);
+ CodeBuilderUtil.blankValue(b, delegatePropType);
+ b.invoke(delegateProp.getWriteMethod());
+ b.returnVoid();
+ notNull.setLocation();
+ b.loadLocal(b.getParameter(0));
+ }
+ b.convert(wrapperPropType, delegatePropType);
+ b.invoke(delegateProp.getWriteMethod());
+ b.returnVoid();
+ }
+
+ try {
+ Class<? extends W> wrapperClass = mClassInjector.defineClass(mClassFile);
+ return wrapperClass.getConstructor(mDelegateInfo.getStorableType());
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private void addUnmatchedProperty(StorableProperty<W> wrapperProp,
+ TypeDesc wrapperPropType)
+ {
+ CodeBuilder b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getReadMethod()));
+ CodeBuilderUtil.blankValue(b, wrapperPropType);
+ b.returnValue(wrapperPropType);
+
+ b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getWriteMethod()));
+ b.returnVoid();
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/Layout.java b/src/main/java/com/amazon/carbonado/layout/Layout.java
index 7aea758..570db8c 100644
--- a/src/main/java/com/amazon/carbonado/layout/Layout.java
+++ b/src/main/java/com/amazon/carbonado/layout/Layout.java
@@ -19,8 +19,11 @@
package com.amazon.carbonado.layout;
import java.io.IOException;
+import java.io.OutputStream;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -135,7 +138,7 @@ public class Layout {
private final StoredLayout mStoredLayout;
private final LayoutOptions mOptions;
- private transient List<LayoutProperty> mAllProperties;
+ private volatile List<LayoutProperty> mAllProperties;
/**
* Creates a Layout around an existing storable.
@@ -248,7 +251,9 @@ public class Layout {
* Returns all the properties of this layout, in their proper order.
*/
public List<LayoutProperty> getAllProperties() throws FetchException {
- if (mAllProperties == null) {
+ List<LayoutProperty> all = mAllProperties;
+
+ if (all == null) {
Cursor <StoredLayoutProperty> cursor = mLayoutFactory.mPropertyStorage
.query("layoutID = ?")
.with(mStoredLayout.getLayoutID())
@@ -262,13 +267,13 @@ public class Layout {
list.add(new LayoutProperty(cursor.next()));
}
- mAllProperties = Collections.unmodifiableList(list);
+ mAllProperties = all = Collections.unmodifiableList(list);
} finally {
cursor.close();
}
}
- return mAllProperties;
+ return all;
}
/**
@@ -482,6 +487,32 @@ public class Layout {
&& getAllProperties().equals(layout.getAllProperties());
}
+ /**
+ * Write a layout to be read by {@link LayoutFactory#readLayoutFrom}.
+ *
+ * @since 1.2.2
+ */
+ public void writeTo(OutputStream out) throws IOException, RepositoryException {
+ mStoredLayout.writeTo(out);
+
+ Cursor <StoredLayoutProperty> cursor = mLayoutFactory.mPropertyStorage
+ .query("layoutID = ?")
+ .with(mStoredLayout.getLayoutID())
+ .fetch();
+
+ try {
+ while (cursor.hasNext()) {
+ StoredLayoutProperty prop = cursor.next();
+ out.write(1);
+ prop.writeTo(out);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ out.write(0);
+ }
+
// Assumes caller is in a transaction.
void insert(int generation) throws PersistException {
if (mAllProperties == null) {
diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java
index 54b6933..50218e6 100644
--- a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java
+++ b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java
@@ -18,6 +18,9 @@
package com.amazon.carbonado.layout;
+import java.io.InputStream;
+import java.io.IOException;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
@@ -266,6 +269,51 @@ public class LayoutFactory implements LayoutCapability {
return new Layout(this, storedLayout);
}
+ /**
+ * Read a layout as written by {@link Layout#writeTo}.
+ *
+ * @since 1.2.2
+ */
+ public Layout readLayoutFrom(InputStream in) throws IOException, RepositoryException {
+ Transaction txn = mRepository.enterTransaction();
+ try {
+ txn.setForUpdate(true);
+ StoredLayout storedLayout = mLayoutStorage.prepare();
+ storedLayout.readFrom(in);
+ try {
+ storedLayout.insert();
+ } catch (UniqueConstraintException e) {
+ StoredLayout existing = mLayoutStorage.prepare();
+ storedLayout.copyPrimaryKeyProperties(existing);
+ if (!existing.tryLoad() || !existing.equalProperties(storedLayout)) {
+ throw e;
+ }
+ }
+
+ int op;
+ while ((op = in.read()) != 0) {
+ StoredLayoutProperty storedProperty = mPropertyStorage.prepare();
+ storedProperty.readFrom(in);
+ try {
+ storedProperty.insert();
+ } catch (UniqueConstraintException e) {
+ StoredLayoutProperty existing = mPropertyStorage.prepare();
+ storedProperty.copyPrimaryKeyProperties(existing);
+ existing.load();
+ if (!existing.tryLoad() || !existing.equalProperties(storedProperty)) {
+ throw e;
+ }
+ }
+ }
+
+ txn.commit();
+
+ return new Layout(this, storedLayout);
+ } finally {
+ txn.exit();
+ }
+ }
+
synchronized void registerReconstructed
(Class<? extends Storable> reconstructed, Layout layout)
{