2

I have several TSV files and want to read them and populate in a HashMap of

[first field --> other fields in an object].

For simplicity, assume that there are two files:

File 1 contains two fields (field1 and field2).
File 2 contains three fields (f1, f2 and f3).

So I defined two classes whose objects are to be values in the hashMap:

Class1{
    String field1 = "";
    String field2 = "";
}
Class2{
    String f1 = "";
    String f2 = "";
    String f3 = "";
}

Now, I have these methods:

public static HashMap<String, Class1> readTSV1(String fileName, Class1 c){
...
}

public static HashMap<String, Class2> readTSV2(String fileName, Class2 c){
...
}
...

But I don't want to define various methods for reading from different files:

I want something like this:

public static HashMap<String, Object> readTSV(String fileName, Class<?> c){
    HashMap<String, c.getClass()> hm = new HashMap<String, c.getClass()>(); //error.
    //Look which field names are in type c, 
    //and then read two or three fields from file, 
    //and put them as key and values of hm (first field is key, other fields are put in instance of the respective class, and put as values)
    return hm;
}

static void main(String[] args){
    Class1 c1;
    HashMap<String, Class1> hm1 = new HashMap<String, Class1>();
    hm1 = readTSV("firstFile.tsv", c1.getClass())

    Class2 c2;
    HashMap<String, Class2> hm1 = new HashMap<String, Class2>();
    hm1 = readTSV("firstFile.tsv", c2.getClass())

    ...
}

Any ideas? ...

4
  • 3
    Sounds like what you actually want your method to return is a HashMap<String,List<String>>. You could pass an int to your readTSV method to say how many fields to read. Or you could just keep reading fields and adding them until the end of the line. Commented Sep 18, 2014 at 19:39
  • It works, but this one, if applicable, is more readable. For example, in the main, we can say: (Class1)hm1.get("field1").field1 instead of fields[i]. The number of files and their different fields are too many and may cause mistakes. Commented Sep 18, 2014 at 19:41
  • Well, to do exactly what you've described, you need reflection, to look at the fields in your class. Believe me, it will be far less readable after you add the reflection code. Commented Sep 18, 2014 at 19:42
  • I don't get how that would work out @Alisa. Can you give an example of such a TSV file? And how you actually want to use it? Commented Sep 18, 2014 at 19:59

2 Answers 2

1

Using a Hashmap<String, List<String>> is probably the simplest way. However if you really want these in objects you could do something with an interface.

public interface CSVConvertable {
    /* sets values in this class according to a row in the CSV file */
    public void setCSVValues(String [] values);
}

class Class1 implements CSVConvertable {
    String field1 = "";
    String field2 = "";
    @Override
    public void setCSVValues(String[] values) {
        field1 = values[0];
        field2 = values[1];
    }
}
class Class2 implements CSVConvertable {
    String f1 = "";
    String f2 = "";
    String f3 = "";
    @Override
    public void setCSVValues(String[] values) {
        f1 = values[0];
        f2 = values[1];
        f3 = values[2];
    }
}

public static <T extends CSVConvertable> HashMap<String, T> readTSV(String fileName, Class<T> c) throws InstantiationException, IllegalAccessException{
    HashMap<String, T> hm = new HashMap<String, T>();
    while(/* read rows in csv*/) {
        CSVConvertable conv = c.newInstance();
        conv.setCSVValues(/*your row array here*/);
    }

    return hm;
}


static void main(String[] args){
    HashMap<String, Class1> hm1 = new HashMap<String, Class1>();
    hm1 = readTSV("firstFile.tsv", Class1.class);

    HashMap<String, Class2> hm2 = new HashMap<String, Class2>();
    hm2 = readTSV("firstFile.tsv", Class2.class);

    ...
}

Reflection

If you really want to use reflection here is a basic implementation for it. You should note however tho that this implementaion would change if you ever added a new property to the class, changed a property name or made the class extend another class.

public static <T> List<T> readTSV(String fileName, Class<T> c) throws InstantiationException, IllegalAccessException, IntrospectionException, NoSuchFieldException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException{
    List<T> list = new ArrayList<T>(); //error.
    List<String> properties = getBeanProperties(c);
    Collections.sort(properties);

    // loop through all rows of the TSV and set each value
    while(/*read rows in tsv*/) {
        T obj = c.newInstance();
        for(int i=0;i<properties.size();i++) {
            setProperty(obj, properties.get(i), /* get row column [i] */);
        }
        list.add(obj);
    }

    return list;
}


public static void main(String[] args) throws InstantiationException, IllegalAccessException, IntrospectionException, NoSuchFieldException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException{
    List<Class1> hm1 = readTSV("firstFile.tsv", Class1.class);
    System.out.println(hm1);

    List<Class2> hm2 = readTSV("firstFile.tsv", Class2.class);
    System.out.println(hm2);

}

public static void setProperty(Object obj, String propName, Object value) throws NoSuchFieldException, SecurityException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    String setterName = "set" + propName.substring(0, 1).toUpperCase()
            + propName.substring(1);

    Field field = obj.getClass().getDeclaredField(propName);
    if(Modifier.isPrivate(field.getModifiers())) {
        Method method = obj.getClass().getMethod(setterName, field.getType());
        method.invoke(obj, value);
    } else {
        field.set(obj, value);
    }
}

public static List<String> getBeanProperties(Class<?> cl) {
    List<String> properties = new ArrayList<String>();
    // check all declared fields
    for (Field field : cl.getDeclaredFields()) {
        // if field is private then look for setters/getters
        if (Modifier.isPrivate(field.getModifiers())) {
            // changing 1st letter to upper case
            String name = field.getName();
            String upperCaseName = name.substring(0, 1).toUpperCase()
                    + name.substring(1);
            // and have getter and setter
            try {
                String simpleType = field.getType().getSimpleName();
                //for boolean property methods should be isProperty and setProperty(propertyType)
                if (simpleType.equals("Boolean") || simpleType.equals("boolean")) {
                    if ((cl.getDeclaredMethod("is" + upperCaseName) != null)
                            && (cl.getDeclaredMethod("set" + upperCaseName,
                                    field.getType()) != null)) {
                    }
                    properties.add(name);
                } 
                //for not boolean property methods should be getProperty and setProperty(propertyType)
                else {
                    if ((cl.getDeclaredMethod("get" + upperCaseName) != null)
                            && (cl.getDeclaredMethod("set" + upperCaseName,
                                    field.getType()) != null)) {
                    }
                    properties.add(name);
                }
            } catch (NoSuchMethodException | SecurityException e) {
                // if there is no method nothing bad will happen
            }
        } else {
            // Accessible property that isnt defined by the jre
            if(!field.isSynthetic()) {
                properties.add(field.getName());
            }
        }
    }
    return properties;
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you. It actually works, but I want the class "Class1 implements CSVConvertable" to do this assignment automatically. I want to assign the fields of the class dynamically (without mentioning their name). Let me say the field names in the class are ordered alphabetically (and with the same order as the TSV). So Can we have access to them by some method like getNextProperty().value().
@Alisa you can do it with reflection and sorting the properties by their name but you can't sort them by the order in which they are declared in your source. This is because the java is a compiled language. So if you dont want the interface your next best bet IMO is using annotations. If you dont even want to use annotations you could use some reflection to get all the field names. I would highly advise not to use reflection to get the properties unless you really know what your doing.
@Alisa I added an example of a reflection implementation
1

You can use inheritance

Marker interface

public interface ForClasses{
}

Class1 implements ForClasses {
    ...
}
Class2 implements ForClasses{
    ...
}

Then you can do :

 HashMap<String, ForClasses > hm = new HashMap<String, ForClasses>();

hm can hold both class1 object and class2 object in the map value part....

2 Comments

But to actually use them, you'd need to cast ... I'd prefer David's approach.
It is ok, but what about looking for the field names of the class (to read from file). I mean the three commented lines in the method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.