/* * Copyright (C) 2012-2015 DataStax Inc. * * 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.datastax.driver.mapping; import com.datastax.driver.core.CCMTestsSupport; import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.Table; import com.google.common.io.Closeables; import org.objectweb.asm.*; import org.testng.annotations.Test; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import static org.testng.Assert.assertEquals; @SuppressWarnings("unused") public class SyntheticFieldsMapperTest extends CCMTestsSupport { @Override public void onTestContextInitialized() { execute("CREATE TABLE synthetic_fields (id int PRIMARY KEY)"); } /** * Test that synthetic fields are ignored by the {@code AnnotationParser} (JAVA-465). * <p/> * This test creates a modified version of this class where {@code futureSynthetic} is marked as synthetic, and * therefore ignored by the {@code AnnotationParser}. If the test throws an error "Cannot find matching getter and * setter for field 'futureSynthetic'", it means that the field is not ignored by the mapped. However, * if it succeed, we know that the field was ignored and that the fix does the right job. * <p/> * If the class {@code ClassWithSyntheticField} was used as-is, the {@code Mapper} would complain that there are * no getter and setter for the field {@code futureSynthetic}. * <p/> * Note that we could also have used not-static inner classes, but the {@code Mapper} is never able to instantiate * those as they require a reference to their enclosing class that is not provided at instantiation time. */ @Test(groups = "short") public void should_ignore_synthetic_fields() { Class<?> classWithSyntheticFields = makeTestClassWithSyntheticFields(); Object instance = instantiateNewClass(classWithSyntheticFields, 42); // Here we cannot use ClassWithSyntheticField since it is not the one the source file was compiled against // Instead, it is a different Class (identity + ClassLoader), a modified version of ClassWithSyntheticField // Nice ClassCastException will be thrown if we try to cast it to ClassWithSyntheticField @SuppressWarnings("unchecked") Mapper<Object> m = (Mapper<Object>) new MappingManager(session()).mapper(classWithSyntheticFields); m.save(instance); assertEquals(m.get(42), instance); } private Class<?> makeTestClassWithSyntheticFields() { InputStream stream = null; try { // Get class bytes Class<ClassWithSyntheticField> c = ClassWithSyntheticField.class; String classAsPath = c.getName().replace('.', '/') + ".class"; stream = c.getClassLoader().getResourceAsStream(classAsPath); // Make "futureSynthetic" field actually synthetic ClassWriter cw = new ClassWriter(0); ClassVisitor cv = new SyntheticFieldCreator(Opcodes.ASM5, cw); ClassReader cr = new ClassReader(stream); cr.accept(cv, 0); byte[] updatedClassBytes = cw.toByteArray(); // Build the new class return new InterceptingClassLoader().defineClass( ClassWithSyntheticField.class.getName(), updatedClassBytes); } catch (IOException e) { throw new RuntimeException("Could not read Class bytes", e); } finally { try { Closeables.close(stream, true); } catch (IOException ignored) { } } } private Object instantiateNewClass(Class<?> classWithSyntheticFields, int id) { try { Constructor<?> declaredConstructor = classWithSyntheticFields.getDeclaredConstructor(int.class); return declaredConstructor.newInstance(id); } catch (Exception e) { throw new RuntimeException("Could not instantiate Class", e); } } @Table(name = "synthetic_fields") public static class ClassWithSyntheticField { @PartitionKey private int id; // Intentionally broken class: there is no getter/setter for this field private int futureSynthetic; // This constructor will be used by the Mapper @SuppressWarnings("unused") public ClassWithSyntheticField() { } // This constructor is invoked using reflection @SuppressWarnings("unused") public ClassWithSyntheticField(int id) { this.id = id; } // Getters & Setters used by the Mapper @SuppressWarnings("unused") public int getId() { return id; } // Getters & Setters used by the Mapper @SuppressWarnings("unused") public void setId(int id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClassWithSyntheticField that = (ClassWithSyntheticField) o; return id == that.id; } @Override public int hashCode() { return id; } } private static class SyntheticFieldCreator extends ClassVisitor { public SyntheticFieldCreator(int api, ClassVisitor cv) { super(api, cv); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { int newAccesses = access; if ("futureSynthetic".equals(name)) { newAccesses = access + Opcodes.ACC_SYNTHETIC; } return super.visitField(newAccesses, name, desc, signature, value); } } private static class InterceptingClassLoader extends ClassLoader { public Class defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } }