diff options
author | Brian S. O'Neill <bronee@gmail.com> | 2006-08-30 02:11:33 +0000 |
---|---|---|
committer | Brian S. O'Neill <bronee@gmail.com> | 2006-08-30 02:11:33 +0000 |
commit | 60e2cc6786962037e5c6ee09b7dc3eae38ee5db7 (patch) | |
tree | 480b4edc0d8e95e3869682d9cdafd0501dc732c9 | |
parent | 41e062b7896bfae5ecd1d75f7b99305b7b2e6ce8 (diff) |
Add support for creating synthetic Storables
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;
|