Coverage Report - net.sf.jmatchparser.util.csv.FixedWidthColumn
 
Classes in this File Line Coverage Branch Coverage Complexity
FixedWidthColumn
94%
74/78
88%
55/62
3,846
FixedWidthColumn$Alignment
100%
11/11
N/A
3,846
 
 1  
 /*
 2  
  * Copyright (c) 2010 - 2011 Michael Schierl
 3  
  * 
 4  
  * All rights reserved.
 5  
  * 
 6  
  * Redistribution and use in source and binary forms, with or without
 7  
  * modification, are permitted provided that the following conditions
 8  
  * are met:
 9  
  * 
 10  
  * - Redistributions of source code must retain the above copyright notice,
 11  
  *   this list of conditions and the following disclaimer.
 12  
  *   
 13  
  * - Redistributions in binary form must reproduce the above copyright
 14  
  *   notice, this list of conditions and the following disclaimer in the
 15  
  *   documentation and/or other materials provided with the distribution.
 16  
  *   
 17  
  * - Neither name of the copyright holders nor the names of its
 18  
  *   contributors may be used to endorse or promote products derived from
 19  
  *   this software without specific prior written permission.
 20  
  *   
 21  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS
 22  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 23  
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 24  
  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 25  
  * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 26  
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 27  
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 28  
  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 29  
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 30  
  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 31  
  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32  
  */
 33  
 package net.sf.jmatchparser.util.csv;
 34  
 
 35  
 import java.io.IOException;
 36  
 import java.util.Arrays;
 37  
 import java.util.regex.Matcher;
 38  
 import java.util.regex.Pattern;
 39  
 
 40  
 /**
 41  
  * A column of a fixed-width CSV file.
 42  
  */
 43  
 public class FixedWidthColumn {
 44  
 
 45  
         private final int width;
 46  
         private final Alignment alignment;
 47  
         private final char padLeft;
 48  
         private final char padRight;
 49  
         private final String[] exceptionStrings;
 50  
 
 51  
         /**
 52  
          * Create a left-aligned fixed-width column of the given width.
 53  
          * 
 54  
          * Spaces are used to pad the value on both sides. Values that contain tabs
 55  
          * or two consecutive spaces are considered invalid.
 56  
          * 
 57  
          * @param width
 58  
          *            The width
 59  
          */
 60  
         public FixedWidthColumn(int width) {
 61  4
                 this(width, Alignment.LEFT);
 62  4
         }
 63  
 
 64  
         /**
 65  
          * Create a fixed-width column of the given width and alignment.
 66  
          * 
 67  
          * Spaces are used to pad the value on both sides. Values that contain tabs
 68  
          * or two consecutive spaces are considered invalid.
 69  
          * 
 70  
          * @param width
 71  
          *            The width
 72  
          * @param alignment
 73  
          *            The alignment
 74  
          */
 75  
         public FixedWidthColumn(int width, Alignment alignment) {
 76  19
                 this(width, alignment, ' ');
 77  19
         }
 78  
 
 79  
         /**
 80  
          * Create a fixed-width column of the given width and alignment, which uses
 81  
          * the given character to pad the value on both sides if needed.
 82  
          * 
 83  
          * Values that contain tabs or two consecutive spaces are considered
 84  
          * invalid.
 85  
          * 
 86  
          * @param width
 87  
          *            The width
 88  
          * @param alignment
 89  
          *            The alignment
 90  
          * @param pad
 91  
          *            The padding character
 92  
          */
 93  
         public FixedWidthColumn(int width, Alignment alignment, char pad) {
 94  22
                 this(width, alignment, pad, pad);
 95  22
         }
 96  
 
 97  
         /**
 98  
          * Create a fixed-width column of the given width and alignment, which uses
 99  
          * two different characters to pad the value on left or right side if
 100  
          * needed.
 101  
          * 
 102  
          * Values that contain tabs or two consecutive spaces are considered
 103  
          * invalid.
 104  
          * 
 105  
          * @param width
 106  
          *            The width
 107  
          * @param alignment
 108  
          *            The alignment
 109  
          * @param padLeft
 110  
          *            The left padding character
 111  
          * @param padRight
 112  
          *            The right padding character
 113  
          */
 114  
         public FixedWidthColumn(int width, Alignment alignment, char padLeft, char padRight) {
 115  27
                 this(width, alignment, padLeft, padRight, "  ", "\t");
 116  27
         }
 117  
 
 118  
         /**
 119  
          * Create a fixed-width column of the given width and alignment, which uses
 120  
          * two different characters to pad the value on left or right side if needed
 121  
          * and the given exception strings.
 122  
          * 
 123  
          * To use an empty exception string array, the empty array has to be given
 124  
          * explicitly to not clash with the
 125  
          * {@link #FixedWidthColumn(int, Alignment, char, char)} constructor.
 126  
          * 
 127  
          * Values that contain one of the given exception strings are considered
 128  
          * invalid.
 129  
          * 
 130  
          * @param width
 131  
          *            The width
 132  
          * @param alignment
 133  
          *            The alignment
 134  
          * @param padLeft
 135  
          *            The left padding character
 136  
          * @param padRight
 137  
          *            The right padding character
 138  
          * @param exceptionStrings
 139  
          *            The exception strings (may not contain null or empty strings!)
 140  
          */
 141  48
         public FixedWidthColumn(int width, Alignment alignment, char padLeft, char padRight, String... exceptionStrings) {
 142  48
                 this.width = width;
 143  48
                 this.alignment = alignment;
 144  48
                 this.padLeft = padLeft;
 145  48
                 this.padRight = padRight;
 146  48
                 this.exceptionStrings = exceptionStrings;
 147  142
                 for (int i = 0; i < exceptionStrings.length; i++) {
 148  94
                         if (exceptionStrings[i] == null || exceptionStrings[i].length() == 0)
 149  0
                                 throw new IllegalArgumentException("Illegal exception string");
 150  
                 }
 151  48
         }
 152  
 
 153  
         /**
 154  
          * Parse a column from a column specification string.
 155  
          * 
 156  
          * <p>
 157  
          * The specification starts with the column width as a decimal number,
 158  
          * followed by an optional LRTB for the alignment, followed by an optional
 159  
          * slash followed by one or two padding characters. If two padding
 160  
          * characters are given, another slash delimits exception strings (each one
 161  
          * delimited by yet another slash). An empty exception list (but with the
 162  
          * leading slash) is treated as no exceptions.
 163  
          * 
 164  
          * <p>
 165  
          * A missing parameter is treated as the default as used by the
 166  
          * {@link #FixedWidthColumn(int)} constructor.
 167  
          * 
 168  
          * @param colSpec
 169  
          *            column specification
 170  
          * @return the column
 171  
          */
 172  
         public static FixedWidthColumn parse(String colSpec) {
 173  17
                 Pattern colRegex = Pattern.compile("([0-9]+)([LRCF]?)(?:(/..?)(/.*)?)?");
 174  17
                 Matcher m = colRegex.matcher(colSpec);
 175  17
                 if (!m.matches())
 176  0
                         throw new IllegalArgumentException("Invalid column spec: " + colSpec);
 177  17
                 int width = Integer.parseInt(m.group(1));
 178  17
                 int alignmentIndex = "LRCF".indexOf(m.group(2));
 179  17
                 if (alignmentIndex == -1)
 180  0
                         alignmentIndex = 0;
 181  17
                 Alignment alignment = new Alignment[] { Alignment.LEFT, Alignment.RIGHT, Alignment.CENTER, Alignment.FILL }[alignmentIndex];
 182  17
                 char padLeft = ' ', padRight = ' ';
 183  17
                 String padGroup = m.group(3);
 184  17
                 if (padGroup != null) {
 185  10
                         padLeft = padGroup.charAt(1);
 186  10
                         padRight = padGroup.charAt(padGroup.length() - 1);
 187  
                 }
 188  17
                 String[] exceptionStrings = new String[] { "  ", "\t" };
 189  17
                 String exceptionGroup = m.group(4);
 190  17
                 if (exceptionGroup != null) {
 191  4
                         exceptionStrings = exceptionGroup.substring(1).split("/");
 192  4
                         if (exceptionGroup.length() == 1)
 193  1
                                 exceptionStrings = new String[0];
 194  
                 }
 195  17
                 return new FixedWidthColumn(width, alignment, padLeft, padRight, exceptionStrings);
 196  
         }
 197  
 
 198  
         /**
 199  
          * Parse a value from a fixed-width CSV file, i. e. remove padding and check
 200  
          * for exception strings.
 201  
          * 
 202  
          * @param value
 203  
          *            Value to parse
 204  
          * @return Parsed value
 205  
          */
 206  
         public String parseValue(String value) {
 207  17
                 if (alignment.isPadLeft()) {
 208  10
                         int first = 0;
 209  25
                         for (int i = 0; i < value.length(); i++) {
 210  25
                                 if (value.charAt(i) != padLeft)
 211  10
                                         break;
 212  15
                                 first = i + 1;
 213  
                         }
 214  10
                         value = value.substring(first);
 215  
                 }
 216  17
                 if (alignment.isPadRight()) {
 217  12
                         int last = value.length();
 218  37
                         for (int i = value.length() - 1; i >= 0; i--) {
 219  37
                                 if (value.charAt(i) != padRight)
 220  12
                                         break;
 221  25
                                 last = i;
 222  
                         }
 223  12
                         value = value.substring(0, last);
 224  
                 }
 225  51
                 for (String exception : exceptionStrings) {
 226  34
                         if (value.contains(exception))
 227  0
                                 throw new IllegalArgumentException("Invalid column value: " + value);
 228  
                 }
 229  17
                 return value;
 230  
         }
 231  
 
 232  
         /**
 233  
          * Format a value to store it into a fixed-width CSV file. The value is
 234  
          * padded as needed.
 235  
          * 
 236  
          * Overlong values can be truncated (on the right unless the column is
 237  
          * right-aligned), left as is or cause an exception, based on your needs.
 238  
          * 
 239  
          * @param value
 240  
          *            Value to format
 241  
          * @param truncate
 242  
          *            Whether to silently truncate overlong values
 243  
          * @param allowOverflow
 244  
          *            Whether to allow overflow for long values
 245  
          * @return Formatted value
 246  
          */
 247  
         public String formatValue(String value, boolean truncate, boolean allowOverflow) throws IOException {
 248  76
                 if (value.length() > width && truncate && alignment == Alignment.RIGHT)
 249  2
                         value = value.substring(value.length() - width);
 250  76
                 if (value.length() > width && truncate)
 251  6
                         value = value.substring(0, width);
 252  76
                 if (value.length() == width)
 253  36
                         return value;
 254  40
                 if (value.length() > width && !allowOverflow)
 255  4
                         throw new IOException("Value too long: " + value);
 256  36
                 StringBuilder sb = new StringBuilder(value);
 257  120
                 while (sb.length() < width && alignment != Alignment.FILL) {
 258  106
                         if (alignment.isPadRight())
 259  82
                                 sb.append(padRight);
 260  106
                         if (sb.length() == width)
 261  22
                                 break;
 262  84
                         if (alignment.isPadLeft())
 263  48
                                 sb.insert(0, padLeft);
 264  
                 }
 265  36
                 if (sb.length() < width && !allowOverflow)
 266  2
                         throw new IOException("Value too short: " + value);
 267  34
                 return sb.toString();
 268  
         }
 269  
 
 270  
         /**
 271  
          * Get the width of this column.
 272  
          */
 273  
         public int getWidth() {
 274  10
                 return width;
 275  
         }
 276  
 
 277  
         @Override
 278  
         public String toString() {
 279  26
                 return "FixedWidthColumn [width=" + width + ", alignment=" + alignment + ", padLeft=" + padLeft + ", padRight=" + padRight + ", exceptionStrings=" + exceptionStrings.length + " " + Arrays.toString(exceptionStrings) + "]";
 280  
         }
 281  
 
 282  
         /**
 283  
          * Enumeration of possible column alignments.
 284  
          */
 285  1
         public static enum Alignment {
 286  
 
 287  
                 /**
 288  
                  * Left alignment, pad on the right.
 289  
                  */
 290  1
                 LEFT(false, true),
 291  
 
 292  
                 /**
 293  
                  * Right alignment, pad on the left.
 294  
                  */
 295  1
                 RIGHT(true, false),
 296  
 
 297  
                 /**
 298  
                  * Center alignment, pad on both sides. When an odd number of padding is
 299  
                  * needed, pad one more on the right side.
 300  
                  */
 301  1
                 CENTER(true, true),
 302  
 
 303  
                 /**
 304  
                  * Filled alignment, do not pad. Mostly used for reading files so that
 305  
                  * no padding is removed.
 306  
                  */
 307  1
                 FILL(false, false);
 308  
 
 309  
                 private final boolean padLeft;
 310  
                 private final boolean padRight;
 311  
 
 312  4
                 private Alignment(boolean padLeft, boolean padRight) {
 313  4
                         this.padLeft = padLeft;
 314  4
                         this.padRight = padRight;
 315  4
                 }
 316  
 
 317  
                 boolean isPadLeft() {
 318  101
                         return padLeft;
 319  
                 }
 320  
 
 321  
                 boolean isPadRight() {
 322  123
                         return padRight;
 323  
                 }
 324  
         }
 325  
 }