1+ package root .utils ;
2+
3+ import java .lang .reflect .Constructor ;
4+ import java .lang .reflect .Field ;
5+ import java .lang .reflect .Method ;
6+ import java .util .ArrayList ;
7+ import java .util .Arrays ;
8+ import java .util .LinkedHashMap ;
9+ import java .util .List ;
10+ import java .util .Map ;
11+ import java .util .stream .Collectors ;
12+
13+ import org .apache .commons .lang3 .StringUtils ;
14+ import org .apache .commons .text .StringEscapeUtils ;
15+
16+ public class CsvUtils {
17+
18+ /**
19+ * 객체리스트를 csv string 형태로 변환한다.
20+ *
21+ * @param list
22+ * @param clazz
23+ * @return
24+ */
25+ public static String toCsvString (List <?> list , Class <?> clazz ) {
26+ StringBuffer sb = new StringBuffer ();
27+
28+ try {
29+ Field [] fields = clazz .getDeclaredFields ();
30+ List <String > fieldName = Arrays .asList (fields ).stream ().map (f -> (f .getName ()))
31+ .collect (Collectors .toList ());
32+
33+ sb .append (createCsvHeader (fieldName ));
34+
35+ for (Object item : list ) {
36+ sb .append (System .lineSeparator ()).append (createCsvRow (item , clazz ));
37+ }
38+ } catch (IllegalArgumentException | IllegalAccessException e ) {
39+ e .printStackTrace ();
40+ }
41+
42+ return sb .toString ();
43+ }
44+
45+ /**
46+ * 문자열리스트를 comma(,) 문자로 이어 csv형태의 헤더를 반환한다.
47+ *
48+ * @param fieldNames
49+ * @return
50+ */
51+ public static String createCsvHeader (List <String > fieldNames ) {
52+ StringBuffer sb = new StringBuffer ();
53+
54+ for (String f : fieldNames ) {
55+ sb .append (sb .isEmpty () ? wrapInDoubleQuotation (f ) : "," + wrapInDoubleQuotation (f ));
56+ }
57+
58+ return sb .toString ();
59+ }
60+
61+ /**
62+ * 매개변수로 전달된 Class의 필드와, 부모 클래스의 필드를 comma(,)로 이어 csv형태의 헤더를 반환한다.
63+ *
64+ * @param clazz
65+ * @return
66+ */
67+ public static String createCsvHeader (Class <?> clazz ) {
68+ StringBuffer sb = new StringBuffer ();
69+
70+ try {
71+ List <Field > fields = getAllFields (clazz );
72+ List <String > fieldName = fields .stream ().map (f -> (f .getName ())).collect (Collectors .toList ());
73+
74+ sb .append (createCsvHeader (fieldName ));
75+ } catch (IllegalArgumentException e ) {
76+ e .printStackTrace ();
77+ }
78+
79+ return sb .toString ();
80+ }
81+
82+ /**
83+ *
84+ * @param object
85+ * @param clazz
86+ * @return
87+ * @throws IllegalArgumentException
88+ * @throws IllegalAccessException
89+ */
90+ public static String createCsvRow (Object object , Class <?> clazz )
91+ throws IllegalArgumentException , IllegalAccessException {
92+ StringBuffer sb = new StringBuffer ();
93+
94+ if (StringUtils .equals (object .getClass ().getName (), clazz .getName ())) {
95+ for (Field f : getAllFields (clazz )) {
96+ @ SuppressWarnings ("deprecation" )
97+ boolean accessible = f .isAccessible ();
98+
99+ f .setAccessible (true );
100+
101+ String appender = sb .isEmpty ()
102+ ? StringUtils .getIfEmpty (wrapInDoubleQuotation (f .get (object ).toString ()),
103+ () -> wrapInDoubleQuotation ("-" ))
104+ : "," + StringUtils .getIfEmpty (wrapInDoubleQuotation (f .get (object ).toString ()),
105+ () -> wrapInDoubleQuotation ("-" ));
106+ sb .append (appender );
107+
108+ f .setAccessible (accessible );
109+ }
110+ }
111+
112+ return sb .toString ();
113+ }
114+
115+ /**
116+ *
117+ * @param <T>
118+ * @param headers
119+ * @param csvString
120+ * @param type
121+ * @return
122+ */
123+ public static <T > List <T > parseCsvToBeanList (List <String > headers , String csvString , Class <T > type ) {
124+ List <T > beanList = new ArrayList <>();
125+
126+ List <Map <String , String >> parsedList = parseCsvString (headers , csvString );
127+
128+ for (Map <String , String > row : parsedList ) {
129+
130+ Constructor <T > constructor ;
131+ Object instance ;
132+ Class <?> instanceClass ;
133+ try {
134+ constructor = type .getConstructor ();
135+ instance = constructor .newInstance ();
136+ instanceClass = instance .getClass ();
137+ } catch (Exception e ) {
138+ e .printStackTrace ();
139+ return null ;
140+ }
141+
142+ for (String header : row .keySet ()) {
143+ String setterName = "set" + header .substring (0 , 1 ).toUpperCase () + header .substring (1 );
144+
145+ try {
146+ List <Field > allFields = getAllFields (instance .getClass ());
147+ Class <?> fieldType = allFields .stream ().filter (f -> f .getName ().equals (header )).findFirst ().get ()
148+ .getType ();
149+
150+ Object fieldValue = null ;
151+
152+ if (fieldType == int .class ) {
153+ fieldValue = Integer .valueOf (row .get (header ));
154+ } else if (fieldType == double .class ) {
155+ try {
156+ fieldValue = Double .valueOf (row .get (header ));
157+ } catch (Exception e ) {
158+ fieldValue = -1 ;
159+ }
160+ } else {
161+ fieldValue = row .get (header );
162+ }
163+
164+ Method method = instanceClass .getMethod (setterName , fieldType );
165+ method .invoke (instance , fieldValue );
166+
167+ } catch (Exception e ) {
168+ e .printStackTrace ();
169+ }
170+ }
171+
172+ beanList .add ((type .cast (instance )));
173+ }
174+ return beanList ;
175+ }
176+
177+ /**
178+ *
179+ * @param headers
180+ * @param csvString
181+ * @return
182+ */
183+ public static List <Map <String , String >> parseCsvString (List <String > headers , String csvString ) {
184+ List <Map <String , String >> result = new ArrayList <>();
185+
186+ String [] csvLines = csvString .split ("\n " );
187+ for (String line : csvLines ) {
188+ Map <String , String > map = new LinkedHashMap <>();
189+
190+ Map <Integer , String > lineMap = parseCsvLine (line );
191+ for (int index : lineMap .keySet ()) {
192+ if (index >= headers .size ()) {
193+ continue ;
194+ } else {
195+ map .put (headers .get (index ), lineMap .get (index ));
196+ }
197+ }
198+
199+ result .add (map );
200+ }
201+
202+ return result ;
203+ }
204+
205+ /**
206+ * csv 형태의 문자열을 파싱하여 순서대로 Map객체에 담아 반환한다.
207+ *
208+ * @param csvLine
209+ * @return
210+ */
211+ public static Map <Integer , String > parseCsvLine (String csvLine ) {
212+ Map <Integer , String > map = new LinkedHashMap <>();
213+
214+ int index = 0 ;
215+ boolean isFirst = true ;
216+ boolean isOpen = false ;
217+ StringBuilder element = new StringBuilder ();
218+ for (char c : csvLine .toCharArray ()) {
219+ if (c == '"' ) {
220+ isOpen = !isOpen ;
221+ if (isFirst ) {
222+ isFirst = false ;
223+ continue ;
224+ }
225+
226+ if (!isOpen ) {
227+ map .put (index , StringEscapeUtils .unescapeHtml4 (element .toString ()));
228+ element = new StringBuilder ();
229+ }
230+ } else if (c == ',' ) {
231+ if (isOpen ) {
232+ element .append (c );
233+ } else {
234+ index ++;
235+ }
236+ } else {
237+ element .append (c );
238+ }
239+ }
240+ return map ;
241+ }
242+
243+ /**
244+ * 문자열을 쌍따옴표로 감싼 후 반환한다. 단, 문자열 내에 쌍따옴표가 있다면 html escape된 문자로 변환한다.
245+ *
246+ * @param string
247+ * @return
248+ */
249+ private static String wrapInDoubleQuotation (String string ) {
250+ return StringUtils .join ("\" " , StringEscapeUtils .escapeHtml4 (string ), "\" " );
251+ }
252+
253+ /**
254+ * 매개변수로 전달된 클래스의 Field와, 부모클래스의 Field까지 Reflection하여 반환한다.
255+ *
256+ * @param clazz
257+ * @return
258+ */
259+ private static List <Field > getAllFields (Class <?> clazz ) {
260+ List <Field > fields = new ArrayList <Field >();
261+
262+ fields .addAll (Arrays .asList (clazz .getDeclaredFields ()));
263+
264+ Class <?> superClazz = clazz .getSuperclass ();
265+ if (superClazz != null ) {
266+ fields .addAll (getAllFields (superClazz ));
267+ }
268+
269+ return fields ;
270+ }
271+ }
0 commit comments