/* An instance of this class represents a CSV-string, i.e., a string ** of characters intended to be interpreted as a sequence of fields, ** where a separator character separates each field from the next one. ** ** Authors: R. McCloskey and P.M.J. ** Date: November 2021 */ public class CSV_String { // class constant // -------------- private static final char DEFAULT_SEPARATOR = ','; // instance variable // ----------------- private final char SEPARATOR; private String str; // constructors // ------------ /* Establishes that the given character will play the role of the ** separator in this CSV-String, which begins with no fields. */ public CSV_String(char sep) { SEPARATOR = sep; str = null; } /* Establishes that the default separator character will play the role ** of the separator in this CSV-String, which begins with no fields. */ public CSV_String() { this(DEFAULT_SEPARATOR); } /* Establishes this CSV-String as having the fields in the given string (s), ** under the interpretation that the specified character (sep) plays the ** role of the separator. (If s is null, it means that (initially) this ** CSV-string has no fields.) */ public CSV_String(char sep, String s) { SEPARATOR = sep; str = s; } /* Establishes this CSV-String as having the fields in the given string (s), ** under the interpretation that the default separator character plays the ** role of the separator. (If s is null, it means that (initially) this ** CSV-string has no fields.) */ public CSV_String(String s) { this(DEFAULT_SEPARATOR, s); } // observers // --------- /* Returns the character that plays the role of the separator in ** this CSV-String. */ public char getSeparator() { return SEPARATOR; } /* Reports how many fields are in this CSV-String. */ public int numberOfFieldsIn() { int result; if (str == null) // null indicates that there are no fields. { result = 0; } else { result = numOccurrencesOf(SEPARATOR) + 1; } return result; } /* Reports whether or not this CSV-String has an i-th field ** (which is true iff 0 <= i < numberOfFieldsIn()). */ public boolean hasIthField(int i) { return 0 <= i && i < numberOfFieldsIn(); } /* If hasIthField(i), the value of the i-th field in this CSV-String ** is returned. Otherwise, an IllegalArgumentException is thrown. */ public String getIthField(int i) { String result = null; if (hasIthField(i)) { int start = indexOfIthField(i); int stop = indexOfNextSeparator(start); result = str.substring(start, stop); } else { throw new IllegalArgumentException("There is no " + i + "-th field!"); } return result; } /* Returns this CSV-String in its raw form as a String. */ public String toString() { return str; } // mutators // -------- /* If hasIthField(i), and if the given string (s) has no occurrences of ** the separator character, the i-th field of this CSV-String is replaced ** by the given string. Otherwise, an IllegalArgumentException is thrown. ** precondition: s != null */ public void setIthField(int i, String s) { if (s.indexOf(SEPARATOR) != -1) { String errorMessage = "A field cannot include the separator"; throw new IllegalArgumentException(errorMessage); } else if(!hasIthField(i)) { throw new IllegalArgumentException("There is no " + i + "-th field!"); } else { int start = indexOfIthField(i); int stop = indexOfNextSeparator(start); str = str.substring(0,start) + s + str.substring(stop); } } /* If hasIthField(i), the i-th field of this CSV-String is removed. ** Otherwise, an IllegalArgumentException is thrown. */ public void removeIthField(int i) { int numFields = numberOfFieldsIn(); if (0 <= i && i < numFields) { if (numFields == 1) { str = null; // lone field is being removed } else { // multiple fields exist int start = indexOfIthField(i); int stop = indexOfNextSeparator(start); if (start == 0) { // first field being removed str = str.substring(stop+1); } else if (stop == str.length()) { // last field being removed, so str = str.substring(0,start-1); // omit last comma and beyond } else { str = str.substring(0,start) + str.substring(stop+1); } /* if (stop == str.length()) { // last field being removed, so str = str.substring(0,start-1); // omit last comma and beyond } else { // non-last field being removed str = str.substring(0,start) + str.substring(stop+1); } */ } } else { String errorMessage = "There is no " + i + "-th field to remove!"; throw new IllegalArgumentException(errorMessage); } } /* If 0 <= i <= numberOfFieldsIn(), and if the specified string (s) includes ** no occurrences of the separator character, the specified string is ** inserted as the i-th field of this CSV-String, causing already-existing ** fields i, i+1, etc., to become fields i+1, i+2, etc.) ** Otherwise, an IllegalArgumentException is thrown. ** precondition: s != null */ public void insertIthField(int i, String s) { if (s.indexOf(SEPARATOR) != -1) { String errorMessage = "A field cannot include the separator"; throw new IllegalArgumentException(errorMessage); } else { int numFields = numberOfFieldsIn(); if (0 <= i && i <= numFields) { if (i < numFields) { // new field precedes an already-existing one int start = indexOfIthField(i); String prefix = str.substring(0,start); String suffix = str.substring(start); str = prefix + s + SEPARATOR + suffix; } else if (i == 0) { // # of existing fields is zero str = s; } else { // 1 <= i == numFields; thus new field goes at the end str = str + SEPARATOR + s; } } else { String errorMssg = "There's no position " + i + " at which to insert"; throw new IllegalArgumentException(errorMssg); } } } // clone // ----- /* Returns a clone of this CSV_String */ public CSV_String clone() { return new CSV_String(this.getSeparator(), str); } // private utility methods // ----------------------- /* Returns the # of occurrences of the given character within * (the String bound to) instance variable 'str'. */ private int numOccurrencesOf(char ch) { int count = 0; final int N = str.length(); for (int i=0; i != N; i = i+1) { if (str.charAt(i) == ch) { count = count+1; } } return count; } /* Returns the position within (the String bound to) instance variable 'str' ** at which the i-th field of this CSV-String begins. For the 0-th field, ** that is position zero. For the i-th field, where i satisfies ** 0 < i < numberOfFieldsIn(), that is the position immediately following ** the i-th occurrence of the SEPARATOR character. For i satisfying ** i >= numberOfFieldsIn(), it is str.length (i.e., the position immediately ** following the last field). ** precondition: i >= 0 */ private int indexOfIthField(int i) { final int N = str.length(); int pos = 0; int cntr = 0; while (pos != N && cntr != i) { if (str.charAt(pos) == SEPARATOR) { cntr = cntr + 1; } pos = pos + 1; } return pos; } /* Returns the lowest numbered position within (the String bound to) ** instance variable 'str', but not less than 'startIndex', at which the ** SEPARATOR character occurs. If no such position exists, the length ** of (the String bound to) 'str' is returned. ** precondition: 0 <= startIndex <= str.length() */ private int indexOfNextSeparator(int startIndex) { final int N = str.length(); int index = startIndex; while(index != N && str.charAt(index) != SEPARATOR) { index = index + 1; } return index; } }