diff options
| author | Brian S. O'Neill <bronee@gmail.com> | 2010-03-14 20:13:11 +0000 | 
|---|---|---|
| committer | Brian S. O'Neill <bronee@gmail.com> | 2010-03-14 20:13:11 +0000 | 
| commit | 10b8b1179e4b1c90db455e68ff81c89e4cb3f015 (patch) | |
| tree | 89e9d8e5e809b9488ae55efe854a0058f5f51c0e /src/main | |
| parent | 8d5c1796c151375ac8978ded048514bdc8922c1e (diff) | |
Allow storable types to differ between client and server.
Diffstat (limited to 'src/main')
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)
      {
 | 
