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