Skip to content

Commit f795205

Browse files
author
Alexandre Dutra
committed
JAVA-971: Make type codecs invariant.
This commit makes TypeCodec.accepts(TypeToken) invariant with respect to the Java type passed as argument. TypeCodec.accepts(Object) remains covariant with respect to the runtime type of the passed object.
1 parent f9c1764 commit f795205

17 files changed

Lines changed: 150 additions & 92 deletions

File tree

changelog/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [improvement] JAVA-885: Pass the authenticator name from the server to the auth provider.
99
- [improvement] JAVA-961: Raise an exception when an older version of guava (<16.01) is found.
1010
- [bug] JAVA-972: TypeCodec.parse() implementations should be case insensitive when checking for keyword NULL.
11+
- [bug] JAVA-971: Make type codecs invariant.
1112

1213
Merged from 2.1 branch:
1314

@@ -25,6 +26,7 @@ Merged from 2.1 branch:
2526
- [improvement] JAVA-652: Add DCAwareRoundRobinPolicy builder.
2627
- [improvement] JAVA-808: Add generic filtering policy that can be used to exclude specific DCs.
2728

29+
2830
### 3.0.0-alpha4
2931

3032
- [improvement] JAVA-926: Change default consistency level to LOCAL_QUORUM.

driver-core/src/main/java/com/datastax/driver/core/TypeCodec.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,15 @@ public DataType getCqlType() {
508508
/**
509509
* Return {@code true} if this codec is capable of serializing
510510
* the given {@code javaType}.
511+
* <p>
512+
* The implementation is <em>invariant</em> with respect to the passed
513+
* argument (through the usage of {@link TypeToken#equals(Object)}
514+
* and <em>it's strongly recommended not to modify this behavior</em>.
515+
* This means that a codec will only ever accept the
516+
* <em>exact</em> Java type that it has been created for.
517+
* <p>
518+
* If the argument represents a Java primitive type, its wrapper type
519+
* is considered instead.
511520
*
512521
* @param javaType The Java type this codec should serialize from and deserialize to; cannot be {@code null}.
513522
* @return {@code true} if the codec is capable of serializing
@@ -519,7 +528,7 @@ public boolean accepts(TypeToken javaType) {
519528
if (javaType.isPrimitive()) {
520529
javaType = primitiveToWrapperMap.get(javaType);
521530
}
522-
return this.javaType.isAssignableFrom(javaType);
531+
return this.javaType.equals(javaType);
523532
}
524533

525534
/**
@@ -548,13 +557,17 @@ public boolean accepts(DataType cqlType) {
548557
* <p>
549558
* Implementation notes:
550559
* <ol>
560+
* <li>The default implementation is <em>covariant</em> with respect to the passed
561+
* argument (through the usage of {@link TypeToken#isAssignableFrom(TypeToken)}
562+
* and <em>it's strongly recommended not to modify this behavior</em>.
563+
* This means that, by default, a codec will accept
564+
* <em>any subtype</em> of the Java type that it has been created for.</li>
551565
* <li>The base implementation provided here can only handle non-parameterized types;
552566
* codecs handling parameterized types, such as collection types, must override
553567
* this method and perform some sort of "manual"
554568
* inspection of the actual type parameters.</li>
555-
* <li>Similarly, codecs that only accept a partial subset of all possible values,
556-
* such as {@link TimeUUIDCodec} or {@link AsciiCodec}, must override
557-
* this method and manually inspect the object to check if it
569+
* <li>Similarly, codecs that only accept a partial subset of all possible values
570+
* must override this method and manually inspect the object to check if it
558571
* complies or not with the codec's limitations.</li>
559572
* </ol>
560573
*
@@ -565,7 +578,7 @@ public boolean accepts(DataType cqlType) {
565578
*/
566579
public boolean accepts(Object value) {
567580
checkNotNull(value);
568-
return accepts(TypeToken.of(value.getClass()));
581+
return this.javaType.isAssignableFrom(TypeToken.of(value.getClass()));
569582
}
570583

571584
@Override

driver-core/src/test/java/com/datastax/driver/core/TypeCodecTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,13 @@ public void test_inheritance() {
184184
ACodec aCodec = new ACodec();
185185
codecRegistry.register(aCodec);
186186
assertThat(codecRegistry.codecFor(cint(), A.class)).isNotNull().isSameAs(aCodec);
187-
// inheritance works: B is assignable to A
188-
assertThat(codecRegistry.codecFor(cint(), B.class)).isNotNull().isSameAs(aCodec);
187+
try {
188+
// covariance not accepted: no codec handles B exactly
189+
codecRegistry.codecFor(cint(), B.class);
190+
fail();
191+
} catch (CodecNotFoundException e) {
192+
//ok
193+
}
189194
TypeCodec<List<A>> expected = TypeCodec.list(aCodec);
190195
TypeCodec<List<A>> actual = codecRegistry.codecFor(list(cint()), new TypeToken<List<A>>(){});
191196
assertThat(actual.getCqlType()).isEqualTo(expected.getCqlType());
@@ -214,8 +219,7 @@ public void test_inheritance() {
214219
// ok
215220
}
216221
TypeCodec<List<B>> expectedB = TypeCodec.list(bCodec);
217-
TypeCodec<List<B>> actualB = codecRegistry.codecFor(list(cint()), new TypeToken<List<B>>() {
218-
});
222+
TypeCodec<List<B>> actualB = codecRegistry.codecFor(list(cint()), new TypeToken<List<B>>(){});
219223
assertThat(actualB.getCqlType()).isEqualTo(expectedB.getCqlType());
220224
assertThat(actualB.getJavaType()).isEqualTo(expectedB.getJavaType());
221225
}

features/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ specific topics.
66
If you're reading this from the [generated HTML documentation on
77
github.io](http://datastax.github.io/java-driver/), use the "Read More"
88
menu on the right hand side. If you're [browsing the source files on
9-
github.com](https://github.com/datastax/java-driver/tree/2.2/features),
9+
github.com](https://github.com/datastax/java-driver/tree/3.0/features),
1010
simply navigate to each sub-directory.
1111

1212
This is a work in progress: new sections will be added to cover existing
1313
features or document new ones.
1414

1515
You can also find more help in the legacy
16-
[user documentation](http://docs.datastax.com/en/developer/java-driver/2.2/java-driver/whatsNew2.html)
16+
[user documentation](http://docs.datastax.com/en/developer/java-driver/2.1/java-driver/whatsNew2.html)
1717
on the DataStax website.

features/address_resolution/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Cluster cluster = Cluster.builder()
3131
Note: the contact points provided while creating the `Cluster` are not translated, only
3232
addresses retrieved from or sent by Cassandra nodes are.
3333

34-
[at]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/policies/AddressTranslator.html
34+
[at]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/policies/AddressTranslator.html
3535

3636
### EC2 multi-region
3737

@@ -56,4 +56,4 @@ This class performs a reverse DNS lookup of the origin address, to find the doma
5656
target instance. Then it performs a forward DNS lookup of the domain name; the EC2 DNS does the
5757
private/public switch automatically based on location.
5858

59-
[ec2]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/policies/EC2MultiRegionAddressTranslator.html
59+
[ec2]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/policies/EC2MultiRegionAddressTranslator.html

features/custom_codecs/README.md

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,45 @@ Foo<Bar> foo = row.get(0, new TypeToken<Foo<Bar>>(){})
374374
Foo<Bar> foo = row.get(0, Foo.class);
375375
```
376376

377+
### Support for subtype polymorphism
378+
379+
Suppose the following class hierarchy:
380+
381+
```java
382+
class Animal {}
383+
class Cat extends Animal {}
384+
```
385+
386+
By default, a codec will accept to serialize any object that extends or
387+
implements its declared Java type: a codec such as
388+
`AnimalCodec extends TypeCodec<Animal>` will accept `Cat` instances as well.
389+
390+
This allows a codec to handle interfaces and superclasses
391+
in a generic way, regardless of the actual implementation being
392+
used by client code; for example, the driver has a built-in codec
393+
that handles `List` instances, and this codec is capable of
394+
serializing any concrete `List` implementation.
395+
396+
But this has one caveat: when setting or retrieving values
397+
with `get()` and `set()`, *it is vital to pass the exact
398+
Java type the codec handles*:
399+
400+
```java
401+
codecRegistry.register(new AnimalCodec());
402+
BoundStatement bs = ...
403+
bs.set(0, new Cat(), Animal.class); // works
404+
bs.set(0, new Cat(), Cat.class); // throws CodecNotFoundException
405+
```
406+
407+
The same is valid when retrieving values:
408+
409+
```java
410+
codecRegistry.register(new AnimalCodec());
411+
Row row = ...
412+
Animal animal = row.get(0, Animal.class); // works
413+
Cat cat = row.get(0, Cat.class); // throws CodecNotFoundException
414+
```
415+
377416
### Performance considerations
378417

379418
A codec lookup operation may be costly; to mitigate this, the `CodecRegistry`
@@ -390,8 +429,8 @@ and if so, adds it to the cache and returns it;
390429
4. if the creation succeeds, the registry adds the created codec to the cache and returns it;
391430
5. otherwise, the registry throws [CodecNotFoundException].
392431

393-
The cache can be used as long as _at least the CQL type is known_. The following situations
394-
are exceptions where it is _not_ possible to cache lookup results:
432+
The cache can be used as long as *at least the CQL type is known*. The following situations
433+
are exceptions where it is *not* possible to cache lookup results:
395434

396435
* With [SimpleStatement] instances;
397436
* With [BuiltStatement] instances (created via the Query Builder).
@@ -422,7 +461,7 @@ consider using prepared statements all the time.
422461
[CustomType]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/DataType.CustomType.html
423462
[TypeToken]: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/reflect/TypeToken.html
424463
[SimpleStatement]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/SimpleStatement.html
425-
[BuiltStatement]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/BuiltStatement.html
464+
[BuiltStatement]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/querybuilder/BuiltStatement.html
426465
[setList]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/SettableByIndexData.html#setList(int,%20java.util.List)
427466
[setSet]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/SettableByIndexData.html#setSet(int,%20java.util.Set)
428467
[setMap]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/SettableByIndexData.html#setMap(int,%20java.util.Map)

features/custom_payloads/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ The log message contains a pretty-printed version of the payload itself, and its
241241
[CASSANDRA-8553]: https://issues.apache.org/jira/browse/CASSANDRA-8553
242242
[v4spec]: https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec
243243
[qh]: https://issues.apache.org/jira/browse/CASSANDRA-6659
244-
[nhae]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/exceptions/NoHostAvailableException.html
244+
[nhae]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/exceptions/NoHostAvailableException.html
245245
[chm]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html
246246
[immutablemap]: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ImmutableMap.html
247-
[ufe]:http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/exceptions/UnsupportedFeatureException.html
247+
[ufe]:http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/exceptions/UnsupportedFeatureException.html
248248

features/logging/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,4 @@ It also turns on slow query tracing as described above.
303303
</log4j:configuration>
304304
```
305305

306-
[query_logger]:http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/QueryLogger.html
306+
[query_logger]:http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/QueryLogger.html

features/metadata/README.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ The driver maintains global information about the Cassandra cluster it
44
is connected to. It is available via
55
[Cluster#getMetadata()][getMetadata].
66

7-
[getMetadata]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Cluster.html#getMetadata()
7+
[getMetadata]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Cluster.html#getMetadata()
88

99
### Schema metadata
1010

1111
Use [getKeyspace(String)][getKeyspace] or [getKeyspaces()][getKeyspaces]
1212
to get keyspace-level metadata. From there you can access the keyspace's
1313
objects (tables, and UDTs and UDFs if relevant).
1414

15-
[getKeyspace]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#getKeyspace(java.lang.String)
16-
[getKeyspaces]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#getKeyspaces()
15+
[getKeyspace]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#getKeyspace(java.lang.String)
16+
[getKeyspaces]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#getKeyspaces()
1717

1818
#### Refreshes
1919

@@ -135,9 +135,9 @@ custom executor).
135135

136136
Check out the API docs for the features in this section:
137137

138-
* [withMaxSchemaAgreementWaitSeconds(int)](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Cluster.Builder.html#withMaxSchemaAgreementWaitSeconds(int))
139-
* [isSchemaInAgreement()](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/ExecutionInfo.html#isSchemaInAgreement())
140-
* [checkSchemaAgreement()](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#checkSchemaAgreement())
138+
* [withMaxSchemaAgreementWaitSeconds(int)](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Cluster.Builder.html#withMaxSchemaAgreementWaitSeconds(int))
139+
* [isSchemaInAgreement()](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/ExecutionInfo.html#isSchemaInAgreement())
140+
* [checkSchemaAgreement()](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#checkSchemaAgreement())
141141

142142

143143
### Token metadata
@@ -181,14 +181,14 @@ Starting with Cassandra 2.1.5, this information is available in a system
181181
table (see
182182
[CASSANDRA-7688](https://issues.apache.org/jira/browse/CASSANDRA-7688)).
183183

184-
[metadata]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html
185-
[getTokenRanges]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#getTokenRanges()
186-
[getTokenRanges2]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#getTokenRanges(java.lang.String,%20com.datastax.driver.core.Host)
187-
[getReplicas]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#getReplicas(java.lang.String,%20com.datastax.driver.core.TokenRange)
188-
[newToken]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#newToken(java.lang.String)
189-
[newTokenRange]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Metadata.html#newTokenRange(com.datastax.driver.core.Token,%20com.datastax.driver.core.Token)
190-
[TokenRange]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/TokenRange.html
191-
[getTokens]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Host.html#getTokens()
192-
[setToken]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/BoundStatement.html#setToken(int,%20com.datastax.driver.core.Token)
193-
[getToken]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Row.html#getToken(int)
194-
[getPKToken]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Row.html#getPartitionKeyToken()
184+
[metadata]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html
185+
[getTokenRanges]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#getTokenRanges()
186+
[getTokenRanges2]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#getTokenRanges(java.lang.String,%20com.datastax.driver.core.Host)
187+
[getReplicas]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#getReplicas(java.lang.String,%20com.datastax.driver.core.TokenRange)
188+
[newToken]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#newToken(java.lang.String)
189+
[newTokenRange]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Metadata.html#newTokenRange(com.datastax.driver.core.Token,%20com.datastax.driver.core.Token)
190+
[TokenRange]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/TokenRange.html
191+
[getTokens]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Host.html#getTokens()
192+
[setToken]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/BoundStatement.html#setToken(int,%20com.datastax.driver.core.Token)
193+
[getToken]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Row.html#getToken(int)
194+
[getPKToken]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Row.html#getPartitionKeyToken()

features/native_protocol/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ All host(s) tried for query failed
5959
[/127.0.0.1:9042] Host /127.0.0.1:9042 does not support protocol version V3 but V2))
6060
```
6161

62-
[gpv]: http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/ProtocolOptions.html#getProtocolVersion()
62+
[gpv]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/ProtocolOptions.html#getProtocolVersion()
6363

6464
#### Protocol version with mixed clusters
6565

@@ -90,19 +90,19 @@ To avoid this issue, you can use one the following workarounds:
9090
#### v1 to v2
9191

9292
* bound variables in simple statements
93-
([Session#execute(String, Object...)](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/Session.html#execute(java.lang.String,%20java.lang.Object...)))
94-
* [batch statements](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/BatchStatement.html)
93+
([Session#execute(String, Object...)](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Session.html#execute(java.lang.String,%20java.lang.Object...)))
94+
* [batch statements](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/BatchStatement.html)
9595
* [query paging](../paging/)
9696

9797
#### v2 to v3
9898

9999
* the number of stream ids per connection goes from 128 to 32768 (see
100100
[Connection pooling](../pooling/))
101-
* [serial consistency on batch statements](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/BatchStatement.html#setSerialConsistencyLevel(com.datastax.driver.core.ConsistencyLevel))
101+
* [serial consistency on batch statements](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/BatchStatement.html#setSerialConsistencyLevel(com.datastax.driver.core.ConsistencyLevel))
102102
* [client-side timestamps](../query_timestamps/)
103103

104104
#### v3 to v4
105105

106-
* [query warnings](http://docs.datastax.com/en/drivers/java/2.2/com/datastax/driver/core/ExecutionInfo.html#getWarnings())
106+
* [query warnings](http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/ExecutionInfo.html#getWarnings())
107107
* allowed unset values in bound statements
108108
* [Custom payloads](../custom_payloads/)

0 commit comments

Comments
 (0)