package hudson.plugins.tfs.util; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parser for the text table output from the TF tool. * The table will always begin with a number of columns that specifies how wide each * column is. The text table parser will use the number of columns and their width to * be able to parse the lines that comes after the column definition. Some columns * are optional, ie they does not contain any data and the line ends before the * optional column begins. * <p> * An example of how a separator definition may look. The table consists of 3 columns; * column 1 contains a string with at most 5 chars, column 2 contains a string with at most * 2 chars and column 3 may contain a string with at most 4 chars. The third column is optional. * <pre> * ----- -- ---- * Data1 A Data * Data2 B * Data3 C Data * </pre> * <code> * TextTableParser t = new TextTableParser(reader, 1); * t.nextRow() // first row * assertEquals("Data1", t.getColumn(0)); * assertEquals("Data", t.getColumn(2)); * t.nextRow(); // Second row * assertEquals("Data2", t.getColumn(0)); * assertNull(t.getColumn(2)); * t.nextRow(); // Third row * assertEquals("Data3", t.getColumn(0)); * assertEquals("Data", t.getColumn(2)); * </code> * @author Erik Ramfelt */ public class TextTableParser { private static final Pattern SEPARATOR_PATTERN = Pattern.compile("(-+)"); private final BufferedReader reader; private List<ColumnRange> columns; private String currentLine; private final int optionalColumnCount; private int lastMandatoryColumnStart; public TextTableParser(Reader reader) throws IOException { this(reader, 0); } public TextTableParser(Reader reader, int optionalColumnCount) throws IOException { this.reader = new BufferedReader( reader ); this.optionalColumnCount = optionalColumnCount; init(); } private void init() throws IOException { String line = reader.readLine(); columns = new ArrayList<ColumnRange>(); while (line != null) { if (line.startsWith("-")) { Matcher matcher = SEPARATOR_PATTERN.matcher(line); if (matcher.find()) { do { columns.add(new ColumnRange(matcher.start(), matcher.end())); } while (matcher.find()); break; } } line = reader.readLine(); } if (columns.size() > 0){ lastMandatoryColumnStart = columns.get(columns.size() - 1 - optionalColumnCount).start; } } /** * Returns the number of columns * @return the number of columns */ public int getColumnCount() throws IOException { return columns.size(); } /** * Return the value in the specified column * @param index the column index * @return the value in the specified column; null if there is no value (the column is optional) */ public String getColumn(int index) throws IOException { if (currentLine == null) { throw new IllegalStateException("There is no active row."); } ColumnRange columnRange = columns.get(index); if (currentLine.length() < columnRange.start) { return null; } if (currentLine.length() < columnRange.end) { return currentLine.substring(columnRange.start).trim(); } else { return currentLine.substring(columnRange.start, columnRange.end).trim(); } } /** * Move to the next row * @return true, if there was a next row; false, if there is no next row. * @throws IOException */ public boolean nextRow() throws IOException { do { currentLine = reader.readLine(); } while ((currentLine != null) && (currentLine.length() < lastMandatoryColumnStart)); return (currentLine != null); } private static class ColumnRange { private final int start; private final int end; public ColumnRange(int start, int end) { this.start = start; this.end = end; } } }