diff options
Diffstat (limited to 'src/main')
11 files changed, 2182 insertions, 0 deletions
diff --git a/src/main/java/com/amazon/carbonado/synthetic/ClassFileBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/ClassFileBuilder.java new file mode 100644 index 0000000..c6f9ed5 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/ClassFileBuilder.java @@ -0,0 +1,60 @@ +/*
 + * 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.synthetic;
 +
 +import org.cojen.classfile.ClassFile;
 +import org.cojen.util.ClassInjector;
 +
 +/**
 + * Simple interface representing a partially filled class and injector pair.
 + *
 + * <P>This is abstract because it provides no mechanism for defining the
 + * classfile or injector; subclasses must provide them.
 + *
 + * @author Don Schneider
 + */
 +public abstract class ClassFileBuilder {
 +
 +    /**
 +     * Partially hydrogenated class operators
 +     */
 +    protected ClassFile mClassFile;
 +    protected ClassInjector mInjector;
 +
 +    /**
 +     * @return Returns the classFile.
 +     */
 +    public ClassFile getClassFile() {
 +        return mClassFile;
 +    }
 +
 +    /**
 +     * @return Returns the injector.
 +     */
 +    public ClassInjector getInjector() {
 +        return mInjector;
 +    }
 +
 +    /**
 +     * Defines the class for this generator
 +     */
 +    @SuppressWarnings("unchecked")
 +    public Class build() {
 +        return getInjector().defineClass(getClassFile());
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/StorableBean.java b/src/main/java/com/amazon/carbonado/synthetic/StorableBean.java new file mode 100644 index 0000000..badc801 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/StorableBean.java @@ -0,0 +1,200 @@ +/*
 + * 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.synthetic;
 +
 +import org.cojen.util.BeanPropertyAccessor;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Storage;
 +
 +/**
 + * Simple wrapper to allow a storable to be accessed via property names rather than
 + * an explicit interface definition.  This is generally expected to be used for accessing
 + * synthetic storables, but could certainly be used with standard storables if the need
 + * arises.
 + *
 + * @author Don Schneider
 + */
 +public class StorableBean<S extends Storable<S>> implements Storable<S> {
 +    private final S mProxy;
 +    private BeanPropertyAccessor mAccessor;
 +
 +    /**
 +     * Wrap a storable
 +     * @param s
 +     */
 +    public StorableBean(S s) {
 +        mProxy = s;
 +    }
 +
 +    /**
 +     * Retrieve a value from the storable by property name
 +     * @param propName name of the property to retrieve
 +     */
 +    public Object getValue(String propName) {
 +        return getAccessor().getPropertyValue(mProxy, propName);
 +    }
 +
 +    /**
 +     * Set a value into the storable by property name
 +     * @param propName name of the property to set
 +     * @param value new value for the property
 +     */
 +    public void setValue(String propName, Object value) {
 +        getAccessor().setPropertyValue(mProxy, propName, value);
 +    }
 +
 +    /**
 +     * @return the unwrapped storable
 +     */
 +    public S getStorable() {
 +        return mProxy;
 +    }
 +
 +    public void load() throws FetchException {
 +        mProxy.load();
 +    }
 +
 +    public boolean tryLoad() throws FetchException {
 +        return mProxy.tryLoad();
 +    }
 +
 +    public void insert() throws PersistException {
 +        mProxy.insert();
 +    }
 +
 +    public boolean tryInsert() throws PersistException {
 +        return mProxy.tryInsert();
 +    }
 +
 +    public void update() throws PersistException {
 +        mProxy.update();
 +    }
 +
 +    public boolean tryUpdate() throws PersistException {
 +        return mProxy.tryUpdate();
 +    }
 +
 +    public void delete() throws PersistException {
 +        mProxy.delete();
 +    }
 +
 +    public boolean tryDelete() throws PersistException {
 +        return mProxy.tryDelete();
 +    }
 +
 +    public Class<S> storableType() {
 +        return mProxy.storableType();
 +    }
 +
 +    public void copyAllProperties(S target) {
 +        mProxy.copyAllProperties(target);
 +    }
 +
 +    public void copyDirtyProperties(S target) {
 +        mProxy.copyDirtyProperties(target);
 +    }
 +
 +    public void copyPrimaryKeyProperties(S target) {
 +        mProxy.copyPrimaryKeyProperties(target);
 +    }
 +
 +    public void copyVersionProperty(S target) {
 +        mProxy.copyVersionProperty(target);
 +    }
 +
 +    public void copyUnequalProperties(S target) {
 +        mProxy.copyUnequalProperties(target);
 +    }
 +
 +    public boolean hasDirtyProperties() {
 +        return mProxy.hasDirtyProperties();
 +    }
 +
 +    public void markPropertiesClean() {
 +        mProxy.markPropertiesClean();
 +    }
 +
 +    public void markAllPropertiesClean() {
 +        mProxy.markAllPropertiesClean();
 +    }
 +
 +    public void markPropertiesDirty() {
 +        mProxy.markPropertiesDirty();
 +    }
 +
 +    public void markAllPropertiesDirty() {
 +        mProxy.markAllPropertiesDirty();
 +    }
 +
 +    public boolean isPropertyUninitialized(String propertyName) {
 +        return mProxy.isPropertyUninitialized(propertyName);
 +    }
 +
 +    public boolean isPropertyDirty(String propertyName) {
 +        return mProxy.isPropertyDirty(propertyName);
 +    }
 +
 +    public boolean isPropertyClean(String propertyName) {
 +        return mProxy.isPropertyClean(propertyName);
 +    }
 +
 +    public boolean isPropertySupported(String propertyName) {
 +        return mProxy.isPropertySupported(propertyName);
 +    }
 +
 +    public int hashCode() {
 +        return mProxy.hashCode();
 +    }
 +
 +    public boolean equals(Object obj) {
 +        if (obj instanceof Storable) {
 +            return mProxy.equalProperties(obj);
 +        }
 +        return false;
 +    }
 +
 +    public boolean equalPrimaryKeys(Object obj) {
 +        return mProxy.equalPrimaryKeys(obj);
 +    }
 +
 +    public boolean equalProperties(Object obj) {
 +        return mProxy.equalProperties(obj);
 +    }
 +
 +    public String toString() {
 +        return mProxy.toString();
 +    }
 +
 +    public String toStringKeyOnly() {
 +        return mProxy.toStringKeyOnly();
 +    }
 +
 +    public S copy() {
 +        return mProxy.copy();
 +    }
 +
 +    private BeanPropertyAccessor getAccessor() {
 +        if (mAccessor == null) {
 +            mAccessor = BeanPropertyAccessor.forClass(mProxy.storableType());
 +        }
 +        return mAccessor;
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticBuilder.java new file mode 100644 index 0000000..057825c --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticBuilder.java @@ -0,0 +1,114 @@ +/*
 + * 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.synthetic;
 +
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.SupportException;
 +
 +/**
 + * A synthetic builder provides a mechanism for creating a user storable from scratch.
 + * The client creates the builder, decorates with properties and indexes on those
 + * properties, then builds.
 + *
 + * If additional, ad hoc decoration is desired, the partially constructed classfile
 + * can be retrieved and operated on directly via the ClassFileBuilder
 + * returned by {@link #prepare}.
 + *
 + * @author Don Schneider
 + */
 +public interface SyntheticBuilder {
 +
 +
 +    /**
 +     * @return {@link ClassFileBuilder} ready for further decoration or building
 +     * @throws SupportException
 +     */
 +    public ClassFileBuilder prepare() throws SupportException;
 +
 +    /**
 +     * @return the generated class file for this builder.  Note that
 +     * proper operation requires that {@link #prepare()} already have been called
 +     * prior to calling this method.
 +     * @throws IllegalStateException if build has not yet been called.
 +     */
 +    public Class<? extends Storable> getStorableClass() throws IllegalStateException;
 +
 +    /**
 +     * Convenience method to generate the class.
 +     * Build will always call {@link #prepare()} and return the result of
 +     * generating the class from that classfile.  If the caller does not
 +     * wish to regenerate the class from scratch, use {@link #getStorableClass()} instead.
 +     */
 +    public Class<? extends Storable> build() throws SupportException;
 +
 +    /**
 +     * Add a property to the set managed by this builder.
 +     * @param name of the property
 +     * @param type of the property
 +     * @return property specification which can be further refined
 +     */
 +    public SyntheticProperty addProperty(String name, Class type);
 +
 +    /**
 +     * Add an externally defined synthetic property to the list
 +     * @param prop to add
 +     * @return original synthetic property as a convenience
 +     */
 +    public SyntheticProperty addProperty(SyntheticProperty prop);
 +
 +    /**
 +     * Check to see if a particular property has already been added to the list of
 +     * properties to generate
 +     * @param name
 +     */
 +    public boolean hasProperty(String name);
 +
 +    /**
 +     * Add a primary key to be built.
 +     * @return key to be decorated with property values defining the primary key
 +     */
 +    public SyntheticKey addPrimaryKey();
 +
 +    /**
 +     * Add an index to the set managed by this builder.  All indexes added this
 +     * way will be in addition to the primary key index.
 +     * @return index to be decorated with property values defining the index
 +     */
 +    public SyntheticIndex addIndex();
 +
 +
 +    /**
 +     * Returns true if a property with the version attribute has been addded
 +     */
 +    public boolean isVersioned();
 +
 +    /**
 +     * Interface used to get the name for the class to generate. This allows the
 +     * client to apply different rules for classname generation.
 +     */
 +    public interface ClassNameProvider {
 +        public String getName();
 +
 +        /**
 +         * SyntheticBuilder may choose to alter the class name to prevent a
 +         * class name collision. When explicit is true, the class name must not
 +         * be altered.
 +         */
 +        public boolean isExplicit();
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticIndex.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticIndex.java new file mode 100644 index 0000000..df804b2 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticIndex.java @@ -0,0 +1,31 @@ +/*
 + * 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.synthetic;
 +
 +/**
 + * Specification of a collection of properties which will participate in an index.  Each
 + * property has its own direction specification.
 + *
 + * @author Brian S O'Neill
 + */
 +public class SyntheticIndex extends SyntheticPropertyList {
 +    SyntheticIndex() {
 +        super();
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticKey.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticKey.java new file mode 100644 index 0000000..ede8de8 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticKey.java @@ -0,0 +1,31 @@ +/*
 + * 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.synthetic;
 +
 +/**
 + * Specification of a collection of properties which will participate in a key.  Each
 + * property has its own direction specification.
 + *
 + * @author Brian S O'Neill
 + */
 +public class SyntheticKey extends SyntheticPropertyList {
 +    SyntheticKey() {
 +        super();
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticProperty.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticProperty.java new file mode 100644 index 0000000..d719fdd --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticProperty.java @@ -0,0 +1,270 @@ +/*
 + * 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.synthetic;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
 +
 +import com.amazon.carbonado.info.StorablePropertyAdapter;
 +
 +/**
 + * Minimal specification of a storable property for use with a {@link SyntheticStorableBuilder}.
 + * Synthetic storables can be used to generate user storables.
 + *
 + * @author Don Schneider
 + * @author Brian S O'Neill
 + */
 +public class SyntheticProperty implements Comparable<SyntheticProperty> {
 +    private final Class mType;
 +    private final String mName;
 +    private String mReadMethodName;
 +    private String mWriteMethodName;
 +    private boolean mIsNullable = false;
 +    private boolean mIsVersion = false;
 +    private StorablePropertyAdapter mAdapter;
 +    private List<String> mAnnotationDescs;
 +
 +    /**
 +     * Generate a name for a bean "get" method ("is" method, for booleans).
 +     * @param name of the property
 +     * @param type return type of the property
 +     * @see #getReadMethodName()
 +     */
 +    public static String makeReadMethodName(String name, Class type) {
 +        return ((type == boolean.class) ? "is" : "get") + makeUppercase(name);
 +    }
 +
 +    /**
 +     * Generate a name for a bean "set" method
 +     * @param name of the property
 +     * @see #getWriteMethodName()
 +     */
 +    public static String makeWriteMethodName(String name) {
 +        return "set" + makeUppercase(name);
 +
 +    }
 +
 +    /**
 +     * Every property requires minimally a name and a type
 +     * @param name for the property
 +     * @param type of the data it contains
 +     */
 +    public SyntheticProperty(String name, Class type) {
 +        if (name == null || type == null) {
 +            throw new IllegalArgumentException();
 +        }
 +        mName = name;
 +        mType = type;
 +    }
 +
 +    /**
 +     *
 +     * @param name property name
 +     * @param type property type
 +     * @param isNullable true if this property can be null (default false)
 +     * @param isVersion true if this property is a version number (default false)
 +     */
 +    public SyntheticProperty(String name,
 +                             Class type,
 +                             boolean isNullable,
 +                             boolean isVersion) {
 +        mIsNullable = isNullable;
 +        mIsVersion = isVersion;
 +        mName = name;
 +        mType = type;
 +    }
 +
 +    /**
 +     * @return Name of the property
 +     */
 +    public String getName() {
 +        return mName;
 +    }
 +
 +    /**
 +     * @return type of the property
 +     */
 +    public Class getType() {
 +        return mType;
 +    }
 +
 +    /**
 +     * @return true if the property can be null
 +     */
 +    public boolean isNullable() {
 +        return mIsNullable;
 +    }
 +
 +    /**
 +     * @param isNullable true if the property can be null
 +     */
 +    public void setIsNullable(boolean isNullable) {
 +        mIsNullable = isNullable;
 +    }
 +
 +    /**
 +     * @return true if the property contains the versioning information for the storable.  Note that
 +     * at most one property can be the version property for a given storable
 +     */
 +    public boolean isVersion() {
 +        return mIsVersion;
 +    }
 +
 +    /**
 +     * @param isVersion true if the property should contain the versioning information for the
 +     * storable
 +     */
 +    public void setIsVersion(boolean isVersion) {
 +        mIsVersion = isVersion;
 +    }
 +
 +    /**
 +     * Returns the name of the read method.
 +     */
 +    public String getReadMethodName() {
 +        if (mReadMethodName == null) {
 +            mReadMethodName = makeReadMethodName(mName, mType);
 +        }
 +        return mReadMethodName;
 +    }
 +
 +    /**
 +     * Call to override default selection of read method name.
 +     */
 +    void setReadMethodName(String name) {
 +        mReadMethodName = name;
 +    }
 +
 +    /**
 +     * Returns the name of the write method.
 +     */
 +    public String getWriteMethodName() {
 +        if (mWriteMethodName == null) {
 +            mWriteMethodName = makeWriteMethodName(mName);
 +        }
 +        return mWriteMethodName;
 +    }
 +
 +    /**
 +     * Call to override default selection of write method name.
 +     */
 +    void setWriteMethodName(String name) {
 +        mWriteMethodName = name;
 +    }
 +
 +    /**
 +     * @return the optional adapter.
 +     */
 +    public StorablePropertyAdapter getAdapter() {
 +        return mAdapter;
 +    }
 +
 +    /**
 +     * Storables cannot currently have more than one adapter per property.
 +     *
 +     * @param adapter The adapter to set.
 +     */
 +    public void setAdapter(StorablePropertyAdapter adapter) {
 +        mAdapter = adapter;
 +    }
 +
 +    /**
 +     * Add an arbitrary annotation to the property accessor method, as
 +     * specified by a descriptor.
 +     *
 +     * @see com.amazon.carbonado.util.AnnotationDescPrinter
 +     */
 +    public void addAccessorAnnotationDescriptor(String annotationDesc) {
 +        if (mAnnotationDescs == null) {
 +            mAnnotationDescs = new ArrayList<String>(4);
 +        }
 +        mAnnotationDescs.add(annotationDesc);
 +    }
 +
 +    /**
 +     * Returns all the added accessor annotation descriptors in an unmodifiable list.
 +     */
 +    public List<String> getAccessorAnnotationDescriptors() {
 +        if (mAnnotationDescs == null) {
 +            return Collections.emptyList();
 +        }
 +        return Collections.unmodifiableList(mAnnotationDescs);
 +    }
 +
 +    /**
 +     * {@link Comparable} implementation.
 +     * @param otherProp
 +     */
 +    public int compareTo(SyntheticProperty otherProp) {
 +        if (this == otherProp) {
 +            return 0;
 +        }
 +        return mName.compareTo(otherProp.mName);
 +    }
 +
 +    public boolean equals(Object o) {
 +        if (this == o) {
 +            return true;
 +        }
 +        if (!(o instanceof SyntheticProperty)) {
 +            return false;
 +        }
 +
 +        final SyntheticProperty syntheticProperty = (SyntheticProperty) o;
 +
 +        if (mIsNullable != syntheticProperty.mIsNullable) {
 +            return false;
 +        }
 +        if (mIsVersion != syntheticProperty.mIsVersion) {
 +            return false;
 +        }
 +        if (mName != null ? !mName.equals(syntheticProperty.mName) : syntheticProperty.mName != null) {
 +            return false;
 +        }
 +        if (mType != null ? !mType.equals(syntheticProperty.mType) : syntheticProperty.mType != null) {
 +            return false;
 +        }
 +
 +        return true;
 +    }
 +
 +    public int hashCode() {
 +        int result;
 +        result = (mType != null ? mType.hashCode() : 0);
 +        result = 29 * result + (mName != null ? mName.hashCode() : 0);
 +        result = 29 * result + (mIsNullable ? 1 : 0);
 +        result = 29 * result + (mIsVersion ? 1 : 0);
 +        return result;
 +    }
 +
 +    private static String makeUppercase(String name) {
 +        if (name.length() > 0 && !Character.isUpperCase(name.charAt(0))) {
 +            name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
 +        }
 +        return name;
 +    }
 +
 +    public String toString() {
 +        return mName+'|'+
 +               (mIsNullable?"NULL|":"")+
 +               (mIsVersion?"VERS|":"")+
 +               '('+mType+')'
 +               ;
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticPropertyList.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticPropertyList.java new file mode 100644 index 0000000..b9c624b --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticPropertyList.java @@ -0,0 +1,91 @@ +/*
 + * 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.synthetic;
 +
 +import java.util.ArrayList;
 +import java.util.Iterator;
 +import java.util.List;
 +
 +import com.amazon.carbonado.info.Direction;
 +
 +/**
 + * Specification of a collection of properties which will participate in a key
 + * or index.  Each property has its own direction specification.
 + *
 + * @author Brian S O'Neill
 + * @author Don Schneider
 + */
 +public abstract class SyntheticPropertyList {
 +    private List<String> mPropertyList;
 +
 +    SyntheticPropertyList() {
 +        mPropertyList = new ArrayList<String>();
 +    }
 +
 +    /**
 +     * Adds a property to this index, with an unspecified direction.
 +     *
 +     * @param propertyName name of property to add to index
 +     */
 +    public void addProperty(String propertyName) {
 +        addProperty(propertyName, null);
 +    }
 +
 +    /**
 +     * Adds a property to this index, with the specified direction.
 +     *
 +     * @param propertyName name of property to add to index
 +     * @param direction optional direction of property
 +     */
 +    public void addProperty(String propertyName, Direction direction) {
 +        if (propertyName == null) {
 +            throw new IllegalArgumentException();
 +        }
 +        if (direction == null) {
 +            direction = Direction.UNSPECIFIED;
 +        }
 +
 +        if (direction != Direction.UNSPECIFIED) {
 +            if (propertyName.length() > 0) {
 +                if (propertyName.charAt(0) == '-' || propertyName.charAt(0) == '+') {
 +                    // Overrule the direction.
 +                    propertyName = propertyName.substring(1);
 +                }
 +            }
 +            propertyName = direction.toCharacter() + propertyName;
 +        }
 +
 +        mPropertyList.add(propertyName);
 +    }
 +
 +    /**
 +     * Returns the count of properties in this index.
 +     */
 +    public int getPropertyCount() {
 +        return mPropertyList.size();
 +    }
 +
 +    /**
 +     * Returns all the properties in this index, optionally prefixed with a '+'
 +     * or '-' to indicate direction.
 +     */
 +    public Iterator<String> getProperties() {
 +        return mPropertyList.iterator();
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticReferenceAccessor.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticReferenceAccessor.java new file mode 100644 index 0000000..6a34319 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticReferenceAccessor.java @@ -0,0 +1,100 @@ +/*
 + * 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.synthetic;
 +
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Storage;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.SupportException;
 +import com.amazon.carbonado.RepositoryException;
 +import com.amazon.carbonado.FetchException;
 +
 +/**
 + * An synthetic reference contains information which is directly related to another
 + * storable, and which requires that the other storable is retrievable.  This is a
 + * convenience class which unifies the builder and the repository information.
 + *
 + * @author Don Schneider
 + */
 +public class SyntheticReferenceAccessor<S extends Storable>  {
 +    private Repository mIndexEntryRepository;
 +    private Storage mIndexEntryStorage;
 +    private Class<? extends Storable> mIndexEntryClass;
 +
 +    private SyntheticStorableReferenceBuilder<S> mBuilder;
 +
 +
 +    public SyntheticReferenceAccessor(Repository repo,
 +                                      SyntheticStorableReferenceBuilder<S> builder)
 +            throws RepositoryException
 +    {
 +        mBuilder = builder;
 +        mIndexEntryClass = mBuilder.getStorableClass();
 +        mIndexEntryStorage = repo.storageFor(mIndexEntryClass);
 +        mIndexEntryRepository = repo;
 +    }
 +
 +    /**
 +     * @return repository in which this index entry is stored
 +     */
 +    public Repository getRepository() {
 +        return mIndexEntryRepository;
 +    }
 +
 +    /**
 +     * @return storage for this index entry
 +     * @throws SupportException
 +     * @throws RepositoryException
 +     */
 +    public Storage getStorage()
 +            throws SupportException, RepositoryException
 +    {
 +        return mIndexEntryStorage;
 +    }
 +
 +    /**
 +     * Retrieve the object for which this index entry was constructed
 +     * @param indexEntry
 +     * @throws FetchException
 +     */
 +    public Storable loadMaster(Storable indexEntry)
 +            throws FetchException
 +    {
 +        return mBuilder.loadMaster(indexEntry);
 +    }
 +
 +    /**
 +     * Creates an index entry storable and fills in all related properties from a master storable.
 +     * @return the filled in metadata storable
 +     */
 +    public Storable setAllProperties(S master)
 +            throws SupportException, RepositoryException
 +    {
 +        Storable entry = getStorage().prepare();
 +        mBuilder.setAllProperties(entry, master);
 +        return entry;
 +    }
 +
 +    public Class getSyntheticClass() {
 +        return mIndexEntryClass;
 +    }
 +
 +    public String toString() {
 +        return mBuilder.getClass().toString();
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableBuilder.java new file mode 100644 index 0000000..af44f45 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableBuilder.java @@ -0,0 +1,532 @@ +/*
 + * 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.synthetic;
 +
 +import java.util.ArrayList;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.Set;
 +import java.util.List;
 +
 +import org.cojen.classfile.ClassFile;
 +import org.cojen.classfile.MethodInfo;
 +import org.cojen.classfile.Modifiers;
 +import org.cojen.classfile.TypeDesc;
 +import org.cojen.classfile.attribute.Annotation;
 +import org.cojen.util.ClassInjector;
 +import org.cojen.util.BeanPropertyAccessor;
 +
 +import com.amazon.carbonado.Index;
 +import com.amazon.carbonado.Indexes;
 +import com.amazon.carbonado.Nullable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.SupportException;
 +import com.amazon.carbonado.Version;
 +
 +import com.amazon.carbonado.info.StorablePropertyAdapter;
 +import com.amazon.carbonado.layout.Unevolvable;
 +import com.amazon.carbonado.util.AnnotationBuilder;
 +import com.amazon.carbonado.util.AnnotationDescParser;
 +
 +/**
 + * Allows the definition of very simple synthetic storables. Only a primary key
 + * index can be defined; at least one property must be a primary key property. A
 + * property can be nullable and can be specified as the version property.
 + *
 + * This class acts both as builder factory and as builder.
 + *
 + * @author Don Schneider
 + * @author Brian S O'Neill
 + */
 +public class SyntheticStorableBuilder
 +        implements SyntheticBuilder {
 +
 +    /**
 +     * DEFAULT ClassNameProvider
 +     */
 +    private class DefaultProvider implements ClassNameProvider {
 +        DefaultProvider() {
 +        }
 +
 +        public String getName() {
 +            StringBuilder b = new StringBuilder();
 +            if (null == SyntheticStorableBuilder.this.getName()) {
 +                b.append("synth");
 +            } else {
 +                b.append(SyntheticStorableBuilder.this.getName());
 +            }
 +
 +            // Append primary keys first.
 +
 +            Iterator<String> props = SyntheticStorableBuilder.this.mPrimaryKey.getProperties();
 +            Set<String> propSet = new HashSet<String>();
 +
 +            while (props.hasNext()) {
 +                String prop = props.next();
 +                if (prop.charAt(0) != '+' && prop.charAt(0) != '-') {
 +                    propSet.add(prop);
 +                    b.append('~');
 +                } else {
 +                    propSet.add(prop.substring(1));
 +                }
 +                b.append(prop);
 +            }
 +
 +            // Append remaining properties.
 +            List<SyntheticProperty> list = SyntheticStorableBuilder.this.getPropertyList();
 +
 +            for (SyntheticProperty prop : list) {
 +                if (!propSet.contains(prop.getName())) {
 +                    b.append('~');
 +                    b.append(prop.getName());
 +                }
 +            }
 +
 +            return b.toString();
 +        }
 +
 +        public boolean isExplicit() {
 +            return true;
 +        }
 +    }
 +
 +    /**
 +     * Name for the generated storable
 +     */
 +    private String mName;
 +
 +    /**
 +     * Set of properties to add to the storable.
 +     */
 +    private List<SyntheticProperty> mPropertyList;
 +
 +    private SyntheticKey mPrimaryKey;
 +
 +    /**
 +     * List of indexes (in addition to the primary key) for this storable
 +     */
 +    private List<SyntheticIndex> mExtraIndexes;
 +
 +    /**
 +     * The partially hydrogenated classfile, together with the injector
 +     * which can make it into a class.
 +     */
 +    private StorableClassFileBuilder mClassFileGenerator;
 +
 +    /**
 +     * Lazily instanced -- this is the point of this class. See
 +     * {@link #getStorableClass}
 +     */
 +    private Class<? extends Storable> mStorableClass;
 +
 +    /**
 +     * Used to generate the classname for this class.
 +     */
 +    private ClassNameProvider mClassNameProvider;
 +
 +    /**
 +     * Class loader to use for the synthetic
 +     */
 +    private ClassLoader mLoader;
 +
 +    /**
 +     * When false, generated class implements Unevolvable.
 +     */
 +    private boolean mEvolvable = false;
 +
 +    /**
 +     * @param name base name for the generated class.  This is usually a fully qualified
 +     * name, a la "com.amazon.carbonado.storables.happy.synthetic.storable"
 +     * @param loader {@link ClassLoader} to use for the generated class
 +     */
 +    public SyntheticStorableBuilder(String name, ClassLoader loader) {
 +        mName = name;
 +        mLoader = loader;
 +        mPropertyList = new ArrayList<SyntheticProperty>();
 +        mExtraIndexes = new ArrayList<SyntheticIndex>();
 +        mClassNameProvider = new DefaultProvider();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#prepare()
 +     */
 +    public ClassFileBuilder prepare() throws SupportException {
 +        if (mPrimaryKey == null) {
 +            throw new IllegalStateException("Primary key not defined");
 +        }
 +
 +        // Clear the cached result, if any
 +        mStorableClass = null;
 +
 +        mClassFileGenerator = new StorableClassFileBuilder(
 +             mClassNameProvider,
 +             mLoader,
 +             SyntheticStorableBuilder.class,
 +             mEvolvable);
 +        ClassFile cf = mClassFileGenerator.getClassFile();
 +
 +        for (SyntheticProperty prop : mPropertyList) {
 +            definePropertyBeanMethods(cf, prop);
 +        }
 +
 +        definePrimaryKey(cf);
 +        defineIndexes(cf);
 +
 +        return mClassFileGenerator;
 +    }
 +
 +    public Class<? extends Storable> getStorableClass() {
 +        if (null == mStorableClass) {
 +            mStorableClass = mClassFileGenerator.build();
 +        }
 +        return mStorableClass;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#build()
 +     */
 +    public Class<? extends Storable> build() throws SupportException {
 +        prepare();
 +        return getStorableClass();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#addProperty(java.lang.String,
 +     *      java.lang.Class)
 +     */
 +    public SyntheticProperty addProperty(String name, Class type) {
 +        SyntheticProperty prop = new SyntheticProperty(name, type);
 +        mPropertyList.add(prop);
 +        return prop;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#addProperty(com.amazon.carbonado.synthetic.SyntheticProperty)
 +     */
 +    public SyntheticProperty addProperty(SyntheticProperty prop) {
 +        mPropertyList.add(prop);
 +        return prop;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#hasProperty(java.lang.String)
 +     */
 +    public boolean hasProperty(String name) {
 +        for (SyntheticProperty prop : mPropertyList) {
 +            if (prop.getName().equals(name)) {
 +                return true;
 +            }
 +        }
 +        return false;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#addPrimaryKey()
 +     */
 +    public SyntheticKey addPrimaryKey() {
 +        if (mPrimaryKey == null) {
 +            mPrimaryKey = new SyntheticKey();
 +        }
 +        return mPrimaryKey;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#addIndex()
 +     */
 +    public SyntheticIndex addIndex() {
 +        SyntheticIndex index = new SyntheticIndex();
 +        mExtraIndexes.add(index);
 +        return index;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#isVersioned()
 +     */
 +    public boolean isVersioned() {
 +        for (SyntheticProperty prop : mPropertyList) {
 +            if (prop.isVersion()) {
 +                return true;
 +            }
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * @return Returns the classNameProvider.
 +     */
 +    public ClassNameProvider getClassNameProvider() {
 +        return mClassNameProvider;
 +    }
 +
 +    /**
 +     * @param classNameProvider
 +     *            The classNameProvider to set.
 +     */
 +    public void setClassNameProvider(ClassNameProvider classNameProvider) {
 +        mClassNameProvider = classNameProvider;
 +    }
 +
 +    /**
 +     * By default, generated storable implements the Unevolvable marker
 +     * interface, which can affect how it is encoded. It usually does not make
 +     * sense to support storable evolution new versions can be (and often will
 +     * be) given different names.
 +     *
 +     * <p>Pass in true to change from the default behavior, and not implement
 +     * Unevolvable. When doing so, a ClassNameProvider should also be provided
 +     * to ensure consistent naming which does not include property names.
 +     */
 +    public void setEvolvable(boolean evolvable) {
 +        mEvolvable = evolvable;
 +    }
 +
 +    /**
 +     * Utility accessor to ease getting access to the properties of the
 +     * generated class
 +     */
 +    public BeanPropertyAccessor getAccessor() throws SupportException {
 +        return BeanPropertyAccessor.forClass(getStorableClass());
 +    }
 +
 +    /**
 +     * Decorate a classfile with the @PrimaryKey for this synthetic storable.
 +     *
 +     * @param cf ClassFile to decorate
 +     */
 +    private void definePrimaryKey(ClassFile cf) {
 +        if (mPrimaryKey == null) {
 +            return;
 +        }
 +
 +        // Add primary key annotation
 +        //
 +        // @PrimaryKey(value={"+/-propName", "+/-propName", ...})
 +
 +        Annotation pk = cf.addRuntimeVisibleAnnotation(TypeDesc.forClass(PrimaryKey.class));
 +
 +        Annotation.MemberValue[] props =
 +            new Annotation.MemberValue[mPrimaryKey.getPropertyCount()];
 +        pk.putMemberValue("value", props);
 +        int propPos = 0;
 +        Iterator<String> propNames = mPrimaryKey.getProperties();
 +        while (propNames.hasNext()) {
 +            String propName = propNames.next();
 +            props[propPos] = pk.makeMemberValue(propName);
 +            propPos++;
 +        }
 +    }
 +
 +    /**
 +     * Decorate a classfile with the @Indexes for this synthetic storable.
 +     *
 +     * @param cf ClassFile to decorate
 +     */
 +    private void defineIndexes(ClassFile cf) {
 +        if (mExtraIndexes.size() == 0) {
 +            return;
 +        }
 +
 +        // Add indexes annotation
 +        //
 +        // @Indexes(value={
 +        //     @Index(value={"+/-propName", "+/-propName", ...})
 +        // })
 +
 +        Annotation indexSet = cf.addRuntimeVisibleAnnotation(TypeDesc.forClass(Indexes.class));
 +
 +        Annotation.MemberValue[] indexes = new Annotation.MemberValue[mExtraIndexes.size()];
 +
 +        // indexSet.value -> indexes
 +        indexSet.putMemberValue("value", indexes);
 +
 +        int position = 0;
 +        for (SyntheticIndex extraIndex : mExtraIndexes) {
 +            Annotation index = addIndex(indexSet, indexes, position++);
 +
 +            Annotation.MemberValue[] indexProps =
 +                new Annotation.MemberValue[extraIndex.getPropertyCount()];
 +            index.putMemberValue("value", indexProps);
 +            int propPos = 0;
 +            Iterator<String> propNames = extraIndex.getProperties();
 +            while (propNames.hasNext()) {
 +                String propName = propNames.next();
 +                indexProps[propPos] = index.makeMemberValue(propName);
 +                propPos++;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * @param annotator
 +     *            source of annotation (eg, makeAnnotation and makeMemberValue)
 +     * @param indexes
 +     * @param position
 +     * @return
 +     */
 +    private Annotation addIndex(Annotation annotator,
 +                                Annotation.MemberValue[] indexes,
 +                                int position)
 +    {
 +        assert (indexes.length > position);
 +
 +        Annotation index = annotator.makeAnnotation();
 +        index.setType(TypeDesc.forClass(Index.class));
 +        indexes[position] = annotator.makeMemberValue(index);
 +        return index;
 +    }
 +
 +    /**
 +     * Add the get & set methods for this property
 +     *
 +     * @return true if version property was added
 +     */
 +    protected boolean definePropertyBeanMethods(ClassFile cf,
 +                                                SyntheticProperty property)
 +    {
 +        TypeDesc propertyType = TypeDesc.forClass(property.getType());
 +        // Add property get method.
 +        final MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_ABSTRACT,
 +                                           property.getReadMethodName(),
 +                                           propertyType,
 +                                           null);
 +        if (property.isNullable()) {
 +            mi.addRuntimeVisibleAnnotation(TypeDesc.forClass(Nullable.class));
 +        }
 +        boolean versioned = false;
 +        if (property.isVersion()) {
 +            mi.addRuntimeVisibleAnnotation(TypeDesc.forClass(Version.class));
 +            versioned = true;
 +        }
 +
 +        if (property.getAdapter() != null) {
 +            StorablePropertyAdapter adapter = property.getAdapter();
 +            Annotation ann = mi.addRuntimeVisibleAnnotation
 +                (TypeDesc.forClass(adapter.getAnnotation().getAnnotationType()));
 +            java.lang.annotation.Annotation jann = adapter.getAnnotation().getAnnotation();
 +            if (jann != null) {
 +                new AnnotationBuilder().visit(jann, ann);
 +            }
 +        }
 +
 +        List<String> annotationDescs = property.getAccessorAnnotationDescriptors();
 +        if (annotationDescs != null && annotationDescs.size() > 0) {
 +            for (String desc : annotationDescs) {
 +                new AnnotationDescParser(desc) {
 +                    protected Annotation buildRootAnnotation(TypeDesc rootAnnotationType) {
 +                        return mi.addRuntimeVisibleAnnotation(rootAnnotationType);
 +                    }
 +                }.parse(null);
 +            }
 +        }
 +
 +        // Add property set method.
 +        cf.addMethod(Modifiers.PUBLIC_ABSTRACT,
 +                     property.getWriteMethodName(),
 +                     null,
 +                     new TypeDesc[] { propertyType });
 +
 +        return versioned;
 +    }
 +
 +    /**
 +     * Frequently used by the {@link SyntheticBuilder.ClassNameProvider} as a
 +     * basis for the generated classname
 +     * @return builder name
 +     */
 +    protected String getName() {
 +        return mName;
 +    }
 +
 +    /**
 +     * Frequently used by the {@link SyntheticBuilder.ClassNameProvider} as a
 +     * basis for the generated classname
 +     * @return properties for this storable
 +     */
 +    protected List<SyntheticProperty> getPropertyList() {
 +        return mPropertyList;
 +    }
 +
 +    public String toString() {
 +        return mName + mPropertyList.toString();
 +    }
 +
 +    /**
 +     * This really belongs in Cojen -- it's just the injector and classfile,
 +     * together again
 +     *
 +     * @author Don Schneider
 +     *
 +     */
 +    static class StorableClassFileBuilder extends ClassFileBuilder {
 +
 +        /**
 +         * Initialize the injector and classfile, defining the basic information
 +         * for a synthetic class
 +         *
 +         * @param className
 +         *            name with which to christen this class -- must be globally
 +         *            unique
 +         * @param sourceClass
 +         *            class to credit with creating the class, usually the class
 +         *            making the call
 +         */
 +        StorableClassFileBuilder(ClassNameProvider nameProvider,
 +                                 ClassLoader loader,
 +                                 Class sourceClass,
 +                                 boolean evolvable) {
 +            String className = nameProvider.getName();
 +
 +            if (nameProvider.isExplicit()) {
 +                mInjector = ClassInjector.createExplicit(className, loader);
 +            } else {
 +                mInjector = ClassInjector.create(className, loader);
 +            }
 +
 +            mClassFile = new ClassFile(mInjector.getClassName());
 +
 +            Modifiers modifiers = mClassFile.getModifiers().toAbstract(true);
 +            mClassFile.setModifiers(modifiers);
 +            mClassFile.addInterface(Storable.class);
 +            if (!evolvable) {
 +                mClassFile.addInterface(Unevolvable.class);
 +            }
 +            mClassFile.markSynthetic();
 +            mClassFile.setSourceFile(sourceClass.getName());
 +            mClassFile.setTarget("1.5");
 +
 +            mClassFile.addDefaultConstructor();
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java new file mode 100644 index 0000000..443d826 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java @@ -0,0 +1,730 @@ +/*
 + * 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.synthetic;
 +
 +import java.lang.reflect.Method;
 +import java.lang.reflect.UndeclaredThrowableException;
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.Comparator;
 +import java.util.Iterator;
 +import java.util.LinkedHashSet;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.Join;
 +import com.amazon.carbonado.Nullable;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.SupportException;
 +import com.amazon.carbonado.info.Direction;
 +import com.amazon.carbonado.info.StorableInfo;
 +import com.amazon.carbonado.info.StorableIntrospector;
 +import com.amazon.carbonado.info.StorableProperty;
 +import com.amazon.carbonado.spi.CodeBuilderUtil;
 +import com.amazon.carbonado.util.ThrowUnchecked;
 +
 +import org.cojen.classfile.ClassFile;
 +import org.cojen.classfile.CodeBuilder;
 +import org.cojen.classfile.Label;
 +import org.cojen.classfile.LocalVariable;
 +import org.cojen.classfile.MethodInfo;
 +import org.cojen.classfile.Modifiers;
 +import org.cojen.classfile.TypeDesc;
 +import org.cojen.util.BeanComparator;
 +
 +/**
 + * A SyntheticStorableReference defines new kinds of Storables from an existing
 + * master storable. This is used in situations when additional information about
 + * a storable needs to be tracked -- eg, for an index, or for caching. The
 + * storable may optionally have completely new, synthetic properties added.
 + *
 + * <P>
 + * All primary key properties of the master storable will also be provided by the
 + * derived storable. Three special methods will be provided:
 + * <ul>
 + * <li>getMaster - retrieves the original storable</li>
 + * <li>setAllProperties - sets the properties the syntheticReference has in
 + * common with the master to the values of the master instance</li>
 + * <li>isConsistent - verifies that the properties the syntheticReference has
 + * in common with the master are consistent with an instance of the master,
 + * meaning that they are in the same state and, if set, equal.</li>
 + * </ul>
 + *
 + * @author Brian S O'Neill
 + * @author Don Schneider
 + */
 +public class SyntheticStorableReferenceBuilder<S extends Storable>
 +        implements SyntheticBuilder {
 +
 +    // The property setter will be called something like "setAllProperties_0"
 +    private static final String ALL_PROPERTIES_PREFIX = "setAllProperties_";
 +
 +    private static final String MASTER_PROPERTY_PREFIX = "master_";
 +
 +    private static final String IS_CONSISTENT_PREFIX = "getIsConsistent_";
 +
 +    // Information about the storable from which this one is derived
 +    // private StorableInfo<S> mBaseStorableInfo;
 +    private Class mMasterStorableClass;
 +
 +    // Stashed copy of the results of calling StorableIntrospector.examine(...)
 +    // on the master storable class.
 +    private StorableInfo<S> mMasterStorableInfo;
 +
 +    // This guy will actually do the work
 +    private SyntheticStorableBuilder mBuilder;
 +
 +    // Primary key of generated storable.
 +    private SyntheticKey mPrimaryKey;
 +
 +    // Elements added to primary key to ensure uniqueness
 +    private Set<String> mExtraPkProps;
 +
 +    // True if the specified properties for this derived reference class are sufficient to
 +    // uniquely identify a unique instance of the referent.
 +    private boolean mIsUnique = true;
 +
 +    // The result of building
 +    private Class<? extends Storable> mSyntheticClass;
 +
 +    // The list of properties explicitly added to this reference builder
 +    private List<SyntheticProperty> mUserProps;
 +
 +    // This list of properties the master and this one have in common
 +    // The StorableProperties that get added to this list
 +    // are retrieved from the master.
 +    private List<StorableProperty> mCommonProps;
 +
 +    // The builder generates and retains a reference to various
 +    // methods which make it possible to implement the "StorableReferenceBean"
 +    // interface
 +    // (if there were any need to formalize it): loadMaster,
 +    // copyCommonProperties,
 +    // setAllProperties, and isConsistent
 +    private String mNameForSetAllPropertiesMethod;
 +
 +    private Method mSetAllPropertiesMethod;
 +
 +    private String mNameForIsConsistentMethod;
 +
 +    private Method mIsConsistentMethod;
 +
 +    private String mNameForGetMasterMethod;
 +
 +    private Method mGetMasterMethod;
 +
 +    private Comparator<? extends Storable> mComparator;
 +
 +    /**
 +     * @param storableClass
 +     *            class of the storable that will be referenced by this
 +     *            synthetic. The name for the synthetic storable will be based
 +     *            on this class's name, decorated with the properties which
 +     *            participate in the primary key for the synthetic storable.
 +     */
 +    public SyntheticStorableReferenceBuilder(Class<S> storableClass,
 +                                             boolean isUnique) {
 +        this(storableClass, null, isUnique);
 +    }
 +
 +    /**
 +     * @param storableClass
 +     *            class of the storable that will be referenced by this
 +     *            synthetic
 +     * @param baseName
 +     *            of the generated synthetic. Note that for some repositories
 +     *            this name will be visible across the entire repository, so it
 +     *            is good practice to include namespace information to guarantee
 +     *            uniqueness.
 +     * @param isUnique
 +     *             true if the properties that are explicitly identified as primary
 +     *             key properites are sufficient to uniquely identify the index object.
 +     */
 +    public SyntheticStorableReferenceBuilder(Class<S> storableClass,
 +                                             final String baseName,
 +                                             boolean isUnique) {
 +        mMasterStorableClass = storableClass;
 +        // Stash this away for later reference
 +        mMasterStorableInfo = StorableIntrospector.examine(storableClass);
 +
 +        mIsUnique = isUnique;
 +        mBuilder = new SyntheticStorableBuilder(storableClass.getCanonicalName()
 +                                                + (baseName != null? "_" + baseName : ""),
 +                                                storableClass.getClassLoader());
 +        mBuilder.setClassNameProvider(new ReferenceClassNameProvider(isUnique));
 +
 +        mPrimaryKey = mBuilder.addPrimaryKey();
 +        mExtraPkProps = new LinkedHashSet<String>();
 +
 +        mUserProps = new ArrayList<SyntheticProperty>();
 +        mCommonProps = new ArrayList<StorableProperty>();
 +
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#prepare()
 +     */
 +    public ClassFileBuilder prepare() throws SupportException {
 +
 +        // Add a property to the reference for each primary key in the master
 +        List<StorableProperty> masterPkProps = new ArrayList<StorableProperty>
 +            (mMasterStorableInfo.getPrimaryKeyProperties().values());
 +
 +        // Sort master primary keys, to ensure consistent behavior.
 +        Collections.sort(masterPkProps,
 +                         BeanComparator.forClass(StorableProperty.class).orderBy("name"));
 +
 +        for (StorableProperty masterPkProp : masterPkProps) {
 +            // Some of them may have already been added as explicit
 +            // primary keys, to articulate sort direction.
 +            if (!mBuilder.hasProperty(masterPkProp.getName())) {
 +                addProperty(masterPkProp);
 +                // For non-unique indexes, *all* master pk properties are members of the
 +                // generated primary key, in order to support duplicates.
 +                if (!mIsUnique) {
 +                    mPrimaryKey.addProperty(masterPkProp.getName());
 +                    mExtraPkProps.add(masterPkProp.getName());
 +                }
 +            }
 +        }
 +
 +        // Ensure that a version property exists in the index entry, if the
 +        // user storable had one.
 +        if (!mBuilder.isVersioned()) {
 +            StorableProperty versionProperty = mMasterStorableInfo.getVersionProperty();
 +            if (versionProperty != null) {
 +                addProperty(versionProperty);
 +            }
 +        }
 +
 +        ClassFileBuilder cfg = mBuilder.prepare();
 +        addSpecialMethods(cfg.getClassFile());
 +        return cfg;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#getStorableClass()
 +     */
 +    public Class<? extends Storable> getStorableClass() throws IllegalStateException {
 +        if (null == mSyntheticClass) {
 +            mSyntheticClass = mBuilder.getStorableClass();
 +
 +            // We need a comparator which follows the same order as the generated
 +            // storable.  We can't construct it until we get here
 +            {
 +                BeanComparator bc = BeanComparator.forClass(mSyntheticClass);
 +                Iterator<String> props = mPrimaryKey.getProperties();
 +                while (props.hasNext()) {
 +                    String prop = props.next();
 +                    // BeanComparator knows how to handle the '+' or '-' prefix.
 +                    bc = bc.orderBy(prop);
 +                    bc = bc.caseSensitive();
 +                }
 +                mComparator = bc;
 +            }
 +        }
 +        return mSyntheticClass;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#build()
 +     */
 +    public Class<? extends Storable> build() throws SupportException {
 +        prepare();
 +        return getStorableClass();
 +    }
 +
 +    /**
 +     * Add a property to the primary key which is a member of the Storable type
 +     * being referenced by this one.
 +     *
 +     * @param name
 +     */
 +    public SyntheticProperty addKeyProperty(String name, Direction direction) {
 +        StorableProperty<S> prop = mMasterStorableInfo.getAllProperties().get(name);
 +        if (null == prop) {
 +            throw new IllegalArgumentException(name + " is not a property of "
 +                    + mMasterStorableInfo.getName());
 +        }
 +
 +        mPrimaryKey.addProperty(name, direction);
 +
 +        SyntheticProperty result = addProperty(prop);
 +        mUserProps.add(result);
 +        return result;
 +    }
 +
 +    /**
 +     * @see com.amazon.carbonado.synthetic.SyntheticStorableBuilder#addProperty(java.lang.String,
 +     *      java.lang.Class)
 +     */
 +    public SyntheticProperty addProperty(String name, Class type) {
 +        SyntheticProperty result = mBuilder.addProperty(name, type);
 +        mUserProps.add(result);
 +        return result;
 +    }
 +
 +    /**
 +     * @see com.amazon.carbonado.synthetic.SyntheticStorableBuilder#addProperty(com.amazon.carbonado.synthetic.SyntheticProperty)
 +     */
 +    public SyntheticProperty addProperty(SyntheticProperty prop) {
 +        SyntheticProperty result = mBuilder.addProperty(prop);
 +        mUserProps.add(result);
 +        return result;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#hasProperty(java.lang.String)
 +     */
 +    public boolean hasProperty(String name) {
 +        return mBuilder.hasProperty(name);
 +    }
 +
 +    /**
 +     * @return Returns the indexProps.
 +     */
 +    public List<SyntheticProperty> getUserProps() {
 +        return mUserProps;
 +    }
 +
 +    public SyntheticKey addPrimaryKey() {
 +        return mBuilder.addPrimaryKey();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#addIndex() Note that
 +     *      using this method for a SyntheticReference being used as an index is
 +     *      not well defined.
 +     */
 +    public SyntheticIndex addIndex() {
 +        return mBuilder.addIndex();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#getName()
 +     */
 +    public Object getName() {
 +        return mBuilder.getName();
 +    }
 +
 +    /**
 +     * True if the generated derived class should be considered unique. If
 +     * non-unique, all properties are added to the primary key so there will be
 +     * no conflicts between various derived classes derived from the same base
 +     * storable.
 +     */
 +    public boolean isUnique() {
 +        return mIsUnique;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     *
 +     * @see com.amazon.carbonado.synthetic.SyntheticBuilder#isVersioned()
 +     */
 +    public boolean isVersioned() {
 +        return mBuilder.isVersioned();
 +    }
 +
 +    /**
 +     * Loads the master object referenced by the given index entry.
 +     *
 +     * @param indexEntry
 +     *            index entry which points to master
 +     * @return master or null if missing
 +     */
 +    public S loadMaster(Storable indexEntry) throws FetchException {
 +        if (null == mGetMasterMethod) {
 +            try {
 +                mGetMasterMethod = getStorableClass().getMethod(mNameForGetMasterMethod,
 +                                                                (Class[]) null);
 +            } catch (NoSuchMethodException e) {
 +                throw new UndeclaredThrowableException(e);
 +            }
 +        }
 +
 +        try {
 +            return (S) mGetMasterMethod.invoke(indexEntry, (Object[]) null);
 +        } catch (Exception e) {
 +            ThrowUnchecked.fireFirstDeclaredCause(e, FetchException.class);
 +            // Not reached.
 +            return null;
 +        }
 +    }
 +
 +    /**
 +     * Sets all the properties of the given index entry, using the applicable
 +     * properties of the given master.
 +     *
 +     * @param indexEntry
 +     *            index entry whose properties will be set
 +     * @param master
 +     *            source of property values
 +     */
 +    public void setAllProperties(Storable indexEntry, S master) {
 +
 +        if (null == mSetAllPropertiesMethod) {
 +            try {
 +                mSetAllPropertiesMethod = mSyntheticClass.getMethod(mNameForSetAllPropertiesMethod,
 +                                                                    mMasterStorableClass);
 +            } catch (NoSuchMethodException e) {
 +                throw new UndeclaredThrowableException(e);
 +            }
 +        }
 +
 +        try {
 +            mSetAllPropertiesMethod.invoke(indexEntry, master);
 +        } catch (Exception e) {
 +            ThrowUnchecked.fireFirstDeclaredCause(e);
 +        }
 +    }
 +
 +    /**
 +     * Returns true if the properties of the given index entry match those
 +     * contained in the master, excluding any version property. This will
 +     * always return true after a call to setAllProperties.
 +     *
 +     * @param indexEntry
 +     *            index entry whose properties will be tested
 +     * @param master
 +     *            source of property values
 +     */
 +    public boolean isConsistent(Storable indexEntry, S master) {
 +
 +        if (null == mIsConsistentMethod) {
 +            try {
 +                mIsConsistentMethod = mSyntheticClass.getMethod(mNameForIsConsistentMethod,
 +                                                                mMasterStorableClass);
 +            } catch (NoSuchMethodException e) {
 +                throw new UndeclaredThrowableException(e);
 +            }
 +        }
 +
 +        try {
 +            return (Boolean) mIsConsistentMethod.invoke(indexEntry, master);
 +        } catch (Exception e) {
 +            ThrowUnchecked.fireFirstDeclaredCause(e);
 +            // Not reached.
 +            return false;
 +        }
 +    }
 +
 +    /**
 +     * Returns a comparator for ordering index entries.
 +     */
 +    public Comparator<? extends Storable> getComparator() {
 +        return mComparator;
 +    }
 +
 +    /**
 +     * Create methods for copying properties to and from and for looking up the
 +     * master object
 +     *
 +     * @throws amazon.carbonado.SupportException
 +     */
 +    private void addSpecialMethods(ClassFile cf) throws SupportException {
 +        TypeDesc masterStorableType = TypeDesc.forClass(mMasterStorableClass);
 +        // The set master method is used to load the master object and set it
 +        // into the master property of this reference storable
 +        String nameForSetMasterMethod;
 +
 +        // Generate safe names for special methods.
 +        {
 +            String safeName = generateSafePropertyName(mMasterStorableInfo,
 +                                                       MASTER_PROPERTY_PREFIX);
 +            // Don't need to pass the class in, it's not a boolean so we'll get
 +            // a "get<foo>" flavor read method
 +            mNameForGetMasterMethod = SyntheticProperty.makeReadMethodName(safeName,
 +                                                                           Object.class);
 +            nameForSetMasterMethod = SyntheticProperty.makeWriteMethodName(safeName);
 +            mNameForSetAllPropertiesMethod = generateSafeMethodName(mMasterStorableInfo,
 +                                                                    ALL_PROPERTIES_PREFIX);
 +            mNameForIsConsistentMethod = generateSafeMethodName(mMasterStorableInfo, IS_CONSISTENT_PREFIX);
 +        }
 +
 +        // Add a method which sets all properties of index entry object from
 +        // master object.
 +        {
 +            // void setAllProperties(Storable master)
 +            TypeDesc[] params = new TypeDesc[] { masterStorableType };
 +            MethodInfo mi = cf.addMethod(Modifiers.PUBLIC,
 +                                         mNameForSetAllPropertiesMethod,
 +                                         null,
 +                                         params);
 +            CodeBuilder b = new CodeBuilder(mi);
 +
 +            // Set Join property: this.setMaster(master)
 +            // (stash a reference to the master object)
 +            b.loadThis();
 +            b.loadLocal(b.getParameter(0));
 +            b.invokeVirtual(nameForSetMasterMethod, null, params);
 +
 +            // Copy across all the properties. At this point they've
 +            // all been added to the property list
 +            for (StorableProperty prop : mCommonProps) {
 +                if (prop.isPrimaryKeyMember()) {
 +                    // No need to set this property, since setting join to
 +                    // master already took care of it.
 +                    continue;
 +                }
 +                b.loadThis();
 +                b.loadLocal(b.getParameter(0));
 +                if (prop.getReadMethod() == null) {
 +                    throw new SupportException("Property does not have a public accessor method: "
 +                            + prop);
 +                }
 +                b.invoke(prop.getReadMethod());
 +                b.invokeVirtual(prop.getWriteMethodName(),
 +                                null,
 +                                new TypeDesc[] { TypeDesc.forClass(prop.getType()) });
 +
 +            }
 +            b.returnVoid();
 +        }
 +
 +        // Add a join property for looking up master object. Since binding is
 +        // based on primary key properties which match to index entry object,
 +        // the join is natural -- no need to specify internal and external
 +        // properties.
 +        {
 +            MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_ABSTRACT,
 +                                         mNameForGetMasterMethod,
 +                                         masterStorableType,
 +                                         null);
 +            mi.addException(TypeDesc.forClass(FetchException.class));
 +            mi.addRuntimeVisibleAnnotation(TypeDesc.forClass(Join.class));
 +            mi.addRuntimeVisibleAnnotation(TypeDesc.forClass(Nullable.class));
 +
 +            cf.addMethod(Modifiers.PUBLIC_ABSTRACT,
 +                         nameForSetMasterMethod,
 +                         null,
 +                         new TypeDesc[] { masterStorableType });
 +        }
 +
 +        // Add a method which tests all properties of index entry object
 +        // against master object, excluding the version property.
 +        {
 +            TypeDesc[] params = new TypeDesc[] {masterStorableType};
 +            MethodInfo mi = cf.addMethod
 +                (Modifiers.PUBLIC, mNameForIsConsistentMethod, TypeDesc.BOOLEAN, params);
 +            CodeBuilder b = new CodeBuilder(mi);
 +
 +            for (StorableProperty prop : mCommonProps) {
 +                if (prop.isVersion()) {
 +                    continue;
 +                }
 +                Label propsAreEqual = b.createLabel();
 +                addPropertyTest(b, prop, b.getParameter(0), propsAreEqual);
 +                propsAreEqual.setLocation();
 +            }
 +
 +            b.loadConstant(true);
 +            b.returnValue(TypeDesc.BOOLEAN);
 +        }
 +
 +
 +
 +    }
 +
 +    /**
 +     * Generate code to test equality of two properties.
 +     * @param b
 +     * @param property
 +     * @param otherInstance
 +     * @param propsAreEqual
 +     */
 +    private static void addPropertyTest(CodeBuilder b,
 +                                        StorableProperty<?> property,
 +                                        LocalVariable otherInstance,
 +                                        Label propsAreEqual)
 +    {
 +        TypeDesc propertyType = TypeDesc.forClass(property.getType());
 +
 +        b.loadThis();
 +        b.invokeVirtual(property.getReadMethodName(), propertyType, null);
 +        b.loadLocal(otherInstance);
 +        b.invoke(property.getReadMethod());
 +        CodeBuilderUtil.addValuesEqualCall(b,
 +                                           propertyType,
 +                                           true,  // test for null
 +                                           propsAreEqual,
 +                                           true); // branch to propsAreEqual when equal
 +
 +        // Property values differ, so return false.
 +        b.loadConstant(false);
 +        b.returnValue(TypeDesc.BOOLEAN);
 +    }
 +
 +    /**
 +     * Generates a property name which doesn't clash with any already defined.
 +     */
 +    private String generateSafeMethodName(StorableInfo info, String prefix) {
 +        Class type = info.getStorableType();
 +
 +//        if (!methodExists(type, prefix)) {
 +//            return prefix;
 +//        }
 +
 +        // Try a few times to generate a unique name. There's nothing special
 +        // about choosing 100 as the limit.
 +        int value = 0;
 +        for (int i = 0; i < 100; i++) {
 +            String name = prefix + value;
 +            if (!methodExists(type, name)) {
 +                return name;
 +            }
 +            value = name.hashCode();
 +        }
 +
 +        throw new InternalError("Unable to create unique method name starting with: "
 +                + prefix);
 +    }
 +
 +    /**
 +     * Generates a property name which doesn't clash with any already defined.
 +     */
 +    protected String generateSafePropertyName(StorableInfo info, String prefix)
 +    {
 +        Map<String, ? extends StorableProperty> properties = info.getAllProperties();
 +        Class type = info.getStorableType();
 +
 +        // Try a few times to generate unique name. There's nothing special
 +        // about choosing 100 as the limit.
 +        for (int i = 0; i < 100; i++) {
 +            String name = prefix + i;
 +            if (properties.containsKey(name)) {
 +                continue;
 +            }
 +            if (methodExists(type, SyntheticProperty.makeReadMethodName(name,
 +                                                                        type))) {
 +                continue;
 +            }
 +            if (methodExists(type, SyntheticProperty.makeWriteMethodName(name))) {
 +                continue;
 +            }
 +            return name;
 +        }
 +
 +        throw new InternalError("Unable to create unique property name starting with: "
 +                + prefix);
 +    }
 +
 +    /**
 +     * Look for conflicting method names
 +     */
 +    private boolean methodExists(Class clazz, String name) {
 +        Method[] methods = clazz.getDeclaredMethods();
 +        for (int i = 0; i < methods.length; i++) {
 +            if (methods[i].getName().equals(name)) {
 +                return true;
 +            }
 +        }
 +
 +        if (clazz.getSuperclass() != null
 +                && methodExists(clazz.getSuperclass(), name)) {
 +            return true;
 +        }
 +
 +        Class[] interfaces = clazz.getInterfaces();
 +        for (int i = 0; i < interfaces.length; i++) {
 +            if (methodExists(interfaces[i], name)) {
 +                return true;
 +            }
 +        }
 +
 +        return false;
 +    }
 +
 +    /**
 +     * @param prop
 +     *            StorableProperty to add to the list of properties to generate.
 +     *            This method will set the nullable and version annotations, but
 +     *            not the primary key or direction annotations. Warning: this
 +     *            should only be called with properties that are also in the
 +     *            master
 +     * @return resulting SyntheticProperty
 +     */
 +    private SyntheticProperty addProperty(StorableProperty prop) {
 +        SyntheticProperty refProp = mBuilder.addProperty(prop.getName(),
 +                                                         prop.getType());
 +        refProp.setReadMethodName(prop.getReadMethodName());
 +        refProp.setWriteMethodName(prop.getWriteMethodName());
 +        refProp.setIsNullable(prop.isNullable());
 +        refProp.setIsVersion(prop.isVersion());
 +
 +        // If property has an adapter, make sure it is still applied here.
 +        if (prop.getAdapter() != null) {
 +            refProp.setAdapter(prop.getAdapter());
 +        }
 +        mCommonProps.add(prop);
 +        return refProp;
 +    }
 +
 +    /**
 +     * Mechanism for making the name correspond to the semantics of a reference
 +     * (an index, really)
 +     */
 +    class ReferenceClassNameProvider implements ClassNameProvider {
 +        boolean mUniquely;
 +
 +        public ReferenceClassNameProvider(boolean unique) {
 +            super();
 +            mUniquely = unique;
 +        }
 +
 +        public String getName() {
 +            StringBuilder b = new StringBuilder();
 +            b.append(SyntheticStorableReferenceBuilder.this.getName());
 +            b.append('~');
 +            b.append(mUniquely ? 'U' : 'N');
 +
 +            Iterator<String> props =
 +                SyntheticStorableReferenceBuilder.this.mPrimaryKey.getProperties();
 +
 +            while (props.hasNext()) {
 +                String prop = props.next();
 +                if (mExtraPkProps.contains(prop)) {
 +                    continue;
 +                }
 +                if (prop.charAt(0) != '+' && prop.charAt(0) != '-') {
 +                    b.append('~');
 +                }
 +                b.append(prop);
 +            }
 +            return b.toString();
 +        }
 +
 +        public boolean isExplicit() {
 +            return true;
 +        }
 +    }
 +}
 +
 diff --git a/src/main/java/com/amazon/carbonado/synthetic/package-info.java b/src/main/java/com/amazon/carbonado/synthetic/package-info.java new file mode 100644 index 0000000..24a33f5 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/synthetic/package-info.java @@ -0,0 +1,23 @@ +/*
 + * 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.
 + */
 +
 +/**
 + * Support for dynamic creation of storables, intended for internal use by
 + * repository implementations.
 + */
 +package com.amazon.carbonado.synthetic;
  | 
