Merge from ECOMP's repository
[vid.git] / vid-app-common / src / main / java / org / onap / vid / services / CsvServiceImpl.java
1 package org.onap.vid.services;
2
3 import com.opencsv.CSVReader;
4 import org.json.JSONArray;
5 import org.json.JSONObject;
6 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
7 import org.springframework.stereotype.Service;
8 import org.springframework.web.multipart.MultipartFile;
9
10 import javax.ws.rs.BadRequestException;
11 import java.io.FileReader;
12 import java.io.IOException;
13 import java.io.InputStreamReader;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.List;
17
18 import static org.onap.vid.utils.Logging.getMethodName;
19
20 @Service
21 public class CsvServiceImpl implements CsvService{
22
23
24     /** The logger. */
25     static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(CsvServiceImpl.class);
26
27     private static final String ARRAY_REGEX = "\\[(.*?)\\]";
28
29
30     /**
31      * In UTF-8 the first line starts with "\uFEFF" so need to remove it
32      * @param line is first line contains BOM
33      * @return line after change
34      */
35     private String [] removeBOMFromCsv(String [] line){
36         if (line.length>0)
37             line[0] = line[0].replaceFirst("\uFEFF","").replaceFirst("","");
38         return line;
39     }
40
41     /**
42      * read a csv file and puts its content in list of string arrays (without the empty lines)
43      * @param filePath - the path of file to read
44      * @return the content of file
45      * @throws IOException
46      */
47     @Override
48     public List<String[]> readCsv(String filePath) throws IOException {
49         CSVReader reader = new CSVReader(new FileReader(filePath));
50         return readCsv(reader);
51     }
52
53     @Override
54     public List<String[]> readCsv(MultipartFile file) throws IOException {
55         CSVReader reader = new CSVReader(new InputStreamReader(file.getInputStream()));
56         return readCsv(reader);
57     }
58
59     private List<String[]> addLineWithoutSpaces(List<String[]> myEntries, String [] line){
60         line = Arrays.stream(line).filter(x -> !"".equals(x)).toArray(String[]::new);
61         if(line.length > 0)
62             myEntries.add(line);
63         return myEntries;
64     }
65
66
67     private  List<String[]> readCsv(CSVReader reader) throws IOException {
68         try {
69             List<String[]> myEntries = new ArrayList<>() ;
70             String [] line;
71             Boolean firstLine = true;
72             while ((line = reader.readNext())!= null) {
73                 if (firstLine) {
74                     line = removeBOMFromCsv(line);
75                     firstLine = false;
76                 }
77                 myEntries = addLineWithoutSpaces(myEntries, line);
78             }
79             return myEntries;
80         }
81         catch (Exception e){
82             logger.error("error during reading CSV file. exception:" + e.getMessage());
83             throw e;
84         }
85
86     }
87
88     /**
89      * main function that call to the recursive function with initial parameters
90      * @param myEntries - the matrix with file content
91      * @return the json
92      * @throws IOException
93      * @throws InstantiationException
94      * @throws IllegalAccessException
95      */
96     @Override
97     public JSONObject convertCsvToJson (List<String[]> myEntries) throws InstantiationException, IllegalAccessException {
98         try {
99             return buildJSON(myEntries, 0, 0, myEntries.size(), JSONObject.class);
100         }
101         catch (Exception e){
102             logger.error("error during parsing CSV file. exception:" + e.getMessage());
103             throw e;
104         }
105
106     }
107
108     /**
109      * it goes over the matrix column while the values are the same and returns the index of changed value
110      * @param myEntries the matrix
111      * @param i row index refer to the whole matrix
112      * @param j column index
113      * @param numLines the length of the current inner matrix
114      * @param startLine row index of inner matrix
115      * @return the index of changed line
116      */
117     private int findIndexOfChangedLine(List<String[]> myEntries, final int i, final int j, final int numLines, final int startLine) {
118         int k;
119         for(k = 0; k + i - startLine < numLines && myEntries.get(i)[j].equals(myEntries.get(k + i)[j]) ; k++);
120         return k;
121     }
122
123     /**
124      *  check in array if its first element or if the key already exist in the previous item
125      * @param jsonArray - the array to search in
126      * @param key - the key to check
127      * @return if exists or first element return true, otherwise- false
128      */
129     private Boolean keyExistsOrFirstElement( JSONArray jsonArray,String key){
130         Boolean exists = false;
131         Boolean first = false;
132         JSONObject lastItem = lastItemInArray(jsonArray);
133         if (lastItem == null) {
134             first = true;
135         }
136         else {
137             if (lastItem.has(key)) {
138                 exists = true;
139             }
140         }
141         return exists||first;
142     }
143
144     /**
145      * return last json in json array
146      * @param jsonArray
147      * @return last item or null if the array is empty
148      */
149     private JSONObject lastItemInArray(JSONArray jsonArray){
150         JSONObject lastItem = null;
151         if(jsonArray.length()>0) {
152             lastItem = (JSONObject) jsonArray.get(jsonArray.length() - 1);
153         }
154         return lastItem;
155     }
156
157     /**
158      * append current json to the main json
159      * @param json - the main json to append to it
160      * @param key - key to append
161      * @param values - value(s) to append
162      * @param <T> can be JSONObject or JSONArray
163      * @param <E> string or jsonObject or jsonArray
164      * @return json after put
165      * @throws IllegalAccessException
166      * @throws InstantiationException
167      */
168     private <T, E> T putJson(T json, String key, E values) {
169         if (json instanceof JSONArray){
170             JSONArray currentJson= ((JSONArray)json);
171             if (values == null) //array of strings (for last item)
172             {
173                 currentJson.put(key);
174             }
175             else {
176                 if (keyExistsOrFirstElement(currentJson, key)) {
177                     currentJson.put(new JSONObject().put(key, values));
178                 } else {
179                 JSONObject lastItem = lastItemInArray(currentJson);
180                     lastItem.put(key, values);
181                 }
182             }
183         }
184         if (json instanceof JSONObject){
185             if (values == null)
186                 throw new BadRequestException("Invalid csv file");
187             ((JSONObject)json).put(key,values);
188         }
189         return json;
190     }
191
192
193     /**
194      *  recursive function to build JSON. Each time it counts the same values in left and send the smaller matrix
195      *  (until the changed value) to the next time.
196      *
197      * @param myEntries - the whole matrix
198      * @param i- row index of the whole matrix
199      * @param j - column index
200      * @param numLines - number of lines of inner matrix (num of same values in the left column)
201      * @param clazz JSONArray or JSONObject
202      * @param <T> JSONArray or JSONObject
203      * @return the json object
204      * @throws IllegalAccessException
205      * @throws InstantiationException
206      */
207     private <T> T buildJSON(List<String[]> myEntries, int i, final int j, final int numLines, Class<T> clazz) throws IllegalAccessException, InstantiationException {
208         logger.debug(EELFLoggerDelegate.debugLogger, "start {}({}, {}, {})", getMethodName(), i, j, numLines);
209         T json = clazz.newInstance();
210         int startLine = i;
211         while(i < numLines + startLine){
212             String[] currentRow = myEntries.get(i);
213             int length = currentRow.length;
214             int currentDuplicateRows = findIndexOfChangedLine(myEntries,i,j,numLines, startLine);
215             String key = currentRow[j];
216             if (j == length-1) {
217                 json = putJson(json,currentRow[j],null);
218             }
219             else
220             {
221                 json = buildJsonRow(myEntries, i, j, json, currentRow, length, currentDuplicateRows, key);
222             }
223             i += currentDuplicateRows;
224         }
225         logger.debug(EELFLoggerDelegate.debugLogger, "end {} json = {}", getMethodName(), json);
226         return json;
227     }
228
229     private <T> T buildJsonRow(List<String[]> myEntries, int i, int j, T json, String[] currentRow, int length, int currentDuplicateRows, String key) throws IllegalAccessException, InstantiationException {
230         if (key.matches(ARRAY_REGEX)){
231             JSONArray arrayObjects = buildJSON(myEntries, i, j + 1, currentDuplicateRows, JSONArray.class);
232             json = putJson(json,key.replaceAll("\\[","").replaceAll("]",""),arrayObjects);
233         }
234         else {
235             if (j < length - 2) {
236                 json = putJson(json, currentRow[j], buildJSON(myEntries, i, j + 1, currentDuplicateRows, JSONObject.class));
237             }
238             else
239             {
240                 if (j == length - 2)//last object
241                 {
242                     if(currentDuplicateRows > 1) {
243                         throw new BadRequestException("Invalid csv file");
244                     }
245                     json = putJson(json, currentRow[j], currentRow[j + 1]);
246                 }
247             }
248         }
249         return json;
250     }
251
252 }
253