From 60e2cc6786962037e5c6ee09b7dc3eae38ee5db7 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 30 Aug 2006 02:11:33 +0000 Subject: Add support for creating synthetic Storables --- .../carbonado/synthetic/ClassFileBuilder.java | 60 ++ .../amazon/carbonado/synthetic/StorableBean.java | 200 ++++++ .../carbonado/synthetic/SyntheticBuilder.java | 114 ++++ .../amazon/carbonado/synthetic/SyntheticIndex.java | 31 + .../amazon/carbonado/synthetic/SyntheticKey.java | 31 + .../carbonado/synthetic/SyntheticProperty.java | 270 ++++++++ .../carbonado/synthetic/SyntheticPropertyList.java | 91 +++ .../synthetic/SyntheticReferenceAccessor.java | 100 +++ .../synthetic/SyntheticStorableBuilder.java | 532 +++++++++++++++ .../SyntheticStorableReferenceBuilder.java | 730 +++++++++++++++++++++ .../amazon/carbonado/synthetic/package-info.java | 23 + 11 files changed, 2182 insertions(+) create mode 100644 src/main/java/com/amazon/carbonado/synthetic/ClassFileBuilder.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/StorableBean.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticBuilder.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticIndex.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticKey.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticProperty.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticPropertyList.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticReferenceAccessor.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableBuilder.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java create mode 100644 src/main/java/com/amazon/carbonado/synthetic/package-info.java 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. + * + *

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> implements Storable { + 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 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 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 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 { + 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 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(4); + } + mAnnotationDescs.add(annotationDesc); + } + + /** + * Returns all the added accessor annotation descriptors in an unmodifiable list. + */ + public List 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 mPropertyList; + + SyntheticPropertyList() { + mPropertyList = new ArrayList(); + } + + /** + * 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 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 { + private Repository mIndexEntryRepository; + private Storage mIndexEntryStorage; + private Class mIndexEntryClass; + + private SyntheticStorableReferenceBuilder mBuilder; + + + public SyntheticReferenceAccessor(Repository repo, + SyntheticStorableReferenceBuilder 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 props = SyntheticStorableBuilder.this.mPrimaryKey.getProperties(); + Set propSet = new HashSet(); + + 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 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 mPropertyList; + + private SyntheticKey mPrimaryKey; + + /** + * List of indexes (in addition to the primary key) for this storable + */ + private List 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 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(); + mExtraIndexes = new ArrayList(); + 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 getStorableClass() { + if (null == mStorableClass) { + mStorableClass = mClassFileGenerator.build(); + } + return mStorableClass; + } + + /* + * (non-Javadoc) + * + * @see com.amazon.carbonado.synthetic.SyntheticBuilder#build() + */ + public Class 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. + * + *

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 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 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 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 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. + * + *

+ * All primary key properties of the master storable will also be provided by the + * derived storable. Three special methods will be provided: + *

+ * + * @author Brian S O'Neill + * @author Don Schneider + */ +public class SyntheticStorableReferenceBuilder + 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 mBaseStorableInfo; + private Class mMasterStorableClass; + + // Stashed copy of the results of calling StorableIntrospector.examine(...) + // on the master storable class. + private StorableInfo 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 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 mSyntheticClass; + + // The list of properties explicitly added to this reference builder + private List 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 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 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 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 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(); + + mUserProps = new ArrayList(); + mCommonProps = new ArrayList(); + + } + + /* + * (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 masterPkProps = new ArrayList + (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 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 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 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 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 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 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" 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 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 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; -- cgit v1.2.3