|
| 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