From 08c89c37b0207a77dfddef15347c096acd6b6127 Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Tue, 19 Nov 2013 09:12:37 -0600 Subject: [PATCH] added option tests => fixed option bugs --- .../cassandra/cql/builder/DefaultOption.java | 47 +++++--- .../data/cassandra/cql/builder/Option.java | 7 +- .../cassandra/cql/builder/TableOption.java | 14 ++- .../{ => builder}/CreateTableBuilderTest.java | 13 ++- .../cassandra/cql/builder/OptionTest.java | 102 ++++++++++++++++++ 5 files changed, 160 insertions(+), 23 deletions(-) rename src/test/java/org/springframework/data/cassandra/cql/{ => builder}/CreateTableBuilderTest.java (63%) create mode 100644 src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java b/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java index 4f197764f..1e843de47 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java @@ -5,14 +5,13 @@ import static org.springframework.data.cassandra.cql.CqlStringUtils.singleQuote; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.Map; +import org.springframework.util.Assert; + /** - * A default implementation of {@link Option} to which {@link Enum} types can delegate, since they can't extend - * anything. + * A default implementation of {@link Option}. * * @author Matthew T. Adams */ @@ -23,27 +22,33 @@ public class DefaultOption implements Option { private boolean requiresValue; private boolean escapesValue; private boolean quotesValue; - private HashSet enumConstants; // HACK for enums only public DefaultOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { + setName(name); + setType(type); + this.requiresValue = requiresValue; + this.escapesValue = escapesValue; + this.quotesValue = quotesValue; + + } + + protected void setName(String name) { + Assert.hasLength(name); this.name = name; + } + + protected void setType(Class type) { if (type != null) { if (type.isInterface() && !(Map.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type))) { throw new IllegalArgumentException("given type [" + type.getName() + "] must be a class, Map or Collection"); } - // HACK for enums only - if (type.isEnum()) { - enumConstants = new HashSet(Arrays.asList(type.getEnumConstants())); - } } this.type = type; - this.requiresValue = requiresValue; - this.escapesValue = escapesValue; - this.quotesValue = quotesValue; } + @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean isCoerceable(Object value) { - if (value == null) { + if (value == null || type == null) { return true; } @@ -57,8 +62,15 @@ public class DefaultOption implements Option { } // check enum if (type.isEnum()) { - // HACK -- prefer to use Enum.valueOf(type, stringValue), but can't - return enumConstants.contains(value.toString()); + try { + String name = value instanceof Enum ? name = ((Enum) value).name() : value.toString(); + Enum.valueOf((Class) type, name); + return true; + } catch (NullPointerException x) { + return false; + } catch (IllegalArgumentException x) { + return false; + } } // check class via String constructor @@ -129,6 +141,8 @@ public class DefaultOption implements Option { if (value == null) { return null; } + checkValue(value); + String string = value.toString(); string = escapesValue ? escapeSingle(string) : string; string = quotesValue ? singleQuote(string) : string; @@ -137,6 +151,7 @@ public class DefaultOption implements Option { @Override public String toString() { - return getName(); + return "[name=" + name + ", type=" + type.getName() + ", requiresValue=" + requiresValue + ", escapesValue=" + + escapesValue + ", quotesValue=" + quotesValue + "]"; } } diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java b/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java index 8fcbb9dbd..34839351d 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java @@ -48,8 +48,11 @@ public interface Option { boolean isCoerceable(Object value); /** - * Renders the given value to a string according to this option's settings. Given null, returns - * null. + * First ensures that the given value is coerceable into the type expected by this option, then returns the result of + * {@link Object#toString()} called on the given value. If this option is escaping quotes ({@link #escapesValue()} is + * true), then single quotes will be escaped, and if this option is quoting values ( + * {@link #quotesValue()} is true), then the value will be surrounded by single quotes. Given + * null, returns null. * * @see #escapesValue() * @see #quotesValue() diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java b/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java index 61a6267d4..5e73a5f42 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java @@ -6,6 +6,8 @@ import java.util.Map; * Enumeration that represents all known table options. If a table option is not listed here, but is supported by * Cassandra, use the method {@link CreateTableBuilder#with(String, Object, boolean, boolean)} to write the raw value. * + * Implements {@link Option} via delegation, since {@link Enum}s can't extend anything. + * * @author Matthew T. Adams * @see CompactionOption * @see CompressionOption @@ -111,7 +113,17 @@ public enum TableOption implements Option { * @author Matthew T. Adams */ public enum CachingOption { - ALL, KEYS_ONLY, ROWS_ONLY, NONE; + ALL("all"), KEYS_ONLY("keys_only"), ROWS_ONLY("rows_only"), NONE("none"); + + private String value; + + private CachingOption(String value) { + this.value = value; + } + + public String toString() { + return value; + } } /** diff --git a/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java b/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java similarity index 63% rename from src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java rename to src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java index 888f738be..00018ae15 100644 --- a/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java @@ -1,14 +1,16 @@ -package org.springframework.data.cassandra.cql; +package org.springframework.data.cassandra.cql.builder; +import static junit.framework.Assert.assertEquals; import static org.springframework.data.cassandra.cql.builder.CqlBuilder.createTable; import static org.springframework.data.cassandra.cql.builder.MapBuilder.map; import static org.springframework.data.cassandra.cql.builder.TableOption.BLOOM_FILTER_FP_CHANCE; +import static org.springframework.data.cassandra.cql.builder.TableOption.CACHING; import static org.springframework.data.cassandra.cql.builder.TableOption.COMMENT; import static org.springframework.data.cassandra.cql.builder.TableOption.COMPACTION; import static org.springframework.data.cassandra.cql.builder.TableOption.CompactionOption.TOMBSTONE_THRESHOLD; import org.junit.Test; -import org.springframework.data.cassandra.cql.builder.CreateTableBuilder; +import org.springframework.data.cassandra.cql.builder.TableOption.CachingOption; import com.datastax.driver.core.DataType; @@ -27,13 +29,16 @@ public class CreateTableBuilderTest { String column2 = "column2"; Object comment = "this is a comment"; Object bloom = "0.00075"; + Object caching = CachingOption.KEYS_ONLY; CreateTableBuilder builder = createTable().ifNotExists().name(name).partitionKeyColumn(partKey0, type0) .partitionKeyColumn(partition1, type0).primaryKeyColumn(primary0, type0).column(column1, type1) .column(column2, type2).with(COMMENT, comment).with(BLOOM_FILTER_FP_CHANCE, bloom) - .with(COMPACTION, map().entry(TOMBSTONE_THRESHOLD, "0.15")); + .with(COMPACTION, map().entry(TOMBSTONE_THRESHOLD, "0.15")).with(CACHING, caching); String cql = builder.toCql(); - System.out.println(cql); + assertEquals( + "CREATE TABLE IF NOT EXISTS mytable (partitionKey0 text, partitionKey1 text, primary0 text, column1 text, column2 bigint, PRIMARY KEY ((partitionKey0, partitionKey1), primary0) WITH CLUSTERING ORDER BY (primary0 ASC) AND comment = 'this is a comment' AND bloom_filter_fp_chance = 0.00075 AND compaction = { 'tombstone_threshold' : 0.15 } AND caching = keys_only;", + cql); } } diff --git a/src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java b/src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java new file mode 100644 index 000000000..34569262f --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java @@ -0,0 +1,102 @@ +package org.springframework.data.cassandra.cql.builder; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; + +public class OptionTest { + + @Test(expected = IllegalArgumentException.class) + public void testOptionWithNullName() { + new DefaultOption(null, Object.class, true, true, true); + } + + @Test(expected = IllegalArgumentException.class) + public void testOptionWithEmptyName() { + new DefaultOption("", Object.class, true, true, true); + } + + @Test + public void testOptionWithNullType() { + new DefaultOption("opt", null, true, true, true); + new DefaultOption("opt", null, false, true, true); + } + + @Test + public void testOptionWithNullTypeIsCoerceable() { + Option op = new DefaultOption("opt", null, true, true, true); + assertTrue(op.isCoerceable("")); + assertTrue(op.isCoerceable(null)); + } + + @Test + public void testOptionValueCoercion() { + String name = "my_option"; + Class type = String.class; + boolean requires = true; + boolean escapes = true; + boolean quotes = true; + + Option op = new DefaultOption(name, type, requires, escapes, quotes); + + assertTrue(op.isCoerceable("opt")); + assertEquals("'opt'", op.toString("opt")); + assertEquals("'opt''n'", op.toString("opt'n")); + + type = Long.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + String expected = "1"; + for (Object value : new Object[] { 1, "1" }) { + assertTrue(op.isCoerceable(value)); + assertEquals(expected, op.toString(value)); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = Long.class; + escapes = false; + quotes = true; + op = new DefaultOption(name, type, requires, escapes, quotes); + + expected = "'1'"; + for (Object value : new Object[] { 1, "1" }) { + assertTrue(op.isCoerceable(value)); + assertEquals(expected, op.toString(value)); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = Double.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + String[] expecteds = new String[] { "1", "1.0", "1.0", "1", "1.0", null }; + Object[] values = new Object[] { 1, 1.0F, 1.0D, "1", "1.0", null }; + for (int i = 0; i < values.length; i++) { + assertTrue(op.isCoerceable(values[i])); + assertEquals(expecteds[i], op.toString(values[i])); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = RetentionPolicy.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + assertTrue(op.isCoerceable(null)); + assertTrue(op.isCoerceable(RetentionPolicy.CLASS)); + assertTrue(op.isCoerceable("CLASS")); + assertFalse(op.isCoerceable("x")); + assertEquals("CLASS", op.toString("CLASS")); + assertEquals("CLASS", op.toString(RetentionPolicy.CLASS)); + } +}