According to the official example, generating getter/setter methods for a Field can be done by implementing an interface. But this is when I know exactly which Fields will be added to the Class. My program gets the fields to be added from the database every time, so I need to add getter/setter methods for these fields at runtime.
I define a getter method and assign it as FieldAccessor using intercept method. When I define a setter method in the same way and specify it as FieldAccessor, the system throws an exception.
Here is a simplified example codeļ¼
#Data
public class MappingFieldBO {
private String fieldName;
private int maxScore;
public MappingFieldBO() {
}
public MappingFieldBO(String fieldName, int maxScore) {
this.fieldName = fieldName;
this.maxScore = maxScore;
}
}
#Data
public class BaseMappingFieldBO {
private Long id;
}
public class Main {
public static void main(String[] args) {
List<MappingFieldBO> mappingFields = getFromDB();
DynamicType.Builder<BaseMappingFieldBO> builder = new ByteBuddy()
.subclass(BaseMappingFieldBO.class)
.name("io.buyan.dv.console.MappingBean");
// add uncertain fields to class
for (MappingFieldBO mappingField : mappingFields) {
String fieldName = mappingField.getFieldName();
builder = builder.defineField(fieldName, String.class, Visibility.PUBLIC)
// define getter method
.defineMethod(getterName(fieldName), String.class, Visibility.PUBLIC)
.intercept(FieldAccessor.ofField(fieldName))
// define setter method
// throw IllegalArgumentException: Method public void io.buyan.dv.console.MappingBean.setShipping() is no bean accessor
.defineMethod(setterName(fieldName), Void.TYPE, Visibility.PUBLIC)
.intercept(FieldAccessor.ofField(fieldName));
}
Class<? extends BaseMappingFieldBO> clazz = builder.make().load(Thread.currentThread().getContextClassLoader()).getLoaded();
}
private static String setterName(String fieldName) {
return "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
private static String getterName(String fieldName) {
return "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
private static List<MappingFieldBO> getFromDB() {
List<MappingFieldBO> list = new ArrayList<>();
list.add(new MappingFieldBO("shipping", 10));
list.add(new MappingFieldBO("deduct", 8));
return list;
}
}
Your setter lacks a parameter of the field's type. It returns void but needs to accept a value of type String.
Byte Buddy has a convenience method for this. Simply add: withProperty(fieldName, String.class) and everything is setup correctly.
Related
I need to get the enum name based on value. I am given with enum class and value and need to pick the corresponding name during run time .
I have a class called Information as below.
class Information {
private String value;
private String type;
private String cValue;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getcValue() {
return cValue;
}
public void setcValue(String cValue) {
this.cValue = cValue;
}
public static void main(String args[]) {
Information inf = new Information();
inf.setType("com.abc.SignalsEnum");
inf.setValue("1");
}
}
class SignalEnum {
RED("1"), GREEN("2"), ORANGE("3");
private String sign;
SignalEnum(String pattern) {
this.sign = pattern;
}
}
class MobileEnum {
SAMSUNG("1"), NOKIA("2"), APPLE("3");
private String mobile;
MobileEnum(String mobile) {
this.mobile = mobile;
}
}
In run time i will come to know the enum name using the attribute type from the Information class and also i am getting the value. I need to figure out the corresponding enum to set the value for cValue attribute of Information class.
Just for example i have provided two enums like SignalEnum and MobileEnum but in my actual case i will get one among 100 enum types. Hence i dont want to check type cast. I am looking for some solution using reflection to se the cValue.
Here is a simple resolver for any enum class.
Since reflection operations are expensive, it's better to prepare all required data once and then just query for it.
class EnumResolver {
private Map<String, Enum> map = new ConcurrentHashMap<>();
public EnumResolver(String className) {
try {
Class enumClass = Class.forName(className);
// look for backing property field, e.g. "sign" in SignalEnum
Field accessor = Arrays.stream(enumClass.getDeclaredFields())
.filter(f -> f.getType().equals(String.class))
.findFirst()
.orElseThrow(() -> new NoSuchFieldException("Not found field to access enum backing value"));
accessor.setAccessible(true);
// populate map with pairs like ["1" => SignalEnum.RED, "2" => SignalEnum.GREEN, etc]
for (Enum e : getEnumValues(enumClass)) {
map.put((String) accessor.get(e), e);
}
accessor.setAccessible(false);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public Enum resolve(String backingValue) {
return map.get(backingValue);
}
private <E extends Enum> E[] getEnumValues(Class<E> enumClass) throws ReflectiveOperationException {
Field f = enumClass.getDeclaredField("$VALUES");
f.setAccessible(true);
Object o = f.get(null);
f.setAccessible(false);
return (E[]) o;
}
}
And here is simple JUnit test
public class EnumResolverTest {
#Test
public void testSignalEnum() {
EnumResolver signalResolver = new EnumResolver("com.abc.SignalEnum");
assertEquals(SignalEnum.RED, signalResolver.resolve("1"));
assertEquals(SignalEnum.GREEN, signalResolver.resolve("2"));
assertEquals(SignalEnum.ORANGE, signalResolver.resolve("3"));
}
#Test
public void testMobileEnum() {
EnumResolver mobileResolver = new EnumResolver("com.abc.MobileEnum");
assertEquals(MobileEnum.SAMSUNG, mobileResolver.resolve("1"));
assertEquals(MobileEnum.NOKIA, mobileResolver.resolve("2"));
assertEquals(MobileEnum.APPLE, mobileResolver.resolve("3"));
}
}
And again for performance sake you can also instantiate these various resolvers once and put them into a separate Map
Map<String, EnumResolver> resolverMap = new ConcurrentHashMap<>();
resolverMap.put("com.abc.MobileEnum", new EnumResolver("com.abc.MobileEnum"));
resolverMap.put("com.abc.SignalEnum", new EnumResolver("com.abc.SignalEnum"));
// etc
Information inf = new Information();
inf.setType("com.abc.SignalsEnum");
inf.setValue("1");
SignalEnum red = (SignalEnum) resolverMap.get(inf.getType()).resolve(inf.getValue());
I would like to define a method and by passing the enum, returns the mapped type based on the enum. So far I only work out this way:
public class Person {
HashMap<String, Object> mData;
void int getDetail(DetailInt detail){
Object data = mData.get(detail.name());
if(data instanceof Integer)
return (int)data;
return 0;
}
void String getDetail(DetailStr detail){
Object data = mData.get(detail.name());
if(data instanceof String)
return (String)data;
return "";
}
}
public enum DetailInt {
Age("age"), Weight("weight"), Height("height");
String columnName;
DetailInt(String columnName){
this.columnName= columnName;
}
}
public enum DetailStr {
FirstName("first_name"), LastName("last_name");
String columnName;
DetailStr (String columnName){
this.columnName= columnName;
}
}
So I can use the same method, but passing different enums to get the data with the type.
int age = person.getDetail(DetailInt.Age);
String firstName = person.getDetail(DetailStr.FirstName);
Now, what I would like to achieve is to merge both enums together, so I can call as below:
int age = person.getDetail(Detail.Age);
String firstName = person.getDetail(Detail.FirstName);
It is neater. However, I have tried generic type and interface, still cannot find the way to do it. Use below way is similar to what I want but this is not enum type.
abstract class Detail {
}
class DetailStr extend Detail {
}
interface Details {
DetailStr firstName = new DetailStr("first_name");
DetailStr lastName = new DetailStr("las_name");
DetailInt age = new DetailInt("age");
DetailInt weight = new DetailInt("weight");
DetailInt height = new DetailInt("height");
}
public class Person {
void int getDetail(DetailInt detail){
....
}
void String getDetail(DetailStr detail){
....
}
}
You can't do this in Java.
This is because a particular value of an enumerator has the same type as any other value of that enumerator. It's therefore not possible to construct an overloaded function since there's no type difference to act as a descriminator. (You cannot overload a function by return type difference alone.)
The obvious solution is to have two methods getDetailAsInt and getDetailAsString.
I'll share this approach that does not use enums, but it might be of some use to you:
public class Key<T> {
private String key;
...
}
public class Keys {
public static final Key FIRST_NAME = new Key<String>("first_name");
public static final Key AGE = new Key<Integer>("age");
}
public class Person {
public <T> T getDetail(Key<T> key) {
Object detail = mData.get(key.getKey());
return (T) detail;
}
}
I'm afraid it might not be possible to convert it to use enums, so you'd have to ensure no unwanted keys are created in some other way (package-private constructor etc.)
i have a domain class(DB):
public class PersonDoamin {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
i also have model class:
public class PersonBean extends PersonDoamin {
}
so when i go to DAOImpl class and query for List and transfer this list to List and return to users as i have interface method for List getAllPerson(). so my questions is here when i transfer all data from List. Here i have some utility method that copies from one bean to another like this:
List<PersonDoamin> list = PersonDAO.getAllPersons();
List<PersonBean> pbList = new ArrayList<PersonBean>();
/* this below logic is pretty much in the all DAO impl*/
for(PersonDoamin p : list){
PersonBean pb = new PersonBean();
CopyHelper.copyBean(p, pb);
pbList.add(pb);
}
return pbList;
can we replace the looping and copying and adding to another list and returning part with somekind of generic method which will take any object two list and loop thorugh one and add it to another passed List parameter and return it. something like below which is not perfect right now:
public static <T> List<T> listToArray(List<T> list,List<T> list2) {
for(T element : list){
list2.add(element);
}
return list2;
}
public static void main(String[] args) {
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
PersonDoamin p = new PersonDoamin();
p.setName("aj");
p.setAge("25");
personList.add(p);
List<PersonBean> personBeansToReturn = new ArrayList<PersonBean>();
Test.listToArray(personList , personBeansToReturn );
}
A bit off topic, your design seems a bit weird that you have "Domain" class and "Bean" class and have "Bean" extends "Domain"...
Anyway, come back to your question, what you are trying to do is:
You have a List<Domain>
You want to transform each Domain in the List into a Bean (by use of some util method)
Put the resulting Beans into a list and return
Let's go through it step by step.
(by the way, the listToArray method you wrote does not align with your original loop as it does not do the transformation (point 2). I guess it is typo?)
(all psuedo code as I don't have environment on hand to make it compile. Concept should be correct I guess)
Step 1: Util method for Person
One biggest problem of your original util method is that, it is illegal to put a Parent object instance to a List of Child (it should be easy to figure why by yourself).
The util method should look like this:
List<PersonBean> toBeans(List<PersonDomain> domains) {
List<PersonBean> beans = new ArrayList<>(domains.size());
for (PersonDomain domain: domains) {
PersonBean bean = new PersonBean();
CopyHelper.copyBean(domain, bean);
beans.add(bean);
}
return beans;
}
Step 2: Make it generic
The problem above is that it only works for Person. If you want to make it generic, you will also need to provide the function to transform Domain to Bean:
(Assume you are using Java8, should be trivial to make your own interface if you are using older version)
<D,B> List<B> toBeans(List<D> domains, Function<B,D> mapper) {
List<PersonBean> beans = new ArrayList<>(domains.size());
for (PersonDomain domain: domains) {
beans.add(mapper.apply(domain));
}
return beans;
}
so that you can use it by:
return toBeans(personDomains, (domain) -> {
PersonBean bean = new PersonBean();
CopyHelper.copyBean(domain, bean);
return bean;
});
(You may consider wrap the function if in most case you are going to use the CopyHelper way)
<D,B> List<B> toBeansByBeanCopy(List<D> domains, Class<B> beanClass) {
return toBeans(domains, (domain)-> {
B bean = beanClass.newInstance();
CopyHelper.copyBean(domain, bean);
return bean;
});
}
so that you can use it as
return toBeansByBeanCopy(personDomains, PersonBean.class);
Step 3: Java has done it for you
Actually what you are trying to do above, it is already provided by Java in Java 8. You can simply do:
return personDomains.stream()
.map(d -> {
PersonBean bean = new PersonBean();
CopyHelper.copyBean(domain, bean);
return bean;
})
.collect(Collectors.toList());
You may write a little method to use in the lambda expression if it is the standard way.
return personDomains.stream()
.map(BeanMapper.mapper(PersonBean.class))
.collect(Collectors.toList());
(Leave the implementation as your exercise)
If you're looking for a way to call new on a generic type, you can, sort of. You have to use reflection and call newInstance on the Class object. I don't know if this is going to be feasible for you.
Also, I don't see anyway of realistically implementing your bean copy method without using some heavy reflection as well. In the example below I faked by just casting to the required classes.
public class GenericCopyTest
{
public static void main( String[] args ) throws Exception
{
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
PersonDoamin p = new PersonDoamin();
p.setName( "aj" );
p.setAge( "25" );
personList.add( p );
List<PersonBean> personBeansToReturn = new ArrayList<PersonBean>();
copyAndDowncast( personList, personBeansToReturn, PersonBean.class );
System.out.println( personBeansToReturn );
}
public static <T,U extends T> List<U> copyAndDowncast( List<T> from,
List<U> to, Class<U> type )
throws InstantiationException, IllegalAccessException
{
for( T element : from ) {
U nu = type.newInstance();
copyBean( element, nu );
to.add( nu );
}
return to;
}
private static <X,Y extends X> void copyBean( X from, Y nu ) {
((PersonBean)nu).setName( ((PersonDoamin)from).getName() );
((PersonBean)nu).setAge( ((PersonDoamin)from).getAge() );
}
}
class PersonDoamin {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
#Override
public String toString()
{
return "PersonDoamin{" + "name=" + name + ", age=" + age + '}';
}
}
class PersonBean extends PersonDoamin {
#Override
public String toString()
{
return "PersonBean{" + getName() + ',' + getAge()+ '}';
}
}
Output:
run:
[PersonBean{aj,25}]
BUILD SUCCESSFUL (total time: 0 seconds)
Why not just use addAll() for this? It does what you're trying to do, and it's already part of the system library.
Remember you can add a PersonBean to a PersonDomain list, but not the other way around.
public class GenericCopyTest
{
public static void main( String[] args ) {
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
List<PersonBean> personBeansToReturn = new ArrayList<PersonBean>();
personList.addAll( personBeansToReturn );
personBeansToReturn.addAll( personList ); // <-- FAILS
// No suitable method found
}
}
class PersonDoamin {}
class PersonBean extends PersonDoamin {}
If you want to put more than one bean class in the same list,
how about creating the list with parent class PersonDoamin , and then, you can store both PersonDoamin and PersonBean classes.
public static void main(String[] args) {
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
PersonDoamin p = new PersonDoamin();
p.setName("aj");
p.setAge("25");
personList.add(p);
// Changed here. PersonBean => PersonDoamin
List<PersonDoamin> personBeansToReturn = new ArrayList<PersonDoamin>();
Test.listToArray(personList, personBeansToReturn);
// also you can insert PersonBean into the list
personBeansToReturn.add(new PersonBean());
}
I'm trying to insert a method dynamically in an enum.
private void loadEnums(ServletContextEvent sce) {
List<Class<?>> classes = CPScanner.scanClasses(new ClassFilter().packageName("br.com.alinesolutions.anotaai.*").annotation(EnumSerialize.class));
CtClass ctClass = null;
EnumMemberValue enumMemberValue;
try {
for (Class<?> clazz : classes) {
if (!Enum.class.isAssignableFrom(clazz)) {
throw new RuntimeException("class " + clazz + " is not an instance of Enum");
}
ClassPool.getDefault().insertClassPath(new ClassClassPath(clazz));
ctClass = ClassPool.getDefault().get(clazz.getName());
for (CtField field : ctClass.getFields()) {
System.out.println(field);
//CtMethod m = CtNewMethod.make("public String getType() { return this.toString(); }", ctClass);
//ctClass.addMethod(m);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
how to create a method in enum field?
I assume that you try to create a method within an enumeration, i.e.
enum Foo {
BAR {
void qux() { }
}
}
The Java compiler creates such a method by creating a specific class that subclasses Foo and adds the method to this class. You would need to remove the final modifier from Foo, create such a subclass and replace the static initializer that creates the enum field for this.
I use RESTEasy for serializing enum objects and need to put the annotation
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
and create a method with the anotation
#JsonCreator
need to create a method for each enum for this.
I have to create the methods, fromObject getProperty and getType dynamically for all enums, the method fromObject is static. that is, a difference to create this method. I created a annotation and when the context initialize want build these methods
package br.com.alinesolutions.anotaai.metadata.model.domain;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum TipoAcesso {
EMAIL("E-Mail"),
TELEFONE("Telefone");
private String descricao;
private TipoAcesso(String descricao) {
this.descricao = descricao;
}
public String getDescricao() {
return descricao;
}
// TODO - Adicionar metodos dinamicamente
public String getType() {
return this.toString();
}
public String getPropertieKey() {
StringBuilder sb = new StringBuilder("enum.");
sb.append(this.getClass().getName()).append(".");
sb.append(toString());
return sb.toString().toLowerCase();
}
#JsonCreator
public static TipoAcesso fromObject(JsonNode node) {
String type = null;
if (node.getNodeType().equals(JsonNodeType.STRING)) {
type = node.asText();
} else {
if (!node.has("type")) {
throw new IllegalArgumentException();
}
type = node.get("type").asText();
}
return valueOf(type);
}
}
I am in trouble, here is a class I want to Serialize/Deserialize with Jackson 2.3.2.
The serialization works fine but not the deserialization.
I have this exception as below:
No suitable constructor found for type [simple type, class Series]: can not instantiate from JSON object (need to add/enable type information?)
The weirdest thing is that it works perfectly if I comment the constructor!
public class Series {
private int init;
private String key;
private String color;
public Series(String key, String color, int init) {
this.key = key;
this.init = init;
this.color = color;
}
//Getters-Setters
}
And my unit test :
public class SeriesMapperTest {
private String json = "{\"init\":1,\"key\":\"min\",\"color\":\"767\"}";
private ObjectMapper mapper = new ObjectMapper();
#Test
public void deserialize() {
try {
Series series = mapper.readValue(json, Series.class);
} catch (IOException e) {
Assert.fail(e.getMessage());
}
}
}
This exception is throwing from the method deserializeFromObjectUsingNonDefault() of BeanDeserializerBase of Jackson lib.
Any idea?
Thanks
Jackson does not impose the requirement for classes to have a default constructor. You can annotate the exiting constructor with the #JsonCreator annotation and bind the constructor parameters to the properties using the #JsonProperty annotation.
Note: #JsonCreator can be even suppressed if you have single constructor.
This approach has an advantage of creating truly immutable objects which is a good thing for various good reasons.
Here is an example:
public class JacksonImmutable {
public static class Series {
private final int init;
private final String key;
private final String color;
public Series(#JsonProperty("key") String key,
#JsonProperty("color") String color,
#JsonProperty("init") int init) {
this.key = key;
this.init = init;
this.color = color;
}
#Override
public String toString() {
return "Series{" +
"init=" + init +
", key='" + key + '\'' +
", color='" + color + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"init\":1,\"key\":\"min\",\"color\":\"767\"}";
System.out.println(mapper.readValue(json, Series.class));
}
}
You have no default (ie no-args) constructor.
Define a no-args constructor:
public Series() {}
The reason it works when you comment out the 3-arg constructor is in java if there are no constructors, the default constructor is implicitly defined.
This leads to the unexpected effect that if there aren't any constructors and you define a non-default constructor, the (implicit) default constructor disappears! Leading you, like many others before you, to wonder what is going on.