src/core/net/sf/basedb/util/basefile/BaseFileParser.java

Code
Comments
Other
Rev Date Author Line
5093 10 Sep 09 nicklas 1 /**
5093 10 Sep 09 nicklas 2   $Id$
5093 10 Sep 09 nicklas 3
5093 10 Sep 09 nicklas 4   Copyright (C) 2009 Nicklas Nordborg
5093 10 Sep 09 nicklas 5
5093 10 Sep 09 nicklas 6   This file is part of BASE - BioArray Software Environment.
5093 10 Sep 09 nicklas 7   Available at http://base.thep.lu.se/
5093 10 Sep 09 nicklas 8
5093 10 Sep 09 nicklas 9   BASE is free software; you can redistribute it and/or
5093 10 Sep 09 nicklas 10   modify it under the terms of the GNU General Public License
5093 10 Sep 09 nicklas 11   as published by the Free Software Foundation; either version 3
5093 10 Sep 09 nicklas 12   of the License, or (at your option) any later version.
5093 10 Sep 09 nicklas 13
5093 10 Sep 09 nicklas 14   BASE is distributed in the hope that it will be useful,
5093 10 Sep 09 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
5093 10 Sep 09 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
5093 10 Sep 09 nicklas 17   GNU General Public License for more details.
5093 10 Sep 09 nicklas 18
5093 10 Sep 09 nicklas 19   You should have received a copy of the GNU General Public License
5093 10 Sep 09 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
5093 10 Sep 09 nicklas 21 */
5093 10 Sep 09 nicklas 22 package net.sf.basedb.util.basefile;
5093 10 Sep 09 nicklas 23
5093 10 Sep 09 nicklas 24 import java.io.IOException;
5093 10 Sep 09 nicklas 25 import java.io.InputStream;
5093 10 Sep 09 nicklas 26 import java.util.Arrays;
5093 10 Sep 09 nicklas 27 import java.util.HashMap;
5093 10 Sep 09 nicklas 28 import java.util.List;
5374 03 Aug 10 nicklas 29 import java.util.ListIterator;
5093 10 Sep 09 nicklas 30 import java.util.Map;
5093 10 Sep 09 nicklas 31 import java.util.regex.Pattern;
5093 10 Sep 09 nicklas 32
5093 10 Sep 09 nicklas 33 import net.sf.basedb.core.AbsoluteProgressReporter;
5093 10 Sep 09 nicklas 34 import net.sf.basedb.core.ItemNotFoundException;
5093 10 Sep 09 nicklas 35 import net.sf.basedb.core.ProgressReporter;
5405 10 Sep 10 nicklas 36 import net.sf.basedb.core.signal.ThreadSignalHandler;
5093 10 Sep 09 nicklas 37 import net.sf.basedb.util.Values;
5093 10 Sep 09 nicklas 38 import net.sf.basedb.util.parser.FlatFileParser;
5093 10 Sep 09 nicklas 39
5093 10 Sep 09 nicklas 40 /**
5093 10 Sep 09 nicklas 41   Parser for serial and matrix BASEfile:s. This class will setup and
5093 10 Sep 09 nicklas 42   perform the line-by-line parsing, but it doesn't handle the data.
5093 10 Sep 09 nicklas 43   Data is handled section-wise by registering {@link BaseFileSectionParser}:s
5093 10 Sep 09 nicklas 44   with {@link #setSectionParser(String, BaseFileSectionParser)}.
5093 10 Sep 09 nicklas 45   Sections that doens't have a registered parser are skipped.
5093 10 Sep 09 nicklas 46
5093 10 Sep 09 nicklas 47   @author Nicklas
5093 10 Sep 09 nicklas 48   @version 2.14
5093 10 Sep 09 nicklas 49   @base.modified $Date$
5093 10 Sep 09 nicklas 50 */
5093 10 Sep 09 nicklas 51 public class BaseFileParser
5093 10 Sep 09 nicklas 52 {
5093 10 Sep 09 nicklas 53
5093 10 Sep 09 nicklas 54   private final Map<String, BaseFileSectionParser> parsers;
5093 10 Sep 09 nicklas 55   private AbsoluteProgressReporter progress;
5093 10 Sep 09 nicklas 56   private Map<String, Integer> sectionCount;
5096 14 Sep 09 nicklas 57   private Map<String, String> redefinedColumnNames;
5096 14 Sep 09 nicklas 58
5093 10 Sep 09 nicklas 59   /**
5093 10 Sep 09 nicklas 60     Creates a new parser object.
5093 10 Sep 09 nicklas 61   */
5093 10 Sep 09 nicklas 62   public BaseFileParser()
5093 10 Sep 09 nicklas 63   {
5093 10 Sep 09 nicklas 64     this.parsers = new HashMap<String, BaseFileSectionParser>();
5093 10 Sep 09 nicklas 65     this.sectionCount = new HashMap<String, Integer>();
5096 14 Sep 09 nicklas 66     this.redefinedColumnNames = new HashMap<String, String>();
5093 10 Sep 09 nicklas 67   }
5093 10 Sep 09 nicklas 68   
5093 10 Sep 09 nicklas 69   /**
5093 10 Sep 09 nicklas 70     Adds a section parser. If a parser already exists for the
5093 10 Sep 09 nicklas 71     given section it is replaced.
5093 10 Sep 09 nicklas 72     
5093 10 Sep 09 nicklas 73     @param section The name of the section the parser handles
5093 10 Sep 09 nicklas 74     @param parser The parser, or null to remove a previously
5093 10 Sep 09 nicklas 75       registered parser
5093 10 Sep 09 nicklas 76   */
5093 10 Sep 09 nicklas 77   public void setSectionParser(String section, BaseFileSectionParser parser)
5093 10 Sep 09 nicklas 78   {
5093 10 Sep 09 nicklas 79     if (section == null) throw new NullPointerException("section");
5093 10 Sep 09 nicklas 80     if (parser == null)
5093 10 Sep 09 nicklas 81     {
5093 10 Sep 09 nicklas 82       parsers.remove(section);
5093 10 Sep 09 nicklas 83     }
5093 10 Sep 09 nicklas 84     else
5093 10 Sep 09 nicklas 85     {
5093 10 Sep 09 nicklas 86       parsers.put(section, parser);
5093 10 Sep 09 nicklas 87     }
5093 10 Sep 09 nicklas 88   }
5093 10 Sep 09 nicklas 89   
5093 10 Sep 09 nicklas 90   /**
5093 10 Sep 09 nicklas 91     Get the parser that is currently registered for a section.
5093 10 Sep 09 nicklas 92     @param section The name of the section
5093 10 Sep 09 nicklas 93     @return A section parser, or null if no parser is registered
5093 10 Sep 09 nicklas 94   */
5093 10 Sep 09 nicklas 95   public BaseFileSectionParser getSectionParser(String section)
5093 10 Sep 09 nicklas 96   {
5093 10 Sep 09 nicklas 97     return parsers.get(section);
5093 10 Sep 09 nicklas 98   }
5093 10 Sep 09 nicklas 99   
5093 10 Sep 09 nicklas 100   /**
5096 14 Sep 09 nicklas 101     Redefined a column name for a specified section. This function is useful
5096 14 Sep 09 nicklas 102     to configure parsers that normally look for, for example, the column
5096 14 Sep 09 nicklas 103     'reporter' to instead look for 'Reporter ID'.
5096 14 Sep 09 nicklas 104     @param section The section the redefined name is valid for
5096 14 Sep 09 nicklas 105     @param defaultName The default name (eg. 'reporter')
5096 14 Sep 09 nicklas 106     @param redefinedName The redefined name (ef. 'Reporter ID')
5096 14 Sep 09 nicklas 107   */
5096 14 Sep 09 nicklas 108   public void setRedefinedColumnName(String section, String defaultName, String redefinedName)
5096 14 Sep 09 nicklas 109   {
5096 14 Sep 09 nicklas 110     redefinedColumnNames.put(section + ":" + defaultName, redefinedName);
5096 14 Sep 09 nicklas 111   }
5096 14 Sep 09 nicklas 112   
5096 14 Sep 09 nicklas 113   /**
5096 14 Sep 09 nicklas 114     Get the redefined column name for a section. If the name has not
5096 14 Sep 09 nicklas 115     been redefined, the default name is returned.
5096 14 Sep 09 nicklas 116     @param section The section the redefined name is valid for
5096 14 Sep 09 nicklas 117     @param defaultName The default name
5096 14 Sep 09 nicklas 118     @return The redefined name, or the default name if it has not
5096 14 Sep 09 nicklas 119       been redefined
5096 14 Sep 09 nicklas 120   */
5096 14 Sep 09 nicklas 121   public String getRedefinedColumnName(String section, String defaultName)
5096 14 Sep 09 nicklas 122   {
5096 14 Sep 09 nicklas 123     String redefinedName = redefinedColumnNames.get(section + ":" + defaultName);
5096 14 Sep 09 nicklas 124     return redefinedName == null ? defaultName : redefinedName;
5096 14 Sep 09 nicklas 125   }
5096 14 Sep 09 nicklas 126   
5096 14 Sep 09 nicklas 127   /**
5096 14 Sep 09 nicklas 128     Copy redefined column names from the given parser into this
5096 14 Sep 09 nicklas 129     parser. Column names that have already been redefined in this
5096 14 Sep 09 nicklas 130     parser will be overwritten only if they have also been redefined in
5096 14 Sep 09 nicklas 131     the other parser.
5096 14 Sep 09 nicklas 132     @param parser The parser to copy redefined column names from
5096 14 Sep 09 nicklas 133   */
5096 14 Sep 09 nicklas 134   public void copyRedefinedColumnNames(BaseFileParser parser)
5096 14 Sep 09 nicklas 135   {
5096 14 Sep 09 nicklas 136     redefinedColumnNames.putAll(parser.redefinedColumnNames);
5096 14 Sep 09 nicklas 137   }
5096 14 Sep 09 nicklas 138   
5096 14 Sep 09 nicklas 139   /**
5093 10 Sep 09 nicklas 140     Set a progress reporter that will be used to report 
5093 10 Sep 09 nicklas 141     how the parsing is progressing.
5093 10 Sep 09 nicklas 142     @param progress A progress reporter, or null to not report progress
5093 10 Sep 09 nicklas 143   */
5093 10 Sep 09 nicklas 144   public void setProgressReporter(AbsoluteProgressReporter progress)
5093 10 Sep 09 nicklas 145   {
5093 10 Sep 09 nicklas 146     this.progress = progress;
5093 10 Sep 09 nicklas 147   }
5093 10 Sep 09 nicklas 148   
5093 10 Sep 09 nicklas 149   /**
5093 10 Sep 09 nicklas 150     Parse the given input stream.
5093 10 Sep 09 nicklas 151     @param in The data stream to parse
5093 10 Sep 09 nicklas 152     @param charset The character set used by the data, or null
5093 10 Sep 09 nicklas 153       to use the configured default
5093 10 Sep 09 nicklas 154     @return The FlatFileParser the was used to parse the BASEfile
5093 10 Sep 09 nicklas 155     @throws IOException If there is any problem with the parsing
5093 10 Sep 09 nicklas 156   */
5093 10 Sep 09 nicklas 157   public FlatFileParser parse(InputStream in, String charset)
5093 10 Sep 09 nicklas 158     throws IOException
5093 10 Sep 09 nicklas 159   {
5093 10 Sep 09 nicklas 160     FlatFileParser ffp = getInitializedFlatFileParser(in, charset);
5093 10 Sep 09 nicklas 161     while (ffp.hasMoreSections())
5093 10 Sep 09 nicklas 162     {
5590 16 Mar 11 nicklas 163       ThreadSignalHandler.checkInterrupted();
5093 10 Sep 09 nicklas 164       FlatFileParser.Line section = ffp.nextSection();      
5093 10 Sep 09 nicklas 165       increaseSectionCount(section.name(), 1);
5093 10 Sep 09 nicklas 166       BaseFileSectionParser parser = getSectionParser(section.name());
5093 10 Sep 09 nicklas 167       if (parser != null)
5093 10 Sep 09 nicklas 168       {
5093 10 Sep 09 nicklas 169         parser.parseSection(this, ffp);
5093 10 Sep 09 nicklas 170       }
5093 10 Sep 09 nicklas 171     }
5093 10 Sep 09 nicklas 172     return ffp;
5093 10 Sep 09 nicklas 173   }
5093 10 Sep 09 nicklas 174   
5093 10 Sep 09 nicklas 175   /**
5093 10 Sep 09 nicklas 176     The number of times we have seen a certain section in the
5093 10 Sep 09 nicklas 177     file.
5093 10 Sep 09 nicklas 178     @param section The name of the section
5093 10 Sep 09 nicklas 179   */
5093 10 Sep 09 nicklas 180   public int getSectionCount(String section)
5093 10 Sep 09 nicklas 181   {
5093 10 Sep 09 nicklas 182     Integer cnt = sectionCount.get(section);
5093 10 Sep 09 nicklas 183     return cnt == null ? 0 : cnt;
5093 10 Sep 09 nicklas 184   }
5093 10 Sep 09 nicklas 185   
5093 10 Sep 09 nicklas 186   /**
5093 10 Sep 09 nicklas 187     Counts the number of times a section has been seen in the file.
5093 10 Sep 09 nicklas 188     @param section The name of the section
5093 10 Sep 09 nicklas 189     @param count The count to add to the current count
5093 10 Sep 09 nicklas 190   */
5093 10 Sep 09 nicklas 191   protected void increaseSectionCount(String section, int count)
5093 10 Sep 09 nicklas 192   {
5093 10 Sep 09 nicklas 193     Integer cnt = sectionCount.get(section);
5093 10 Sep 09 nicklas 194     if (cnt == null) cnt = 0;
5093 10 Sep 09 nicklas 195     sectionCount.put(section, cnt + count);
5093 10 Sep 09 nicklas 196   }
5093 10 Sep 09 nicklas 197   
5093 10 Sep 09 nicklas 198   /**
5093 10 Sep 09 nicklas 199     Update the progress of the parsing.
5093 10 Sep 09 nicklas 200     @see ProgressReporter
5093 10 Sep 09 nicklas 201   */
5093 10 Sep 09 nicklas 202   public void setProgress(long completed, String message)
5093 10 Sep 09 nicklas 203   {
5093 10 Sep 09 nicklas 204     if (progress != null)
5093 10 Sep 09 nicklas 205     {
5093 10 Sep 09 nicklas 206       progress.displayAbsolute(completed, message);
5093 10 Sep 09 nicklas 207     }
5093 10 Sep 09 nicklas 208   }
5093 10 Sep 09 nicklas 209   
5093 10 Sep 09 nicklas 210   /**
5093 10 Sep 09 nicklas 211     Creates a {@link FlatFileParser} for parsing a BASEfile.
5093 10 Sep 09 nicklas 212     
5093 10 Sep 09 nicklas 213     @param stream The stream that the parser should read from.
5093 10 Sep 09 nicklas 214     @return The initialised parser
5093 10 Sep 09 nicklas 215   */
5093 10 Sep 09 nicklas 216   protected FlatFileParser getInitializedFlatFileParser(InputStream stream, String charset)
5093 10 Sep 09 nicklas 217   {
5093 10 Sep 09 nicklas 218     FlatFileParser ffp = new FlatFileParser();
5093 10 Sep 09 nicklas 219     ffp.setSectionRegexp(Pattern.compile("section[\\t ](.*)"));
5093 10 Sep 09 nicklas 220     ffp.setHeaderRegexp(Pattern.compile("(.*?)\\t(.*)"));
5093 10 Sep 09 nicklas 221     ffp.setDataHeaderRegexp(Pattern.compile("%"));
5093 10 Sep 09 nicklas 222     ffp.setDataFooterRegexp(Pattern.compile("^$"));
5093 10 Sep 09 nicklas 223     ffp.setDataSplitterRegexp(Pattern.compile("\\t"));
5093 10 Sep 09 nicklas 224     ffp.setUseNullIfEmpty(false);
5093 10 Sep 09 nicklas 225     ffp.setInputStream(stream, charset);
5093 10 Sep 09 nicklas 226     return ffp;
5093 10 Sep 09 nicklas 227   }
5093 10 Sep 09 nicklas 228   
5093 10 Sep 09 nicklas 229   /**
5093 10 Sep 09 nicklas 230     Get the value of a required header. This method calls
5093 10 Sep 09 nicklas 231     {@link FlatFileParser#getHeader(String)} to get the value of
5093 10 Sep 09 nicklas 232     the header. If the header is missing or has an empty value
5093 10 Sep 09 nicklas 233     an exception is thrown.
5093 10 Sep 09 nicklas 234     
5093 10 Sep 09 nicklas 235     @param ffp The flat file parser
5093 10 Sep 09 nicklas 236     @param header The name of the header
5093 10 Sep 09 nicklas 237     @param section The name of the section that is being parsed
5093 10 Sep 09 nicklas 238     @param filename The name of the file that is being parsed (used to 
5093 10 Sep 09 nicklas 239       create the error message in case the header is missing)
5093 10 Sep 09 nicklas 240     @return The value of the header
5093 10 Sep 09 nicklas 241   */
5093 10 Sep 09 nicklas 242   public String getRequiredHeader(FlatFileParser ffp, String header, 
5093 10 Sep 09 nicklas 243     String section, String filename)
5093 10 Sep 09 nicklas 244   {
5093 10 Sep 09 nicklas 245     String value = Values.getStringOrNull(ffp.getHeader(header));
5093 10 Sep 09 nicklas 246     if (value == null)
5093 10 Sep 09 nicklas 247     {
5093 10 Sep 09 nicklas 248       throw new ItemNotFoundException("Missing header '" + header + 
5093 10 Sep 09 nicklas 249         "' in section '" + section + "' at line " + 
5093 10 Sep 09 nicklas 250         ffp.getParsedLines() + " in file '" + filename + "'");
5093 10 Sep 09 nicklas 251     }
5093 10 Sep 09 nicklas 252     return value;
5093 10 Sep 09 nicklas 253   }
5093 10 Sep 09 nicklas 254   
5093 10 Sep 09 nicklas 255   /**
5093 10 Sep 09 nicklas 256     Get the value of a header as a list of sub-values. If the header is
5093 10 Sep 09 nicklas 257     missing or empty an exception is thrown. Otherwise the 'split' 
5093 10 Sep 09 nicklas 258     regular expression is used to the split the value into a list of values.
5093 10 Sep 09 nicklas 259     
5093 10 Sep 09 nicklas 260     @param ffp The flat file parser
5093 10 Sep 09 nicklas 261     @param header The name of the header
5093 10 Sep 09 nicklas 262     @param split A regular expression used to split the value
5093 10 Sep 09 nicklas 263     @param section The name of the section that is being parsed
5093 10 Sep 09 nicklas 264     @param filename The name of the file that is being parsed (used to 
5093 10 Sep 09 nicklas 265       create the error message in case the header is missing)
5093 10 Sep 09 nicklas 266     @return A list with the values of the header
5093 10 Sep 09 nicklas 267   */
5093 10 Sep 09 nicklas 268   public List<String> getRequiredHeader(FlatFileParser ffp, String header, String split, 
5093 10 Sep 09 nicklas 269     String section, String filename)
5093 10 Sep 09 nicklas 270   {
5093 10 Sep 09 nicklas 271     return Arrays.asList(getRequiredHeader(ffp, header, section, filename).split(split));
5093 10 Sep 09 nicklas 272   }
5093 10 Sep 09 nicklas 273   
5093 10 Sep 09 nicklas 274   /**
5093 10 Sep 09 nicklas 275     Get the index of value in a list of values or throw an exception
5374 03 Aug 10 nicklas 276     if the value is not found. The lookup is case-insensitive.
5093 10 Sep 09 nicklas 277     
5093 10 Sep 09 nicklas 278     @param values The list of values to look in
5093 10 Sep 09 nicklas 279     @param value The value to look for
5093 10 Sep 09 nicklas 280     @param header The name of the header the values are from
5093 10 Sep 09 nicklas 281     @param section The name of the section that is being parsed
5093 10 Sep 09 nicklas 282     @param line The current line number in the parsed file
5093 10 Sep 09 nicklas 283     @param filename The The name of the file that is being parsed (used to 
5093 10 Sep 09 nicklas 284       create the error message in case the header is missing)
5093 10 Sep 09 nicklas 285     @return The index of the value
5093 10 Sep 09 nicklas 286   */
5093 10 Sep 09 nicklas 287   public int getRequiredIndex(List<String> values, String value, 
5093 10 Sep 09 nicklas 288       String header, String section, int line, String filename)
5093 10 Sep 09 nicklas 289   {
5093 10 Sep 09 nicklas 290     int index = values.indexOf(value);
5374 03 Aug 10 nicklas 291     if (index == -1 && value != null)
5374 03 Aug 10 nicklas 292     {
5374 03 Aug 10 nicklas 293       // Try case-insensitive lookup
5374 03 Aug 10 nicklas 294       ListIterator<String> it = values.listIterator();
5374 03 Aug 10 nicklas 295       while (it.hasNext())
5374 03 Aug 10 nicklas 296       {
5374 03 Aug 10 nicklas 297         if (value.equalsIgnoreCase(it.next()))
5374 03 Aug 10 nicklas 298         {
5374 03 Aug 10 nicklas 299           index = it.previousIndex();
5374 03 Aug 10 nicklas 300           break;
5374 03 Aug 10 nicklas 301         }
5374 03 Aug 10 nicklas 302       }
5374 03 Aug 10 nicklas 303     }
5093 10 Sep 09 nicklas 304     if (index == -1) 
5093 10 Sep 09 nicklas 305     {
5093 10 Sep 09 nicklas 306       throw new ItemNotFoundException("Missing value '" + value + 
5093 10 Sep 09 nicklas 307           "' for header '" + header + "' in section '" + section + 
5093 10 Sep 09 nicklas 308           "' at line " + line + " in file '" + filename + "'");
5093 10 Sep 09 nicklas 309     }
5093 10 Sep 09 nicklas 310     return index;
5093 10 Sep 09 nicklas 311   }
5093 10 Sep 09 nicklas 312
5093 10 Sep 09 nicklas 313
5093 10 Sep 09 nicklas 314 }