/* * Copyright 2006-2012 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 com.amazon.carbonado.AlternateKeys; import com.amazon.carbonado.Index; import com.amazon.carbonado.Indexes; import com.amazon.carbonado.Key; import com.amazon.carbonado.Name; 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 * @author David Rosenstrauch */ 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 alternate keys for this storable */ private List<SyntheticKey> mAlternateKeys; /** * List of indexes (in addition to the primary and alternate keys) 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>(); mAlternateKeys = new ArrayList<SyntheticKey>(); 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); defineAlternateKeys(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#addAlternateKey() */ public SyntheticKey addAlternateKey() { SyntheticKey alternateKey = new SyntheticKey(); mAlternateKeys.add(alternateKey); return alternateKey; } /* * (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; } /** * 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 @AlternateKeys for this synthetic storable. * * @param cf ClassFile to decorate */ private void defineAlternateKeys(ClassFile cf) { // Add alternate keys annotation // // @AlternateKeys(value={ // @Key(value={"+/-propName", "+/-propName", ...}) // }) defineIndexes(cf, mAlternateKeys, AlternateKeys.class, Key.class); } /** * Decorate a classfile with the @Indexes for this synthetic storable. * * @param cf ClassFile to decorate */ private void defineIndexes(ClassFile cf) { // Add indexes annotation // // @Indexes(value={ // @Index(value={"+/-propName", "+/-propName", ...}) // }) defineIndexes(cf, mExtraIndexes, Indexes.class, Index.class); } private void defineIndexes(ClassFile cf, List<? extends SyntheticPropertyList> definedIndexes, Class annotationGroupClass, Class annotationClass) { if (definedIndexes.size() == 0) { return; } Annotation indexSet = cf.addRuntimeVisibleAnnotation(TypeDesc.forClass(annotationGroupClass)); Annotation.MemberValue[] indexes = new Annotation.MemberValue[definedIndexes.size()]; // indexSet.value -> indexes indexSet.putMemberValue("value", indexes); int position = 0; for (SyntheticPropertyList extraIndex : definedIndexes) { Annotation index = addIndex(indexSet, indexes, position++, annotationClass); 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 * @param annotationClass TODO * @return */ private Annotation addIndex(Annotation annotator, Annotation.MemberValue[] indexes, int position, Class annotationClass) { assert (indexes.length > position); Annotation index = annotator.makeAnnotation(); index.setType(TypeDesc.forClass(annotationClass)); 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.getName() != null) { // Define @Name Annotation ann = mi.addRuntimeVisibleAnnotation(TypeDesc.forClass(Name.class)); ann.putMemberValue("value", property.getName()); } 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) { @Override 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; } @Override 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(); } } }