summaryrefslogtreecommitdiff
path: root/src/test/java/com/amazon/carbonado/synthetic
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/amazon/carbonado/synthetic')
-rw-r--r--src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java746
1 files changed, 746 insertions, 0 deletions
diff --git a/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java b/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java
new file mode 100644
index 0000000..faace6d
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java
@@ -0,0 +1,746 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.joda.time.DateTime;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.PrimaryKey;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.Version;
+import com.amazon.carbonado.Nullable;
+import com.amazon.carbonado.Join;
+
+import com.amazon.carbonado.adapter.YesNoAdapter;
+import com.amazon.carbonado.adapter.TextAdapter;
+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.repo.toy.ToyRepository;
+
+import com.amazon.carbonado.stored.StorableTestBasic;
+
+/**
+ * @author Don Schneider
+ */
+public class TestSyntheticStorableBuilders extends TestCase {
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestSyntheticStorableBuilders.class);
+ }
+
+ private static final ValueVendor VENDOR = new ValueVendor();
+
+ public static final String VERSION = '@'
+ + Version.class.toString().substring(10) + "()";
+
+ public static final String NULLABLE = '@'
+ + Nullable.class.toString().substring(10) + "()";
+
+ public static final String JOIN = '@' + Join.class.toString().substring(10) + '(';
+
+ public static final String INTERNAL = "internal=[]";
+
+ public static final String EXTERNAL = "external=[]";
+
+ public final static String YESNO = '@'
+ + YesNoAdapter.class.toString().substring(10) + "(lenient=true)";
+
+ public static final String TEXT = '@'
+ + TextAdapter.class.toString().substring(10) + "(charset=UTF-8, altCharsets=[])";
+
+ private static Set s_storableInterfaceMethods = new HashSet();
+
+ public static final TestDef[] TESTS = new TestDef[] {
+ new TestDef("string/long",
+ StorableTestBasic.class,
+ StorableTestBasic.class.getName() + "~N+stringProp-longProp",
+ new IndexDef[] {
+ new IndexDef("stringProp", Direction.ASCENDING),
+ new IndexDef("longProp", Direction.DESCENDING), },
+ new MethodDef[] {
+ new MethodDef("getStringProp", new String[] { TEXT }),
+ new MethodDef("getLongProp", null),
+ new MethodDef("getId", null),
+ new MethodDef("getIntProp", null),
+ new MethodDef("getDoubleProp", null),
+ // FIXME: add version prop to STB
+// new MethodDef("getVersionNumber",
+// new String[] { VERSION }),
+ new MethodDef("getMaster_0", new String[] {
+ NULLABLE, JOIN }),
+ new MethodDef("getIsConsistent_0", null),
+// new MethodDef("setVersionNumber", null),
+ new MethodDef("setAllProperties_0", null),
+ new MethodDef("setMaster_0", null),
+ new MethodDef("setId", null),
+
+ new MethodDef("setStringProp", null),
+ new MethodDef("setLongProp", null),
+ new MethodDef("setID", null),
+ new MethodDef("setIntProp", null),
+ new MethodDef("setDoubleProp", null),
+
+ new MethodDef("getClass", null),
+ new MethodDef("wait", null),
+ new MethodDef("notify", null),
+ new MethodDef("notifyAll", null), },
+ null),
+ new TestDef("string/long+synthetic",
+ StorableTestBasic.class,
+ StorableTestBasic.class.getName() + "~N+stringProp-longProp",
+ new IndexDef[] {
+ new IndexDef("stringProp", Direction.ASCENDING),
+ new IndexDef("longProp", Direction.DESCENDING), },
+ new MethodDef[] {
+ new MethodDef("getStringProp", new String[] { TEXT }),
+ new MethodDef("getLongProp", null),
+ new MethodDef("getId", null),
+ new MethodDef("getIntProp", null),
+ new MethodDef("getDoubleProp", null),
+ // FIXME: add version prop to STB
+// new MethodDef("getVersionNumber",
+// new String[] { VERSION }),
+ new MethodDef("getMaster_0", new String[] {
+ NULLABLE, JOIN }),
+ new MethodDef("getIsConsistent_0", null),
+// new MethodDef("setVersionNumber", null),
+ new MethodDef("setAllProperties_0", null),
+ new MethodDef("setMaster_0", null),
+ new MethodDef("setId", null),
+
+ new MethodDef("setStringProp", null),
+ new MethodDef("setLongProp", null),
+ new MethodDef("setID", null),
+ new MethodDef("setIntProp", null),
+ new MethodDef("setDoubleProp", null),
+
+ new MethodDef("getClass", null),
+ new MethodDef("wait", null),
+ new MethodDef("notify", null),
+ new MethodDef("notifyAll", null),
+
+ new MethodDef("getTestSynth", new String[] { NULLABLE }),
+ new MethodDef("setTestSynth", null),
+ },
+ new SyntheticProperty[] { new SyntheticProperty("testSynth",
+ String.class,
+ true,
+ false) }),
+ new TestDef("string/long+versionedSynthetic",
+ StorableTestBasic.class,
+ StorableTestBasic.class.getName() + "~N+stringProp-longProp",
+ new IndexDef[] {
+ new IndexDef("stringProp", Direction.ASCENDING),
+ new IndexDef("longProp", Direction.DESCENDING), },
+ new MethodDef[] {
+
+ new MethodDef("getStringProp", new String[] { TEXT }),
+ new MethodDef("getLongProp", null),
+ new MethodDef("getId", null),
+ new MethodDef("getIntProp", null),
+ new MethodDef("getDoubleProp", null),
+ new MethodDef("getMaster_0", new String[] {
+ NULLABLE, JOIN }),
+ new MethodDef("getIsConsistent_0", null),
+ new MethodDef("setAllProperties_0", null),
+ new MethodDef("setMaster_0", null),
+ new MethodDef("setId", null),
+
+ new MethodDef("setStringProp", null),
+ new MethodDef("setLongProp", null),
+ new MethodDef("setID", null),
+ new MethodDef("setIntProp", null),
+ new MethodDef("setDoubleProp", null),
+
+ new MethodDef("getClass", null),
+ new MethodDef("wait", null),
+ new MethodDef("notify", null),
+ new MethodDef("notifyAll", null),
+
+ new MethodDef("getTestSynth", new String[] { VERSION }),
+ new MethodDef("setTestSynth", null),
+ },
+ new SyntheticProperty[] { new SyntheticProperty("testSynth",
+ int.class,
+ false,
+ true) }) };
+
+ static {
+ for (Method m : Storable.class.getMethods()) {
+ s_storableInterfaceMethods.add(m.getName());
+ }
+ }
+
+ protected Repository mRepository;
+
+ public TestSyntheticStorableBuilders(String name) {
+ super(name);
+ }
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ mRepository = new ToyRepository();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mRepository != null) {
+ mRepository.close();
+ mRepository = null;
+ }
+ }
+
+ public void testSanity() throws Exception {
+ exerciseStorable(StorableTestBasic.class);
+ }
+
+
+ /**
+ * Test syntheticBuilder
+ */
+ public <S extends Storable> void test_syntheticBuilder() throws Exception {
+ SyntheticProperty[] props = new SyntheticProperty[] {
+ new SyntheticProperty("synthId",
+ int.class,
+ false,
+ false),
+ new SyntheticProperty("synthStr",
+ String.class,
+ true,
+ false),
+ new SyntheticProperty("synthInt",
+ int.class,
+ false,
+ false),
+ new SyntheticProperty("synthVers",
+ int.class,
+ false,
+ true),
+ new SyntheticProperty("synthBool",
+ boolean.class,
+ false,
+ false), };
+
+ TestDef test = new TestDef("synthetic",
+ null,
+ "TSSB~synthId~synthStr~synthInt~synthVers~synthBool",
+ null,
+ new MethodDef[] {
+ new MethodDef("getSynthId",
+ null),
+ new MethodDef("getSynthStr",
+ new String[] { NULLABLE }),
+ new MethodDef("getSynthInt", null),
+ new MethodDef("getSynthVers",
+ new String[] { VERSION }),
+ new MethodDef("isSynthBool", null),
+
+ new MethodDef("setSynthId", null),
+ new MethodDef("setSynthStr", null),
+ new MethodDef("setSynthInt", null),
+ new MethodDef("setSynthVers", null),
+ new MethodDef("setSynthBool", null),
+
+ new MethodDef("getClass", null),
+ new MethodDef("wait", null),
+ new MethodDef("notify", null),
+ new MethodDef("notifyAll", null), },
+ props);
+
+ SyntheticStorableBuilder b = new SyntheticStorableBuilder(
+ "TSSB", this.getClass().getClassLoader());
+ int i = 0;
+ for (SyntheticProperty source : props) {
+ b.addProperty(source);
+ if (i == 0) {
+ // First is primary key.
+ b.addPrimaryKey().addProperty(source.getName());
+ }
+ i++;
+ }
+ Class synth = b.build();
+ validateIndexEntry(test, synth);
+
+ exerciseStorable(synth);
+ }
+
+
+
+ public void testSyntheticReference() throws Exception {
+ for (TestDef test : TESTS) {
+ StorableInfo info = StorableIntrospector.examine(test.mClass);
+
+ SyntheticStorableReferenceBuilder<StorableTestBasic> builder
+ = new SyntheticStorableReferenceBuilder<StorableTestBasic>(test.mClass, false);
+
+ for (IndexDef refProp : test.mProps) {
+ StorableProperty storableProperty = ((StorableProperty) info.getAllProperties()
+ .get(refProp.mProp));
+ builder.addKeyProperty(refProp.mProp, refProp.getDir());
+ }
+ builder.build();
+ Class s = builder.getStorableClass();
+ validateIndexEntry(test, s);
+ exerciseStorable(s);
+
+ StorableTestBasic master =
+ mRepository.storageFor(StorableTestBasic.class).prepare();
+ populate(master);
+ master.insert();
+
+ Storable index = mRepository.storageFor(s).prepare();
+ builder.setAllProperties(index, master);
+ index.insert();
+
+ Storable indexChecker = mRepository.storageFor(s).prepare();
+ builder.setAllProperties(indexChecker, master);
+ assertTrue(indexChecker.tryLoad());
+
+ StorableTestBasic masterChecker = builder.loadMaster(indexChecker);
+ assertEquals(master, masterChecker);
+
+ assertTrue(builder.isConsistent(index, master));
+ masterChecker =
+ mRepository.storageFor(StorableTestBasic.class).prepare();
+ master.copyAllProperties(masterChecker);
+ assertTrue(builder.isConsistent(index, masterChecker));
+ masterChecker.setId(-42);
+ assertFalse(builder.isConsistent(index, masterChecker));
+
+ }
+ }
+
+ /**
+ * @param <S>
+ * @param storableType
+ * @throws SupportException
+ * @throws RepositoryException
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws PersistException
+ * @throws FetchException
+ */
+ protected <T extends Storable> void exerciseStorable(Class<T> storableType) throws SupportException, RepositoryException, IllegalAccessException, InvocationTargetException, PersistException, FetchException {
+ T persister = mRepository.storageFor(storableType).prepare();
+ Map<String, Object> valueMap = populate(persister);
+
+ persister.insert();
+ T reader = mRepository.storageFor(storableType).prepare();
+ persister.copyPrimaryKeyProperties(reader);
+ assertTrue(reader.tryLoad());
+
+ for (Method method : storableType.getMethods()) {
+ if (method.getName().startsWith("get") ) {
+ Class returnType = method.getReturnType();
+ Class[] paramTypes = method.getParameterTypes();
+ if (VENDOR.hasValue(returnType) && paramTypes.length == 0) {
+ Object expectedValue = valueMap.get(method.getName().substring(3));
+ Object actualValue = method.invoke(persister, (Object[]) null);
+ assertEquals(expectedValue, actualValue);
+ }
+ }
+ }
+ }
+
+ /**
+ * Populates a storable with random values. Any complex properties will be skipped.
+ * @param storable to populate
+ * @return map from property name to value set into it
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ protected <T extends Storable> Map<String, Object> populate(T storable)
+ throws IllegalAccessException, InvocationTargetException {
+ ValueVendor vendor = new ValueVendor();
+ Map<String, Object> valueMap = new HashMap<String, Object>();
+ vendor.reset();
+ for (Method method : storable.getClass().getMethods()) {
+ if (method.getName().startsWith("set") ) {
+ Class[] paramTypes = method.getParameterTypes();
+ Class type = paramTypes[0];
+ if (vendor.hasValue(type)) {
+ Object value = vendor.getValue(type);
+ method.invoke(storable, new Object[] {value});
+ valueMap.put(method.getName().substring(3), value);
+ }
+ }
+ }
+ return valueMap;
+ }
+
+
+
+
+ protected void validateIndexEntry(TestDef test, Class syntheticClass) {
+ assertEquals(test.mClassName, syntheticClass.getName());
+ Map<String, String[]> expectedMethods = new HashMap<String, String[]>();
+ if (null != test.mMethods) {
+ for (MethodDef m : test.mMethods) {
+ expectedMethods.put(m.mName, m.mAnnotations);
+ }
+ }
+
+ Class iface = syntheticClass.getInterfaces()[0];
+ assertEquals(test.mTestMoniker, Storable.class, iface);
+
+ boolean missed = false;
+ int storableIfaceMethodCount = 0;
+ int methodCount = 0;
+ for (Method m : syntheticClass.getMethods()) {
+ if (s_storableInterfaceMethods.contains(m.getName())) {
+ storableIfaceMethodCount++;
+ } else {
+ Set expectedAnnotations = null;
+ if (expectedMethods.containsKey(m.getName())) {
+ expectedAnnotations = new HashSet();
+ String[] expectedAnnotationArray = expectedMethods.get(m.getName());
+ if (null != expectedAnnotationArray) {
+ for (String a : expectedAnnotationArray) {
+ expectedAnnotations.add(a);
+ }
+ }
+ } else {
+ System.out.println("missed method " + methodCount
+ + "\nMethodDef(\"" + m.getName() + "\", null),");
+ missed = true;
+ }
+
+ if (expectedAnnotations != null) {
+ assertEquals(test.mTestMoniker + ": " + m.getName(),
+ expectedAnnotations.size(),
+ m.getDeclaredAnnotations().length);
+ } else {
+ assertEquals(test.mTestMoniker + ": " + m.getName(),
+ 0,
+ m.getDeclaredAnnotations().length);
+ }
+
+ for (Annotation a : m.getDeclaredAnnotations()) {
+ if (missed) {
+ System.out.println(" " + a);
+ }
+ if (expectedAnnotations != null) {
+ if (!expectedAnnotations.contains(a.toString())) {
+ boolean found = false;
+ for (Object candidate : expectedAnnotations.toArray()) {
+ if (a.toString().startsWith((String) candidate)) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(test.mTestMoniker + ':' + m.getName()
+ + " -- unexpected annotation " + a, found);
+ } // else we're ok
+ }
+ ;
+ }
+ }
+ methodCount++;
+ }
+ assertEquals(test.mTestMoniker,
+ s_storableInterfaceMethods.size(),
+ storableIfaceMethodCount);
+ assertFalse(test.mTestMoniker, missed);
+ }
+
+ static class MethodDef {
+ String mName;
+
+ String[] mAnnotations;
+
+ public MethodDef(String name, String[] annotations) {
+ mName = name;
+ mAnnotations = annotations;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ String annotations = null;
+ if (null != mAnnotations) {
+ for (int i = 0; i < mAnnotations.length; i++) {
+ if (null == annotations) {
+ annotations = "";
+ } else {
+ annotations += ", ";
+ }
+ annotations += mAnnotations[i];
+ }
+ } else {
+ annotations = " -- ";
+ }
+ return mName + " [" + annotations + ']';
+ }
+
+ }
+
+ /**
+ * More intuitive mechanism for defining index properties; propClass is
+ * optionally used for synthetic references (since we don't formally define
+ * properties in this test environment)
+ */
+ public static class IndexDef {
+ private String mProp;
+
+ private Direction mDir;
+
+ public IndexDef(String prop, Direction dir) {
+ mProp = prop;
+ mDir = dir;
+ }
+
+ public String getProp() {
+ return mProp;
+ }
+
+ public Direction getDir() {
+ return mDir;
+ }
+ }
+
+ public static class TestDef {
+ public String mTestMoniker;
+
+ public Class mClass;
+
+ public String mClassName;
+
+ public IndexDef[] mProps;
+
+ public MethodDef[] mMethods;
+
+ public SyntheticProperty[] mSynths;
+
+ public TestDef(String moniker,
+ Class aClass,
+ String aClassName,
+ IndexDef[] props,
+ MethodDef[] methods,
+ SyntheticProperty[] synths) {
+ mTestMoniker = moniker;
+ mClass = aClass;
+ mClassName = aClassName;
+ mProps = props;
+ mMethods = methods;
+ mSynths = synths;
+ }
+
+ public String toString() {
+ return mTestMoniker;
+ }
+ }
+
+ public interface ValueGetter {
+ public void reset();
+ public Object getValue();
+ }
+
+ static class BoolGetter implements ValueGetter {
+ final boolean start = false;
+ boolean mNext = start;
+ public void reset() {
+ mNext = start;
+ }
+ public Boolean getValue() {
+ mNext = !mNext;
+ return mNext;
+ }
+ }
+
+ static class IntGetter implements ValueGetter {
+ // hang onto the seed so we get a reproducible stream
+ int mSeed;
+ Random mNext;
+
+ IntGetter() {
+ reset();
+ }
+
+ public void reset() {
+ Random seeder = new Random();
+ while (mSeed == 0) {
+ mSeed = seeder.nextInt();
+ }
+ mNext = new Random(mSeed);
+ }
+
+ public Integer getValue() {
+ return mNext.nextInt();
+ }
+
+ public Integer getPositiveValue() {
+ return Math.abs(mNext.nextInt());
+ }
+ }
+
+ static class LongGetter implements ValueGetter {
+ final long start = 56789;
+ long mNext = start;
+ public void reset() {
+ mNext = start;
+ }
+ public Long getValue() {
+ return mNext++;
+ }
+ }
+
+ static class FloatGetter implements ValueGetter {
+ final float start = (float) Math.PI;
+ float mNext = start;
+ public void reset() {
+ mNext = start;
+ }
+ public Float getValue() {
+ float next = mNext;
+ mNext = next + 0.1f;
+ return next;
+ }
+ }
+
+ static class DoubleGetter implements ValueGetter {
+ final double start = Math.PI;
+ double mNext = start;
+ public void reset() {
+ mNext = start;
+ }
+ public Double getValue() {
+ double next = mNext;
+ mNext = next + 0.1;
+ return next;
+ }
+ }
+
+ static class CharGetter implements ValueGetter {
+
+ static final String source = "zookeepers fly piglatin homogeneous travesty";
+
+ IntGetter mNext = new IntGetter();
+
+ public void reset() {
+ mNext.reset();
+ }
+
+
+ public Character getValue() {
+ return source.charAt(mNext.getPositiveValue() % source.length());
+ }
+ }
+
+ static class StringGetter implements ValueGetter {
+ IntGetter mSizer = new IntGetter();
+ CharGetter mNext = new CharGetter();
+
+ public void reset() {
+ mSizer.reset();
+ mNext.reset();
+ }
+ public String getValue() {
+ int size = mSizer.getPositiveValue() % 255;
+ StringBuilder sb = new StringBuilder(size);
+ for (int i = 0; i<size; i++) {
+ sb.append(mNext.getValue());
+ }
+ return sb.toString();
+ }
+ }
+
+ static class DateTimeGetter implements ValueGetter {
+ static final DateTime start = new DateTime("2005-01-02");
+ DateTime mNext = start;
+ public void reset() {
+ mNext = start;
+ }
+ public DateTime getValue() {
+ DateTime next = mNext;
+ mNext = mNext.dayOfYear().addToCopy(1);
+ return next;
+ }
+ }
+
+ static class ValueVendor {
+ Map<Class, ValueGetter> getters = new HashMap(10);
+ ValueVendor() {
+ getters.put(String.class, new StringGetter());
+ getters.put(DateTime.class, new DateTimeGetter());
+
+ getters.put(Boolean.class, new BoolGetter());
+ getters.put(Integer.class, new IntGetter());
+ getters.put(Long.class, new LongGetter());
+ getters.put(Float.class, new FloatGetter());
+ getters.put(Double.class, new DoubleGetter());
+ getters.put(Character.class, new CharGetter());
+
+ getters.put(boolean.class, new BoolGetter());
+ getters.put(int.class, new IntGetter());
+ getters.put(long.class, new LongGetter());
+ getters.put(float.class, new FloatGetter());
+ getters.put(double.class, new DoubleGetter());
+ getters.put(char.class, new CharGetter());
+ }
+
+ void reset() {
+ for (Iterator iter = getters.values().iterator(); iter.hasNext();) {
+ ValueGetter getter = (ValueGetter) iter.next();
+ getter.reset();
+ }
+ }
+
+ boolean hasValue(Class type) {
+ return getters.containsKey(type);
+ }
+
+
+ Object getValue(Class type) {
+ return getters.get(type).getValue();
+ }
+ }
+}