Skip to content

Commit 9ecaada

Browse files
committed
Test that synthetic fields are ignored by Mapper (JAVA-465)
1 parent f803b7a commit 9ecaada

2 files changed

Lines changed: 167 additions & 0 deletions

File tree

driver-mapping/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@
4747
<version>6.8.1</version>
4848
<scope>test</scope>
4949
</dependency>
50+
51+
<dependency>
52+
<groupId>org.ow2.asm</groupId>
53+
<artifactId>asm-all</artifactId>
54+
<version>RELEASE</version>
55+
<scope>test</scope>
56+
</dependency>
5057
<!--
5158
<dependency>
5259
<groupId>org.ow2.asm</groupId>
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package com.datastax.driver.mapping;
2+
3+
import com.datastax.driver.core.CCMBridge;
4+
import com.datastax.driver.mapping.annotations.PartitionKey;
5+
import com.datastax.driver.mapping.annotations.Table;
6+
import com.google.common.io.Closeables;
7+
import org.objectweb.asm.*;
8+
import org.testng.annotations.Test;
9+
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.lang.reflect.Constructor;
13+
import java.util.Arrays;
14+
import java.util.Collection;
15+
16+
import static org.testng.Assert.assertEquals;
17+
18+
public class SyntheticFieldsMapperTest extends CCMBridge.PerClassSingleNodeCluster {
19+
20+
protected Collection<String> getTableDefinitions() {
21+
return Arrays.asList("CREATE TABLE synthetic_fields (id int PRIMARY KEY)");
22+
}
23+
24+
/**
25+
* Test that synthetic fields are ignored by the {@code AnnotationParser} (JAVA-465).
26+
* <p/>
27+
* This test creates a modified version of this class where {@code futureSynthetic} is marked as synthetic, and
28+
* therefore ignored by the {@code AnnotationParser}. If the test throws an error "Cannot find matching getter and
29+
* setter for field 'futureSynthetic'", it means that the field is not ignored by the mapped. However,
30+
* if it succeed, we know that the field was ignored and that the fix does the right job.
31+
* <p/>
32+
* If the class {@code ClassWithSyntheticField} was used as-is, the {@code Mapper} would complain that there are
33+
* no getter and setter for the field {@code futureSynthetic}.
34+
* <p/>
35+
* Note that we could also have used not-static inner classes, but the {@code Mapper} is never able to instantiate
36+
* those as they require a reference to their enclosing class that is not provided at instantiation time.
37+
*/
38+
@Test(groups = "short")
39+
public void should_ignore_synthetic_fields() {
40+
Class<?> classWithSyntheticFields = makeTestClassWithSyntheticFields();
41+
Object instance = instantiateNewClass(classWithSyntheticFields, 42);
42+
43+
// Here we cannot use ClassWithSyntheticField since it is not the one the source file was compiled against
44+
// Instead, it is a different Class (identity + ClassLoader), a modified version of ClassWithSyntheticField
45+
// Nice ClassCastException will be thrown if we try to cast it to ClassWithSyntheticField
46+
@SuppressWarnings("unchecked")
47+
Mapper<Object> m = (Mapper<Object>) new MappingManager(session).mapper(classWithSyntheticFields);
48+
m.save(instance);
49+
50+
assertEquals(m.get(42), instance);
51+
}
52+
53+
private Class<?> makeTestClassWithSyntheticFields() {
54+
InputStream stream = null;
55+
try {
56+
// Get class bytes
57+
Class<ClassWithSyntheticField> c = ClassWithSyntheticField.class;
58+
String classAsPath = c.getName().replace('.', '/') + ".class";
59+
stream = c.getClassLoader().getResourceAsStream(classAsPath);
60+
61+
// Make "futureSynthetic" field actually synthetic
62+
ClassWriter cw = new ClassWriter(0);
63+
ClassVisitor cv = new SyntheticFieldCreator(Opcodes.ASM5, cw);
64+
ClassReader cr = new ClassReader(stream);
65+
cr.accept(cv, 0);
66+
byte[] updatedClassBytes = cw.toByteArray();
67+
68+
// Build the new class
69+
return new InterceptingClassLoader().defineClass(
70+
ClassWithSyntheticField.class.getName(),
71+
updatedClassBytes);
72+
} catch (IOException e) {
73+
throw new RuntimeException("Could not read Class bytes", e);
74+
} finally {
75+
try {
76+
Closeables.close(stream, true);
77+
} catch (IOException ignored) {
78+
}
79+
}
80+
}
81+
82+
private Object instantiateNewClass(Class<?> classWithSyntheticFields, int id) {
83+
try {
84+
Constructor<?> declaredConstructor = classWithSyntheticFields.getDeclaredConstructor(int.class);
85+
return declaredConstructor.newInstance(id);
86+
} catch (Exception e) {
87+
throw new RuntimeException("Could not instantiate Class", e);
88+
}
89+
}
90+
91+
@Table(keyspace = "ks", name = "synthetic_fields")
92+
public static class ClassWithSyntheticField {
93+
@PartitionKey
94+
private int id;
95+
96+
// Intentionally broken class: there is no getter/setter for this field
97+
private int futureSynthetic;
98+
99+
// This constructor will be used by the Mapper
100+
@SuppressWarnings("unused")
101+
public ClassWithSyntheticField() {
102+
}
103+
104+
// This constructor is invoked using reflection
105+
@SuppressWarnings("unused")
106+
public ClassWithSyntheticField(int id) {
107+
this.id = id;
108+
}
109+
110+
// Getters & Setters used by the Mapper
111+
@SuppressWarnings("unused")
112+
public int getId() {
113+
return id;
114+
}
115+
116+
// Getters & Setters used by the Mapper
117+
@SuppressWarnings("unused")
118+
public void setId(int id) {
119+
this.id = id;
120+
}
121+
122+
@Override
123+
public boolean equals(Object o) {
124+
if (this == o) return true;
125+
if (o == null || getClass() != o.getClass()) return false;
126+
127+
ClassWithSyntheticField that = (ClassWithSyntheticField) o;
128+
129+
if (id != that.id) return false;
130+
131+
return true;
132+
}
133+
134+
@Override
135+
public int hashCode() {
136+
return id;
137+
}
138+
}
139+
140+
private static class SyntheticFieldCreator extends ClassVisitor {
141+
public SyntheticFieldCreator(int api, ClassVisitor cv) {
142+
super(api, cv);
143+
}
144+
145+
@Override
146+
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
147+
int newAccesses = access;
148+
if ("futureSynthetic".equals(name)) {
149+
newAccesses = access + Opcodes.ACC_SYNTHETIC;
150+
}
151+
return super.visitField(newAccesses, name, desc, signature, value);
152+
}
153+
}
154+
155+
private static class InterceptingClassLoader extends ClassLoader {
156+
public Class defineClass(String name, byte[] b) {
157+
return defineClass(name, b, 0, b.length);
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)