Related
The code is groovy but the answer can be both, Groovy or Java.
I have a Person class with this fields:
class Person(){
String name
String lasName
}
I have a method that returns two objects from the same class. One object with some fields and the other with the rest, in my example it would be like this:
person1 = "name : Jon"
person2 = "lastName : Snow"
What I need is to replace all the null fields of person1 with the person2 field if this is not null, in our example, the output would be:
person1.merge(person2)
person1= "name : Jon, lastName : Snow"
Is there any method on Java or Groovy to do something similar to this without writing all my fields(using some kind of loop)?
If there isn't any default method to use, how can I iterate through all the fields from a class?
Just tested using reflection. The desired output is
merged person:Person{name=John, lastName=Snow}
public static void testReflection() {
Person p1 = new Person("John", null);
Person p2 = new Person(null, "Snow");
Person merged = (Person) mergePersons(p1, p2);
System.out.println("merged person:" + merged);
}
public static Object mergePersons(Object obj1, Object obj2) throws Exception {
Field[] allFields = obj1.getClass().getDeclaredFields();
for (Field field : allFields) {
if (Modifier.isPublic(field.getModifiers()) && field.isAccessible() && field.get(obj1) == null && field.get(obj2) != null) {
field.set(obj1, field.get(obj2));
}
}
return obj1;
}
mergePersons accepts two Objects.
Then it go through all fields and validate if the first object has a null value.
If yes, then it verify if the second object is not nulled.
If this is true it assigns the value to the first Object.
Providing this solution you only access public data. If you want to access private data aswell, you need to remove the Modifier verification and set if accessible before like:
public static Object mergePersons(Object obj1, Object obj2) throws Exception {
Field[] allFields = obj1.getClass().getDeclaredFields();
for (Field field : allFields) {
if (!field.isAccessible() && Modifier.isPrivate(field.getModifiers()))
field.setAccessible(true);
if (field.get(obj1) == null && field.get(obj2) != null) {
field.set(obj1, field.get(obj2));
}
}
return obj1;
}
This is a quick (and presumptuous) approach that is basically the same as using reflection on the fields but instead uses:
Groovy's built-in getProperties() method on java.lang.Object, which provides us with a Map of its property names and values
Groovy's default Map constructor, which allows use to create instances of an Object given a Map of properties.
Given these two features, you can describe each object you want to merge as a Map of their properties, strip out the null-valued entries, combine the Maps together (and remove the pesky 'class' entry which is readonly), and use the merged Map to construct your merged instance.
class Person {
String first, last, middle
}
def p1 = new Person(first: 'bob')
def p2 = new Person(last: 'barker')
Person merged = (p1.properties.findAll { k, v -> v } // p1's non-null properties
+ p2.properties.findAll { k, v -> v }) // plus p2's non-null properties
.findAll { k, v -> k != 'class' } // excluding the 'class' property
assert merged.first == 'bob'
assert merged.last == 'barker'
assert merged.middle == null
Given Groovy fields are implemented as a getter/setter pair with a backing field you can probably do it this way in Groovy:
static <T> void merge(T from, T to) {
from.metaClass.properties.findAll { p ->
p.getProperty(to) == null &&
p.getProperty(from) != null &&
to.respondsTo(MetaProperty.getSetterName(p.name))
}
.each {
p -> p.setProperty(to, p.getProperty(from))
}
}
You're going to have to go the reflection route. I'm assuming you have a default constructor, otherwise the following won't work. Also, it needs two same types.
public static <T> T mergeObjects(T first, T second) throws IllegalAccessException, InstantiationException {
Class<?> clazz = first.getClass();
Field[] fields = clazz.getDeclaredFields();
Object returnValue = clazz.newInstance();
for (Field field : fields) {
field.setAccessible(true);
Object value1 = field.get(first);
Object value2 = field.get(second);
Object value = (value1 != null) ? value1 : value2;
field.set(returnValue, value);
}
return (T) returnValue;
}
Here is example
import java.lang.reflect.Field;
public class Merge2Obj {
private String name;
private String lasName;
public Merge2Obj() {
super();
}
public Merge2Obj(String name, String lasName) {
super();
this.name = name;
this.lasName = lasName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLasName() {
return lasName;
}
public void setLasName(String lasName) {
this.lasName = lasName;
}
public static <T> T mergeObjects(T first, T second) throws IllegalAccessException, InstantiationException {
Class<?> clazz = first.getClass();
Field[] fields = clazz.getDeclaredFields();
Object returnValue = clazz.newInstance();
for (Field field : fields) {
field.setAccessible(true);
Object value1 = field.get(first);
Object value2 = field.get(second);
Object value = (value1 != null) ? value1 : value2;
field.set(returnValue, value);
}
return (T) returnValue;
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Merge2Obj obj1 = new Merge2Obj("ABC", null);
Merge2Obj obj2 = new Merge2Obj("PQR", "LMN");
Merge2Obj obj3 = mergeObjects(obj1, obj2);
System.out.println(obj3.name);
System.out.println(obj3.lasName);
}
}
Assuming a mutable data class with getters and setters, Apache BeanUtils may suit your needs.
By default BeanUtilBeansBean.copyProperties(Object dest, Object orig) looks for pairs of T orig.get*() and dest.set*(T value), and calls the latter with the result of the former.
But you can inject a custom PropertyUtilsBean, so you could wrap the default one to prevent it from replacing non-null properties:
public NoClobberPropertyUtilsBean extends PropertyUtilsBean {
#Override
public void setSimpleProperty((Object bean,
String name,
Object value)
throws IllegalAccessException,
InvocationTargetException,
NoSuchMethodException {
if(getProperty(bean,name) == null) {
super.setSimpleProperty(bean,name,value);
}
}
}
Now you can merge with:
BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), new NoClobberPropertyUtilsBean());
Person merged = new Person();
beanUtils.copyProperties(person1);
beanUtils.copyProperties(person2);
If a property is non-null in both sources, the first copyProperties wins.
You could of course change the semantics, for example it would behave a different way if the guard was if(value != null).
At one level BeanUtils is just a wrapper around the kind of Reflection operations other answers suggest. It's up to you whether you want the extra level of abstraction. You may need to override more methods if you want to support map/list members, or BeanUtils' DynaBean class.
I am using spring and hibernate. I have a class (DTO) with a lot of string member variables. I'm trying to implement search for this class. The user should be able to search by each field. I'm using jackson json mapper to serialize and deserialize objects. Is there anyway to identify the fieldName by using JsonProperty value?
Let this be an example: my DTO
public class SampleDTO{
private String field1;
private String field2;
private String field3;
private String field4;
#JsonProperty("FIELD_1")
public String getField1(){
return field1;
}
#JsonProperty("FIELD_2")
public String getField2(){
return field2;
}
#JsonProperty("FIELD_3")
public String getField3(){
return field3;
}
#JsonProperty("FIELD_4")
public String getField4(){
return field4;
}
}
Let this be my search function
public Set<T> search(String fieldName, String searchKeyword) {
String originalFieldName = someMagicFunction(fieldName);
//if fieldName= "FIELD_1", someMagicFunction should return "field1"
Criteria criteria = session.createCriteria(T.class);
criteria.add(Restrictions.eq(originalFieldName, searchKeyword));
return new HashSet<T>(criteria.list());
}
Any implementation is fine. I'm looking for a good approach to handle cases like this. It feels like finding fields manually involves "too much typing".
You basically want to use reflection. There are two possibilities here when it comes to field lookup:
Value of #JsonProperty annotation
Real name of the field
In the first case you may want to use some additional library to ease the pain when using reflection + annotation, but the crude code would look more less like this:
SampleDTO dto = new SampleDTO();
// setup some values here
Field[] fields = r.getClass().getFields();
for(Field f : fields) {
JsonProperty jsonProperty = f.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals("FIELD_1")) {
return (String) f.get(dto);
}
// throw exception since passed field name is illegal
}
In the second one it would be so much easier:
SampleDTO dto = new SampleDTO();
// setup some values here
String field1Value = (String) r.getClass().getField("field1").get(dto);
In case if anyone is interested, this is how I solved the problem. I added this code to DAO's constructor.
try {
BeanInfo beanInfo = Introspector.getBeanInfo(T.class);
Method[] methods = T.class.getMethods();
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor: propertyDescriptors) {
//I'm looking for string fields only
if (propertyDescriptor.getPropertyType().equals( String.class)) {
//My annotations are on methods
for(Method method: methods) {
if(propertyDescriptor.getReadMethod().equals(method)) {
JsonProperty jsonProperty = method.getAnnotation(JsonProperty.class);
if (jsonProperty != null) {
//jsonFieldMapping is a Map<String,String>
//will be saving the mapping in the format {"FIELD_1":"field1", "FIELD_2":"field2"}
jsonFieldMapping.put(jsonProperty.value(), propertyDescriptor.getDisplayName());
} else {
logger.debug("jsonProperty is null");
}
}
}
}
}
// just printing out the values identified from class
for(String key: jsonFieldMapping.keySet()) {
logger.debug("key: " + key + "value: " + jsonFieldMapping.get(key));
}
} catch (IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
So, my magic method will be
public String getField(String jsonFieldName){
if (jsonFieldMapping.containsKey(jsonFieldName)) {
return jsonFieldMapping.get(jsonFieldName);
} else {
throw new IllegalArgumentException("searching field not found");
}
}
I haven't tested this code completely. Looks like the values in the logs are correct.
I have two classes, as follows:
public class Person {
private String dob;
private PersonName personName;
}
public class PersonName {
private String firstName;
private String lastName;
}
I am setting these values dynamically using Java Reflection.
First, I create an instance of Person and I set the value for dob. After that, I need to set a PersonName value in Person. So I created another instance of PersonName and I set the values in that PersonName. After that, I am trying to set the PersonName instance in the Person entity.
For that I used code like this:
Class componentClass = Class.forName(clazz.getName());
Field field = parentClass.getDeclaredField(Introspector
.decapitalize(clazz.getSimpleName()));
field.setAccessible(true);
field.set(parentClass, componentClass);
Here, parentClass is a Person instance and componentClass is a PersonName instance. I am trying to set the PersonName in the Person, but I am getting the following exception:
java.lang.IllegalArgumentException: Can not set com.rise.common.model.PersonName field
com.rise.common.model.Person.personName to java.lang.Class
So how can I set the values dynamically?
Thanks.
My Whole Code:
protected void assignProperties(List<Object[]> argResults,
List<Class> argAllClassesList, Class argParentClass)
throws ClassNotFoundException, NoSuchFieldException,
SecurityException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException, InstantiationException {
List<Object[]> results = (List<Object[]>) Precondition.ensureNotEmpty(
argResults, "Output List");
List<Class<?>> personClassList = new ArrayList<Class<?>>();
for (Object[] recordValues : results) {
Class parentClass = Class.forName(this.getPersistentClass()
.getName());
parentClass.newInstance();
int count = 0;
count = assignValues(recordValues, parentClass, count);
for (Class clazz : argAllClassesList) {
Class componentClass = Class.forName(clazz.getName());
componentClass.newInstance();
String decapitalize = Introspector.decapitalize(clazz
.getSimpleName());
Field field = parentClass.getDeclaredField(decapitalize);
field.setAccessible(true);
assignValues(recordValues, componentClass, count);
field.set(parentClass, componentClass);
}
personClassList.add(parentClass);
}
for (Class<?> class1 : personClassList) {
Class<Person> person = (Class<Person>) class1;
System.out.println(person);
}
}
private int assignValues(Object[] argRecordValues, Class argClass,
int argCount) {
String paramName = Introspector.decapitalize(argClass.getSimpleName());
if (Precondition.checkNotEmpty(paramName)) {
List<Field> fieldNames = TenantConfigHelper.getInstance()
.getModelNameVsFieldsMap().get(paramName);
try {
for (Field field : fieldNames) {
BeanUtils.setProperty(argClass, field.getName(),
argRecordValues[argCount]);
++argCount;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return argCount;
}
The message explains what's wrong: componentClass is not an instance of PersonName. It's an object of type Class (probably Class<PersonName>). You probably forgot to instantiate the class.
Edit:
Your code does:
parentClass.newInstance();
and
componentClass.newInstance();
This is the equivalent of doing
new Parent();
and
new ParentName();
So it creates an instance, but doesn't assign it to any variable, and thus doesn't do anything with the created instance, which will be garbage-collectable immediately.
You want
Object parent = parentClass.newInstance();
Object component = componentClass.newInstance();
field.set(parent, component);
I think there may be some confusion over the difference between Java class definitions and instances at work here. You want to set the values of fields on particular instances, not the classes themselves. Something like this may work:
Object parentClassInstance = parentClass.newInstance();
Class componentClass = Class.forName(clazz.getName());
Object componentClassInstance = componentClass.newInstance();
Field field = parentClass.getDeclaredField(Introspector
.decapitalize(clazz.getSimpleName()));
field.setAccessible(true);
field.set(parentClassInstance, componentClassInstance);
Looking at the whole code sample, however, it is a little hard to follow. Why have a List of Classes, with a name like personClassList, which would seem to indicate that each class should be the same class, Person? I feel it should probably instead be a List<Person> or perhaps List<Object>, which you would populate with your Person instances, not the Class objects themselves.
Edit to answer the following question in a comment:
I have to return List instances insetad of List so how can I type case from Class to Person dynamically...?
You can't cast from Class to Person, since Person is probably not a subclass of Class.
Instead, declare your list as a List<Person> instead of a List<Class<?>>
List<Person> personList = new ArrayList<Person>();
Then add the Person objects instead of the Class objects to your list at the bottom of your first for loop.
personList.add((Person)parentClassInstance);
And the loop at the bottom will need to change too:
for (Person person : personList) {
System.out.println(person);
}
I was looking for this same type of answer and basically what everyone is specifying with the Object is correct. I also created a generic list like List<Object> instead of the actual class name. Below is a function that returns a List of type Object's. I pass in the class name and load that with the .loadClass. Than create a new object with that new class instance with the .newInstance. The dList gets loaded with all the information for each objectClass which is the class I pass in with the className variable. The rest is basically just dynamically invoking all of the "set" methods within that particular class with the values from the result set.
protected List<Object> FillObject(ResultSet rs, String className)
{
List<Object> dList = new ArrayList<Object>();
try
{
ClassLoader classLoader = GenericModel.class.getClassLoader();
while (rs.next())
{
Class reflectionClass = classLoader.loadClass("models." + className);
Object objectClass = reflectionClass.newInstance();
Method[] methods = reflectionClass.getMethods();
for(Method method: methods)
{
if (method.getName().indexOf("set") > -1)
{
Class[] parameterTypes = method.getParameterTypes();
for(Class pT: parameterTypes)
{
Method setMethod = reflectionClass.getMethod(method.getName(), pT);
switch(pT.getName())
{
case "int":
int intValue = rs.getInt(method.getName().replace("set", ""));
setMethod.invoke(objectClass, intValue);
break;
case "java.util.Date":
Date dateValue = rs.getDate(method.getName().replace("set", ""));
setMethod.invoke(objectClass, dateValue);
break;
case "boolean":
boolean boolValue = rs.getBoolean(method.getName().replace("set", ""));
setMethod.invoke(objectClass, boolValue);
break;
default:
String stringValue = rs.getString(method.getName().replace("set", ""));
setMethod.invoke(objectClass, stringValue);
break;
}
}
}
}
dList.add(objectClass);
}
}
catch (Exception e)
{
this.setConnectionMessage("ERROR: reflection class loading: " + e.getMessage());
}
return dList;
}
The type system is verified by the compiler. While there is some additional safety possible during runtime, that additional safety is not even close to the safety the compiler imposes. Which begs the question, Why?
Assuming you could get the stuff to work, exactly how would you be able to justify casting class1 below into a Class<Person> type? You declared it to be a Class<?>!
for (Class<?> class1 : personClassList) {
Class<Person> person = (Class<Person>) class1;
System.out.println(person);
}
Can I write a generic method to trim all strings within an complex object (object containing other objects)? Should java reflection api be used to achieve this?Thanks.
I have provided a sample below. However in reality there could be multiple objects within objects. Each object might contain a collection of String or collection of other objects which may contain String. Is there a way to trim the Strings - ones directly with the objects and ones within collection.
public class School{
private List<Course> courses;
private List<Student> students;
// Getters and Setters
}
public class Course{
private String name;
private String xxx;
private String yyy;
private List<String> zzzList;
}
public class Student{
private Map<String,String> xxx;
private List<Course> courseList;
}
Yes, reflection is the way. Basically, you need to:
get the class of the top level object (with [object].getClass())
get all the fields of the object (with clazz.getFields() - beware, it works only with public fields)
check if the field is String (either get field.getType() and check it's a string, or do a field.get(the object) and a instanceof String)
if it's the case, replace the string in the object with the trimmed one, using field.set([your object],[trimmed string])
if the field is an object but not a string, call your method recursively
That will do the trick.
---- just seen your update
Trimming strings in collection will be more tricky, since the strings are not exposed as public fields of the collection (List for example).
You will need something more clever, that will check if an object is an instance of List, or Map, or etc... (or a derived class!).
Main problem is also that java generics are done with erasing type at compile type. So you cannot know that your field is List[String] or List[Integer] or whatever. Every List[?] becomes List.
Still you can try to do it like that:
if field type is List
iterate through the list values
if a value is instanceof String, you have to remove it from the list and insert in place the trimmed version
if a value is an object, there you go again recursively with your method.
Not very interesting in real life samples, but more on a library side maybe.
Long way to go though!
Yes, you can do that with reflection, quite easily. Just check if the field is instanceof String.
The exact way to do it depends on your object structure.
/*********************************************************************************************
* Trim first level children of string type in this object
* #param obj which all string properties to be trimmed
*********************************************************************************************/
public static void trimAll(final Object obj)
throws LocalException
{
if (obj==null) return;
final Class c = obj.getClass();
final Method[] methods = c.getMethods();
final Class[] SETTER_ARGS = new Class[]{String.class};
final Object[] SETTER_VAL = new Object[1];
final String SET = "set";
final String GET = "get";
final String SPACE = "\u0020";
final String TAB = "\t";
for (final Method m:methods)
{
try
{
final String name=m.getName();
if (
name.length()>GET.length()
&& name.indexOf(GET)==0
&& m.getReturnType().equals(String.class)
&& m.getParameterTypes().length==0)
{
final String v = (String)m.invoke(obj);
if (v!=null && (v.contains(SPACE) || v.contains(TAB)) )
{
final Method setter=c.getMethod(SET+name.substring(3),SETTER_ARGS);
if (setter!=null)
{
SETTER_VAL[0]=v.trim();
setter.invoke(obj,SETTER_VAL);
}
}
}
}
catch (final Throwable e)
{
throw new LocalException(LocalException.EC_GENERAL_EXCEPTION,e);
}
}
}
We can also use Jackson to serialize and then deserialize the object. While deserializing we can use custom deserializer to trim all the String values.
Create a deserializer like this:
public class TrimStringToNullDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (isNull(value)) {
return null;
}
value = value.trim();
if (value.length() == 0) {
value = null;
}
return value;
}
And then we can use Jackson to trim all values:
public class TrimStringToNullConfiguration {
private ObjectMapper objectMapper;
public Client trimToNull(Client inputClient) throws JsonProcessingException {
return getObjectMapper().readValue(getObjectMapper().writeValueAsString(inputClient), Client.class);
}
private ObjectMapper getObjectMapper() {
if (isNull(objectMapper)) {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new TrimStringToNullDeserializer());
objectMapper.registerModule(module);
}
return objectMapper;
}
I have placed a working example over here.
private <T> T toTrim(T t) {
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
try {
if (field.get(t) instanceof String) {
Object o = field.get(t);
String s = (String) o;
field.set(t, s.trim().toUpperCase());
}
} catch (IllegalAccessException e) {
log.info("Error converting field "+ field.getName() );
}
}
return t;
}
if (yourObject instanceof String){
yourObject = yourObject.trim();
}
Hope it helps :)
I need to compare dozens of fields in two objects (instances of the same class), and do some logging and updating in case there are differences. Meta code could look something like this:
if (a.getfield1 != b.getfield1)
log(a.getfield1 is different than b.getfield1)
b.field1 = a.field1
if (a.getfield2!= b.getfield2)
log(a.getfield2 is different than b.getfield2)
b.field2 = a.field2
...
if (a.getfieldn!= b.getfieldn)
log(a.getfieldn is different than b.getfieldn)
b.fieldn = a.fieldn
The code with all the comparisons is very terse, and I would like to somehow make it more compact. It would be nice if I could have a method which would take as a parameter method calls to setter and getter, and call this for all fields, but unfortunately this is not possible with java.
I have come up with three options, each which their own drawbacks.
1. Use reflection API to find out getters and setters
Ugly and could cause run time errors in case names of fields change
2. Change fields to public and manipulate them directly without using getters and setters
Ugly as well and would expose implementation of the class to external world
3. Have the containing class (entity) do the comparison, update changed fields and return log message
Entity should not take part in business logic
All fields are String type, and I can modify code of the class owning the fields if required.
EDIT: There are some fields in the class which must not be compared.
Use Annotations.
If you mark the fields that you need to compare (no matter if they are private, you still don't lose the encapsulation, and then get those fields and compare them. It could be as follows:
In the Class that need to be compared:
#ComparableField
private String field1;
#ComparableField
private String field2;
private String field_nocomparable;
And in the external class:
public <T> void compare(T t, T t2) throws IllegalArgumentException,
IllegalAccessException {
Field[] fields = t.getClass().getDeclaredFields();
if (fields != null) {
for (Field field : fields) {
if (field.isAnnotationPresent(ComparableField.class)) {
field.setAccessible(true);
if ( (field.get(t)).equals(field.get(t2)) )
System.out.println("equals");
field.setAccessible(false);
}
}
}
}
The code is not tested, but let me know if helps.
The JavaBeans API is intended to help with introspection. It has been around in one form or another since Java version 1.2 and has been pretty usable since version 1.4.
Demo code that compares a list of properties in two beans:
public static void compareBeans(PrintStream log,
Object bean1, Object bean2, String... propertyNames)
throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
Set<String> names = new HashSet<String>(Arrays
.asList(propertyNames));
BeanInfo beanInfo = Introspector.getBeanInfo(bean1
.getClass());
for (PropertyDescriptor prop : beanInfo
.getPropertyDescriptors()) {
if (names.remove(prop.getName())) {
Method getter = prop.getReadMethod();
Object value1 = getter.invoke(bean1);
Object value2 = getter.invoke(bean2);
if (value1 == value2
|| (value1 != null && value1.equals(value2))) {
continue;
}
log.format("%s: %s is different than %s%n", prop
.getName(), "" + value1, "" + value2);
Method setter = prop.getWriteMethod();
setter.invoke(bean2, value2);
}
}
if (names.size() > 0) {
throw new IllegalArgumentException("" + names);
}
}
Sample invocation:
compareBeans(System.out, bean1, bean2, "foo", "bar");
If you go the annotations route, consider dumping reflection and generating the comparison code with a compile-time annotation processor or some other code generator.
I would go for option 1, but I would use getClass().getDeclaredFields() to access the fields instead of using the names.
public void compareAndUpdate(MyClass other) throws IllegalAccessException {
for (Field field : getClass().getDeclaredFields()) {
if (field.getType() == String.class) {
Object thisValue = field.get(this);
Object otherValue = field.get(other);
// if necessary check for null
if (!thisValue.equals(otherValue)) {
log(field.getName() + ": " + thisValue + " <> " + otherValue);
field.set(other, thisValue);
}
}
}
}
There are some restrictions here (if I'm right):
The compare method has to be implemented in the same class (in my opinion it should - regardless of its implementation) not in an external one.
Just the fields from this class are used, not the one's from a superclass.
Handling of IllegalAccessException necessary (I just throw it in the example above).
This is probably not too nice either, but it's far less evil (IMHO) than either of the two alternatives you've proposed.
How about providing a single getter/setter pair that takes a numeric index field and then have getter/setter dereference the index field to the relevant member variable?
i.e.:
public class MyClass {
public void setMember(int index, String value) {
switch (index) {
...
}
}
public String getMember(int index) {
...
}
static public String getMemberName(int index) {
...
}
}
And then in your external class:
public void compareAndUpdate(MyClass a, MyClass b) {
for (int i = 0; i < a.getMemberCount(); ++i) {
String sa = a.getMember();
String sb = b.getMember();
if (!sa.equals(sb)) {
Log.v("compare", a.getMemberName(i));
b.setMember(i, sa);
}
}
}
This at least allows you to keep all of the important logic in the class that's being examined.
While option 1 may be ugly, it will get the job done. Option 2 is even uglier, and opens your code to vulnerabilities you can't imagine. Even if you eventually rule out option 1, I pray you keep your existing code and not go for option 2.
Having said this, you can use reflection to get a list of the field names of the class, if you don't want to pass this as a static list to the method. Assuming you want to compare all fields, you can then dynamically create the comparisons, in a loop.
If this isn't the case, and the strings you compare are only some of the fields, you can examine the fields further and isolate only those that are of type String, and then proceed to compare.
Hope this helps,
Yuval =8-)
since
All fields are String type, and I can modify code of the class owning the fields if required.
you could try this class:
public class BigEntity {
private final Map<String, String> data;
public LongEntity() {
data = new HashMap<String, String>();
}
public String getFIELD1() {
return data.get(FIELD1);
}
public String getFIELD2() {
return data.get(FIELD2);
}
/* blah blah */
public void cloneAndLogDiffs(BigEntity other) {
for (String field : fields) {
String a = this.get(field);
String b = other.get(field);
if (!a.equals(b)) {
System.out.println("diff " + field);
other.set(field, this.get(field));
}
}
}
private String get(String field) {
String value = data.get(field);
if (value == null) {
value = "";
}
return value;
}
private void set(String field, String value) {
data.put(field, value);
}
#Override
public String toString() {
return data.toString();
}
magic code:
private static final String FIELD1 = "field1";
private static final String FIELD2 = "field2";
private static final String FIELD3 = "field3";
private static final String FIELD4 = "field4";
private static final String FIELDN = "fieldN";
private static final List<String> fields;
static {
fields = new LinkedList<String>();
for (Field field : LongEntity.class.getDeclaredFields()) {
if (field.getType() != String.class) {
continue;
}
if (!Modifier.isStatic(field.getModifiers())) {
continue;
}
fields.add(field.getName().toLowerCase());
}
}
this class has several advantages:
reflects once, at class loading
it is very simply adding new fields, just add new static field (a better solution here
is using Annotations: in the case you care using reflection works also java 1.4)
you could refactor this class in an abstract class, all derived class just get both
data and cloneAndLogDiffs()
the external interface is typesafe (you could also easily impose immutability)
no setAccessible calls: this method is problematic sometimes
A broad thought:
Create a new class whose object takes the following parameters: the first class to compare, the second class to compare, and a lists of getter & setter method names for the objects, where only methods of interest are included.
You can query with reflection the object's class, and from that its available methods. Assuming each getter method in the parameter list is included in the available methods for the class, you should be able to call the method to get the value for comparison.
Roughly sketched out something like (apologies if it isn't super-perfect... not my primary language):
public class MyComparator
{
//NOTE: Class a is the one that will get the value if different
//NOTE: getters and setters arrays must correspond exactly in this example
public static void CompareMyStuff(Object a, Object b, String[] getters, String[] setters)
{
Class a_class = a.getClass();
Class b_class = b.getClass();
//the GetNamesFrom... static methods are defined elsewhere in this class
String[] a_method_names = GetNamesFromMethods(a_class.getMethods());
String[] b_method_names = GetNamesFromMethods(b_class.getMethods());
String[] a_field_names = GetNamesFromFields(a_class.getFields());
//for relative brevity...
Class[] empty_class_arr = new Class[] {};
Object[] empty_obj_arr = new Object[] {};
for (int i = 0; i < getters.length; i++)
{
String getter_name = getter[i];
String setter_name = setter[i];
//NOTE: the ArrayContainsString static method defined elsewhere...
//ensure all matches up well...
if (ArrayContainsString(a_method_names, getter_name) &&
ArrayContainsString(b_method_names, getter_name) &&
ArrayContainsString(a_field_names, setter_name)
{
//get the values from the getter methods
String val_a = a_class.getMethod(getter_name, empty_class_arr).invoke(a, empty_obj_arr);
String val_b = b_class.getMethod(getter_name, empty_class_arr).invoke(b, empty_obj_arr);
if (val_a != val_b)
{
//LOG HERE
//set the value
a_class.getField(setter_name).set(a, val_b);
}
}
else
{
//do something here - bad names for getters and/or setters
}
}
}
}
You say you presently have getters and setters for all these fields? Okay, then change the underlying data from a bunch of individual fields to an array. Change all the getters and setters to access the array. I'd create constant tags for the indexes rather than using numbers for long-term maintainability. Also create a parallel array of flags indicating which fields should be processed. Then create a generic getter/setter pair that use an index, as well as a getter for the compare flag. Something like this:
public class SomeClass
{
final static int NUM_VALUES=3;
final static int FOO=0, BAR=1, PLUGH=2;
String[] values=new String[NUM_VALUES];
static boolean[] wantCompared={true, false, true};
public String getFoo()
{
return values[FOO];
}
public void setFoo(String foo)
{
values[FOO]=foo;
}
... etc ...
public int getValueCount()
{
return NUM_VALUES;
}
public String getValue(int x)
{
return values[x];
}
public void setValue(int x, String value)
{
values[x]=value;
}
public boolean getWantCompared(int x)
{
return wantCompared[x];
}
}
public class CompareClass
{
public void compare(SomeClass sc1, SomeClass sc2)
{
int z=sc1.getValueCount();
for (int x=0;x<z;++x)
{
if (!sc1.getWantCompared[x])
continue;
String sc1Value=sc1.getValue(x);
String sc2Value=sc2.getValue(x);
if (!sc1Value.equals(sc2Value)
{
writeLog(x, sc1Value, sc2Value);
sc2.setValue(x, sc1Value);
}
}
}
}
I just wrote this off the top of my head, I haven't tested it, so their may be bugs in the code, but I think the concept should work.
As you already have getters and setters, any other code using this class should continue to work unchanged. If there is no other code using this class, then throw away the existing getters and setters and just do everything with the array.
I would also propose a similar solution to the one by Alnitak.
If the fields need to be iterated when comparing, why not dispense with the separate fields, and put the data into an array, a HashMap or something similar that is appropriate.
Then you can access them programmatically, compare them etc. If different fields need to be treated & compared in different ways, you could create approriate helper classes for the values, which implement an interface.
Then you could just do
valueMap.get("myobject").compareAndChange(valueMap.get("myotherobject")
or something along those lines...