FormatReader.java

  1. /*
  2.  * GovWay - A customizable API Gateway
  3.  * https://govway.org
  4.  *
  5.  * Copyright (c) 2005-2025 Link.it srl (https://link.it).
  6.  *
  7.  * This program is free software: you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License version 3, as published by
  9.  * the Free Software Foundation.
  10.  *
  11.  * This program is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  *
  19.  */

  20. package org.openspcoop2.utils.csv;
  21. import java.io.InputStream;
  22. import java.util.Properties;

  23. import org.apache.commons.csv.CSVFormat;
  24. import org.apache.commons.csv.QuoteMode;
  25. import org.apache.commons.lang.ArrayUtils;
  26. import org.openspcoop2.utils.BooleanNullable;
  27. import org.openspcoop2.utils.UtilsException;

  28. /**
  29.  * FormatReader
  30.  *
  31.  * @author Andrea Poli (apoli@link.it)
  32.  * @author $Author$
  33.  * @version $Rev$, $Date$
  34.  */
  35. public class FormatReader {

  36.     /*
  37.      * Default: Standard comma separated format, as for RFC4180 but allowing empty lines.
  38.      * Excel: Excel file format (using a comma as the value delimiter).
  39.      * MySQL: Default MySQL format used by the SELECT INTO OUTFILE and LOAD DATA INFILE operations.
  40.      * RFC4180: Comma separated format as defined by RFC 4180.
  41.      * TDF: Tab-delimited format.
  42.      **/
  43.     public static final String CSV_FORMAT = "format";
  44.    
  45.     /*
  46.      * Sets the missing column names behavior of the format
  47.      **/
  48.     public static final String CSV_ALLOW_MISSING_COLUMN_NAMES = "allowMissingColumnNames";
  49.    
  50.     /*
  51.      * Sets the comment start marker of the format to the specified character.
  52.      **/
  53.     public static final String CSV_COMMENT_MARKER = "commentMarker";
  54.    
  55.     /*
  56.      * Sets the delimiter of the format to the specified character.
  57.      **/
  58.     public static final String CSV_DELIMITER = "delimiter";
  59.    
  60.     /*
  61.      * Sets the escape character of the format to the specified character.
  62.      **/
  63.     public static final String CSV_ESCAPE = "escape";
  64.    
  65.     /*
  66.      * Sets the header of the format.
  67.      **/
  68.     public static final String CSV_WITH_HEADER = "withHeader";
  69.     public static final String CSV_HEADER = "header";
  70.    
  71.     /*
  72.      * Sets the empty line skipping behavior of the format to true.
  73.      **/
  74.     public static final String CSV_WITH_IGNORE_EMPTY_LINES = "ignoreEmptyLines";
  75.    
  76.     /*
  77.      * Sets the trimming behavior of the format to true.
  78.      **/
  79.     public static final String CSV_WITH_IGNORE_SURROUNDING_SPACES = "ignoreSurroundingSpaces";
  80.    
  81.     /*
  82.      * Performs conversions to and from null for strings on input and output.
  83.      **/
  84.     public static final String CSV_WITH_NULL_STRING = "nullString";
  85.    
  86.     /*
  87.      * Sets the quoteChar of the format to the specified character.
  88.      **/
  89.     public static final String CSV_WITH_QUOTE = "quote";
  90.    
  91.     /*
  92.      * Sets the output quote policy of the format to the specified value.
  93.      * ALL: Quotes all fields.
  94.      * MINIMAL: Quotes fields which contain special characters such as a delimiter, quotes character or any of the characters in line separator.
  95.      * NON_NUMERIC: Quotes all non-numeric fields.
  96.      * NONE: Never quotes fields.
  97.      **/
  98.     public static final String CSV_WITH_QUOTE_MODE = "quoteMode";
  99.    
  100.     /*
  101.      * Sets the record separator of the format to the specified character.
  102.      **/
  103.     public static final String CSV_WITH_RECORD_SEPARATOR = "recordSeparator";
  104.    
  105.     /*
  106.      * Sets skipping the header record to true.
  107.      **/
  108.     public static final String CSV_WITH_SKIP_HEADER_RECORD = "skipHeaderRecord";
  109.        
  110.     /*
  111.      * Sets skipping the record empty (es. ,,,,,, )
  112.      **/
  113.     public static final String CSV_SKIP_EMPTY_RECORD = "skipEmptyRecord";
  114.    
  115.    
  116.    
  117.    
  118.     private CSVFormat format;
  119.     private boolean skipEmptyRecord = true;
  120.    
  121.     public FormatReader(InputStream is) throws UtilsException {
  122.         Properties p = null;
  123.         try{
  124.             p = new Properties();
  125.             p.load(is);
  126.         }catch(Exception e){
  127.             throw new UtilsException(e.getMessage(),e);
  128.         }
  129.         this.init(p);
  130.     }
  131.     public FormatReader(Properties properties) throws UtilsException {
  132.         this.init(properties);
  133.     }
  134.     private void init(Properties properties) throws UtilsException{
  135.        
  136.         CSVFormat.Builder builder = null;
  137.        
  138.         // format
  139.         if(properties.containsKey(CSV_FORMAT)){
  140.             String input = properties.getProperty(CSV_FORMAT).trim();
  141.             CSVFormat.Predefined[]p = CSVFormat.Predefined.values();
  142.             boolean found = false;
  143.             for (int i = 0; i < p.length; i++) {
  144.                 if(p[i].toString().equalsIgnoreCase(input)){
  145.                     builder = CSVFormat.Builder.create( p[i].getFormat() );
  146.                     found=true;
  147.                     break;
  148.                 }
  149.             }
  150.            
  151.             if(!found)
  152.                 builder = CSVFormat.Builder.create( CSVFormat.DEFAULT );
  153.            
  154.         }
  155.         else{
  156.             builder = CSVFormat.Builder.create( CSVFormat.DEFAULT );
  157.         }
  158.        
  159.         // behaviour
  160.        
  161.         BooleanNullable allowMissingColumnsNames = getBooleanProperty(properties, CSV_ALLOW_MISSING_COLUMN_NAMES);
  162.         if(allowMissingColumnsNames!=null && allowMissingColumnsNames.getValue()!=null){
  163.             builder.setAllowMissingColumnNames(allowMissingColumnsNames.getValue());
  164.         }
  165.        
  166.         Character commentMarker = getCharProperty(properties, CSV_COMMENT_MARKER);
  167.         if(commentMarker!=null){
  168.             builder.setCommentMarker(commentMarker);
  169.         }
  170.        
  171.         Character delimiter = getCharProperty(properties, CSV_DELIMITER);
  172.         if(delimiter!=null){
  173.             builder.setDelimiter(delimiter);
  174.         }
  175.        
  176.         Character escape = getCharProperty(properties, CSV_ESCAPE);
  177.         if(escape!=null){
  178.             builder.setEscape(escape);
  179.         }
  180.        
  181.         BooleanNullable withHeader = getBooleanProperty(properties, CSV_WITH_HEADER);
  182.         if(withHeader!=null && withHeader.getValue()!=null){
  183.             if(withHeader.getValue()){
  184.                 String [] h = getArrayStringProperty(properties, CSV_HEADER);
  185.                 if(h!=null && h.length>0)
  186.                     builder.setHeader(h);
  187.                 else
  188.                     builder.setHeader();
  189.             }
  190.         }
  191.         else{
  192.             String [] h = getArrayStringProperty(properties, CSV_HEADER);
  193.             if(h!=null && h.length>0){
  194.                 builder.setHeader(h);
  195.             }
  196.         }
  197.        
  198.         BooleanNullable withIgnoreEmptyLines = getBooleanProperty(properties, CSV_WITH_IGNORE_EMPTY_LINES);
  199.         if(withIgnoreEmptyLines!=null && withIgnoreEmptyLines.getValue()!=null){
  200.             builder.setIgnoreEmptyLines(withIgnoreEmptyLines.getValue());
  201.         }
  202.        
  203.         BooleanNullable withIgnoreSurroundingSpaces = getBooleanProperty(properties, CSV_WITH_IGNORE_SURROUNDING_SPACES);
  204.         if(withIgnoreSurroundingSpaces!=null && withIgnoreSurroundingSpaces.getValue()!=null){
  205.             builder.setIgnoreSurroundingSpaces(withIgnoreSurroundingSpaces.getValue());
  206.         }
  207.        
  208.         String withNullString = getProperty(properties, CSV_WITH_NULL_STRING);
  209.         if(withNullString!=null){
  210.             builder.setNullString(withNullString);
  211.         }
  212.        
  213.         Character withQuote = getCharProperty(properties, CSV_WITH_QUOTE);
  214.         if(withQuote!=null){
  215.             builder.setQuote(withQuote);
  216.         }
  217.        
  218.         String withQuoteMode = getProperty(properties, CSV_WITH_QUOTE_MODE);
  219.         if(withQuoteMode!=null){
  220.             QuoteMode[]q = QuoteMode.values();
  221.             boolean found = false;
  222.             for (int i = 0; i < q.length; i++) {
  223.                 if(q[i].toString().equalsIgnoreCase(withQuoteMode)){
  224.                     builder.setQuoteMode(q[i]);
  225.                     found = true;
  226.                     break;
  227.                 }
  228.             }
  229.             if(!found){
  230.                 throw new UtilsException("Quote Mode property ["+CSV_WITH_QUOTE_MODE+"] with wrong value ["+withQuoteMode+"]. Excpected values: "+ArrayUtils.toString(QuoteMode.values()));
  231.             }
  232.         }
  233.        
  234.         Character withRecordSeparator = getCharProperty(properties, CSV_WITH_RECORD_SEPARATOR);
  235.         if(withRecordSeparator!=null){
  236.             builder.setRecordSeparator(withRecordSeparator);
  237.         }
  238.        
  239.         BooleanNullable withSkipHeaderRecord = getBooleanProperty(properties, CSV_WITH_SKIP_HEADER_RECORD);
  240.         if(withSkipHeaderRecord!=null && withSkipHeaderRecord.getValue()!=null){
  241.             builder.setSkipHeaderRecord(withSkipHeaderRecord.getValue());
  242.         }
  243.        
  244.         BooleanNullable skipEmptyRecord = getBooleanProperty(properties, CSV_SKIP_EMPTY_RECORD);
  245.         if(skipEmptyRecord!=null && skipEmptyRecord.getValue()!=null){
  246.             this.skipEmptyRecord = skipEmptyRecord.getValue();
  247.         }
  248.        
  249.         this.format = builder.build();
  250.     }
  251.    
  252.     public Format getFormat() {
  253.         Format format = new Format();
  254.         format.setCsvFormat(this.format);
  255.         format.setSkipEmptyRecord(this.skipEmptyRecord);
  256.         return format;
  257.     }
  258.    
  259.     private String[] getArrayStringProperty(Properties properties,String name) throws UtilsException{
  260.         if(properties.containsKey(name)){
  261.             String tmp = properties.getProperty(name);
  262.             if(tmp!=null){
  263.                 tmp = tmp.trim();
  264.                 String [] ret = tmp.split(",");
  265.                 if(ret!=null && ret.length>=1){
  266.                     return ret;
  267.                 }
  268.                 else{
  269.                     throw new UtilsException("Valore della proprietà ["+name+"] deve essere una lista di valori separati da ','; trovato invece ["+tmp+"]");
  270.                 }
  271.             }
  272.         }
  273.         return null;
  274.     }
  275.    
  276.     private BooleanNullable getBooleanProperty(Properties properties,String name) throws UtilsException{
  277.         if(properties.containsKey(name)){
  278.             String tmp = properties.getProperty(name);
  279.             if(tmp!=null){
  280.                 tmp = tmp.trim();
  281.                 if("true".equalsIgnoreCase(tmp)){
  282.                     return BooleanNullable.TRUE();
  283.                 }
  284.                 else if("false".equalsIgnoreCase(tmp)){
  285.                     return BooleanNullable.FALSE();
  286.                 }
  287.                 else{
  288.                     throw new UtilsException("Valore della proprietà ["+name+"] deve essere un valore booleano, trovato invece ["+tmp+"]");
  289.                 }
  290.             }
  291.         }
  292.         return BooleanNullable.NULL();
  293.     }
  294.    
  295.     private Character getCharProperty(Properties properties,String name) throws UtilsException{
  296.         if(properties.containsKey(name)){
  297.             String tmp = properties.getProperty(name);
  298.             if(tmp!=null){
  299.                 tmp = tmp.trim();
  300.                 if(tmp.length()==1){
  301.                     return tmp.charAt(0);
  302.                 }
  303.                 else{
  304.                     throw new UtilsException("Valore della proprietà ["+name+"] deve essere un singolo carattere, trovato invece ["+tmp+"]");
  305.                 }
  306.             }
  307.         }
  308.         return null;
  309.     }
  310.    
  311.     private String getProperty(Properties properties,String name) throws UtilsException{
  312.         if(properties.containsKey(name)){
  313.             String tmp = properties.getProperty(name);
  314.             if(tmp!=null){
  315.                 tmp = tmp.trim();
  316.                 return tmp;
  317.             }
  318.         }
  319.         return null;
  320.     }


  321.    
  322. }