001    /**
002     * Copyright 2007 Mike Kroutikov.
003     *
004     * This program is free software; you can redistribute it and/or modify
005     *   it under the terms of the Lesser GNU General Public License as 
006     *   published by the Free Software Foundation; either version 3 of
007     *   the License, or (at your option) any later version.
008     *
009     *   This program is distributed in the hope that it will be useful,
010     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
011     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012     *   Lesser GNU General Public License for more details.
013     *
014     *   You should have received a copy of the Lesser GNU General Public License
015     *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
016     */
017    
018    package org.otfeed.samples;
019    
020    import java.lang.reflect.Method;
021    import java.util.Map;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.LinkedList;
025    
026    /**
027     * Class that provides CSV formatting for Java POJO beans.
028     *
029     */
030    public class CSVFormatter {
031            
032            private String delimeter = ", ";
033            
034            public String getDelimeter() { return delimeter; }
035            public void setDelimeter(String val) { delimeter = val; }
036    
037            private static class Prop {
038                    public final String name;
039                    public final Method method;
040    
041                    private Prop(String name, Method method) {
042                            this.name = name;
043                            this.method = method;
044                    }
045            }
046    
047            private final Map<Class<?>,List<Prop>> map
048                                            = new HashMap<Class<?>,List<Prop>>();
049            
050            private final Map<Class<?>,IPropertyFormatter> customFormatters
051                                            = new HashMap<Class<?>,IPropertyFormatter>();
052    
053            public void setCustomPropertyFormatter(Class<?> cls, IPropertyFormatter fmt) {
054                    customFormatters.put(cls, fmt);
055            }
056            
057            private static String normalizeName(String name) {
058                    if(name.startsWith("get")) {
059                            name = name.substring(3);
060                    } else if(name.startsWith("is")) {
061                            name = name.substring(2);
062                    }
063    
064                    if(name.length() > 1 && Character.isLowerCase(name.charAt(1))) {
065                            return name.substring(0, 1).toLowerCase() + name.substring(1);
066                    } else {
067                            return name;
068                    }
069            }
070    
071            private static boolean isReadableProperty(Method method) {
072                    String name = method.getName();
073    
074                    if(method.getParameterTypes().length > 0) return false;
075    
076                    if(name.length() > 3 && name.startsWith("get")) {
077                            return true;
078                    } else if(name.length() > 2 && name.startsWith("is")) {
079                            // FIXME: check for boolean class on output??
080                            return true;
081                    }
082    
083                    return false;
084            }
085    
086            private static List<Prop> introspect(Class<?> cls) throws Exception {
087    
088                    Method[] method = cls.getDeclaredMethods();
089    
090                    List<Prop> list = new LinkedList<Prop>();
091                    for(int i = 0; i < method.length; i++) {
092                            if(!isReadableProperty(method[i])) continue;
093    
094                            String name = normalizeName(method[i].getName());
095                            list.add(new Prop(name, method[i]));
096                    }
097    
098                    return list;
099            }
100    
101            public String header(Class<?> cls) {
102    
103                    List<Prop> prop = map.get(cls);
104                    if(prop == null) {
105                            try {
106                                    prop = introspect(cls);
107                                    map.put(cls, prop);
108                            } catch(Exception ex) {
109                                    throw new AssertionError();
110                            }
111                    }
112    
113                    String out = "";
114                    for(Prop p : prop) {
115                            if(out.length() > 0) out += ", ";
116                            out += p.name;
117                    }
118    
119                    return out;
120            }
121    
122            public String format(Object obj) {
123                    if(obj == null) return "null";
124    
125                    Class<?> cls = obj.getClass();
126                    List<Prop> prop = map.get(cls);
127                    if(prop == null) {
128                            try {
129                                    prop = introspect(cls);
130                                    map.put(cls, prop);
131                            } catch(Exception ex) {
132                                    throw new AssertionError();
133                            }
134                    }
135    
136                    String out = "";
137                    for(Prop p : prop) {
138                            if(out.length() > 0) out += ", ";
139                            try {
140                                    Object val = p.method.invoke(obj);
141                                    IPropertyFormatter fmt = customFormatters.get(val.getClass());
142                                    if(fmt != null) {
143                                            out += fmt.format(val);
144                                    } else {
145                                            out += val;
146                                    }
147                            } catch(Exception ex) {
148                                    throw new AssertionError();
149                            }
150                    }
151    
152                    return out;
153            }
154    }