Changeset 3023


Ignore:
Timestamp:
Dec 12, 2006, 1:32:16 PM (16 years ago)
Author:
Nicklas Nordborg
Message:

Fixes #311: Allow parsing of numbers with different locale settings

Location:
trunk/src
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/core/ExtendedProperty.java

    r2535 r3023  
    2424package net.sf.basedb.core;
    2525
     26import java.text.NumberFormat;
    2627import java.util.List;
    2728
     
    179180  }
    180181
     182  /**
     183    Parse a string and return an object of the correct type for this property.
     184    Numeric properties are parsed with the specified number format.
     185    @param value The value to parse
     186    @param numberFormat The number format, or null to use Float.valueOf or Double.valueOf
     187    @return An object
     188    @throws InvalidDataException If the string cannot be converted to the correct type
     189    @see #getType()
     190    @since 2.2
     191  */
     192  public Object parseString(String value, NumberFormat numberFormat)
     193  {
     194    return getType().parseString(value, numberFormat);
     195  }
     196 
    181197  /**
    182198    Check if an object is valid according to the specifications of this property.
  • trunk/src/core/net/sf/basedb/core/Type.java

    r2751 r3023  
    3737import org.hibernate.type.NullableType;
    3838
     39import java.text.NumberFormat;
     40import java.text.ParsePosition;
    3941import java.util.Date;
    4042import java.util.Map;
     
    7072      return new Integer(Float.valueOf(value).intValue());
    7173    }
     74    public Number convertNumber(Number num)
     75    {
     76      return Integer.valueOf(num.intValue());
     77    }
    7278  },
    7379
     
    9096      return new Long(Float.valueOf(value).intValue());
    9197    }
     98    public Number convertNumber(Number num)
     99    {
     100      return Long.valueOf(num.longValue());
     101    }
    92102  },
    93103
     
    105115      return value == null ? 0 : 4;
    106116    }
     117    public Number convertNumber(Number num)
     118    {
     119      return Float.valueOf(num.floatValue());
     120    }
    107121  },
    108122
     
    119133    {
    120134      return value == null ? 0 : 8;
     135    }
     136    public Number convertNumber(Number num)
     137    {
     138      return Double.valueOf(num.doubleValue());
    121139    }
    122140  },
     
    351369 
    352370  /**
     371    Convert a number to the appropriate class. The default implementation
     372    returns null. Each numeric type overrides this method to create a number of
     373    the correct type.
     374    @since 2.2
     375  */
     376  public Number convertNumber(Number num)
     377  {
     378    return null;
     379  }
     380 
     381  /**
    353382    Parse a string and return a value of the correct type.
    354383  */
     
    363392    {
    364393      return getHibernateType().fromStringValue(value);
     394    }
     395  }
     396 
     397  /**
     398    Parse a string and return a value of the correct type using a specific
     399    number format for numerical values.
     400    @param value The string value to pars
     401    @param numberFormat The number format, or null to use {@link #parseString(String)}
     402    @since 2.2
     403  */
     404  public Object parseString(String value, NumberFormat numberFormat)
     405  {
     406    if (value == null)
     407    {
     408      return null;
     409    }
     410    else if (!isNumerical || numberFormat == null)
     411    {
     412      return parseString(value);
     413    }
     414    else
     415    {
     416      ParsePosition pos = new ParsePosition(0);
     417      Number num = numberFormat.parse(value, pos);
     418      if (pos.getIndex() < value.length())
     419      {
     420        throw new NumberFormatException("For input string: \"" + value + "\"");
     421      }
     422      return convertNumber(num);
    365423    }
    366424  }
  • trunk/src/core/net/sf/basedb/util/parser/ColFunction.java

    r2655 r3023  
    2424package net.sf.basedb.util.parser;
    2525
     26import java.text.NumberFormat;
     27import java.text.ParsePosition;
    2628import java.util.HashMap;
    2729import java.util.List;
     
    5961 
    6062  private final Map<String, Integer> columnHeaders;
     63  private final NumberFormat numberFormat;
     64  private final ParsePosition pos;
    6165  private FlatFileParser.Data data;
    6266 
    63   public ColFunction(Map<String, Integer> columnHeaders)
     67  public ColFunction(Map<String, Integer> columnHeaders, NumberFormat numberFormat)
    6468  {
    6569    this.columnHeaders = columnHeaders;
     70    this.numberFormat = numberFormat;
     71    this.pos = new ParsePosition(0);
    6672  }
    6773 
    68   public ColFunction(List<String> columnHeaders)
     74  public ColFunction(List<String> columnHeaders, NumberFormat numberFormat)
    6975  {
     76    this.numberFormat = numberFormat;
     77    this.pos = new ParsePosition(0);
    7078    this.columnHeaders = new HashMap<String, Integer>(columnHeaders.size());
    7179    int index = 0;
     
    138146      throw new ParseException("Invalid parameter type: " + argument + "; expected number");
    139147    }
    140     try
     148    Double d = null;
     149    if (value != null)
    141150    {
    142       stack.push(value == null ? null : Double.valueOf(value));
     151      if (numberFormat == null)
     152      {
     153        try
     154        {
     155          d = Double.valueOf(value);
     156        }
     157        catch (NumberFormatException ex)
     158        {
     159          stack.push(value);
     160          return;
     161        }
     162      }
     163      else
     164      {
     165        pos.setIndex(0);
     166        d = numberFormat.parse(value, pos).doubleValue();
     167        if (pos.getIndex() < value.length())
     168        {
     169          stack.push(value);
     170          return;
     171        }
     172      }
    143173    }
    144     catch (NumberFormatException ex)
    145     {
    146       stack.push(value);
    147     }
     174    stack.push(d);
    148175  }
    149176  // -------------------------------------------
  • trunk/src/core/net/sf/basedb/util/parser/ColumnMapper.java

    r2655 r3023  
    2424package net.sf.basedb.util.parser;
    2525
     26import java.text.NumberFormat;
     27import java.text.ParsePosition;
     28
    2629import net.sf.basedb.util.parser.FlatFileParser.Data;
    2730
     
    4043  private final int index;
    4144  private final String name;
     45  private final NumberFormat parser;
     46  private final ParsePosition pos;
    4247
    4348  /**
     
    4954  public ColumnMapper(int index, String name)
    5055  {
     56    this(index, name, null);
     57  }
     58 
     59  /**
     60    Create a new column mapper using a specific number format.
     61    @param index The index of the data column to use, starting at 0
     62    @param name An optional name of the column, use in the <code>toString</code>
     63      method
     64    @param parser The parser to use or null to use Float.valueOf()
     65   */
     66  public ColumnMapper(int index, String name, NumberFormat parser)
     67  {
    5168    this.index = index;
    5269    this.name = name;
     70    this.parser = parser;
     71    this.pos = parser == null ? null : new ParsePosition(0);
    5372  }
    5473 
     
    6887  public Integer getInt(Data data)
    6988  {
    70     String stringValue = getValue(data);
    71     return stringValue == null ? null : Float.valueOf(stringValue).intValue();
     89    return getFloat(getValue(data)).intValue();
    7290  }
    7391  public Float getFloat(Data data)
    7492  {
    75     String stringValue = getValue(data);
    76     return stringValue == null ? null : Float.valueOf(stringValue);
     93    return getFloat(getValue(data));
    7794  }
    7895  // -------------------------------------------
     
    87104  }
    88105  // -------------------------------------------
     106 
     107  private Float getFloat(String value)
     108  {
     109    if (value == null) return null;
     110    Float f = null;
     111    if (parser == null)
     112    {
     113      f = Float.valueOf(value);
     114    }
     115    else
     116    {
     117      pos.setIndex(0);
     118      f = parser.parse(value, pos).floatValue();
     119      if (pos.getIndex() < value.length())
     120      {
     121        throw new NumberFormatException("For input string: \"" + value + "\"");
     122      }
     123    }
     124    return f;
     125  }
     126 
    89127}
    90128
  • trunk/src/core/net/sf/basedb/util/parser/ConstantMapper.java

    r2792 r3023  
    2424package net.sf.basedb.util.parser;
    2525
     26import java.text.NumberFormat;
     27import java.text.ParsePosition;
     28
    2629import net.sf.basedb.util.parser.FlatFileParser.Data;
    2730
     
    4346  private NumberFormatException floatException;
    4447 
     48  /**
     49    Create a constant mapper.
     50  */
    4551  public ConstantMapper(String constant)
     52  {
     53    this(constant, null);
     54  }
     55 
     56  /**
     57    Create a constant mapper using a specific number formatter as it's parser.
     58    @param constant The constant expression
     59    @param parser The number format to use or null to use Float.valueOf()
     60    @since 2.2
     61  */
     62  public ConstantMapper(String constant, NumberFormat parser)
    4663  {
    4764    this.constant = constant;
    4865    if (constant != null)
    4966    {
    50       try
     67      if (parser != null)
    5168      {
    52         asFloat = Float.valueOf(constant);
    53       }
    54       catch (NumberFormatException ex)
    55       {
    56         floatException = ex;
    57       }
    58       if (asFloat != null)
    59       {
    60         asInteger = asFloat.intValue();
     69        ParsePosition pos = new ParsePosition(0);
     70        asFloat = parser.parse(constant, pos).floatValue();
     71        if (pos.getIndex() < constant.length())
     72        {
     73          asFloat = null;
     74          floatException = new NumberFormatException("For input string: \"" + constant + "\"");
     75          integerException = floatException;
     76        }
     77        else
     78        {
     79          asInteger = asFloat.intValue();
     80        }
    6181      }
    6282      else
    6383      {
    64         integerException = floatException;
     84        try
     85        {
     86          asFloat = Float.valueOf(constant);
     87        }
     88        catch (NumberFormatException ex)
     89        {
     90          floatException = ex;
     91        }
     92        if (asFloat != null)
     93        {
     94          asInteger = asFloat.intValue();
     95        }
     96        else
     97        {
     98          integerException = floatException;
     99        }
    65100      }
    66101    }
  • trunk/src/core/net/sf/basedb/util/parser/FlatFileParser.java

    r2992 r3023  
    3030import java.nio.charset.Charset;
    3131
     32import java.text.NumberFormat;
    3233import java.util.regex.Pattern;
    3334import java.util.regex.Matcher;
     
    238239 
    239240  /**
     241    The default number formatter to use for creating mappers.
     242  */
     243  private NumberFormat numberFormat = null;
     244 
     245  /**
    240246    List of lines parsed by the {@link #parseHeaders()} method.
    241247  */
     
    753759 
    754760  /**
     761    Set the default number format to use when creating mappers.
     762    @param numberFormat The number format to use, or null to parse
     763      numbers with Float.valueOf or Double.valueOf
     764    @since 2.2
     765    @see #getMapper(String)
     766    @see #getMapper(String, NumberFormat)
     767   */
     768  public void setDefaultNumberFormat(NumberFormat numberFormat)
     769  {
     770    this.numberFormat = numberFormat;
     771  }
     772 
     773  /**
     774    Get the default number format.
     775    @return The number format, or null if none has been specified
     776    @since 2.2
     777  */
     778  public NumberFormat getDefaultNumberFormat()
     779  {
     780    return numberFormat;
     781  }
     782 
     783  /**
     784    Get a mapper using the default number format.
     785    @see #getMapper(String, NumberFormat)
     786  */
     787  public Mapper getMapper(String expression)
     788  {
     789    return getMapper(expression, numberFormat);
     790  }
     791 
     792  /**
    755793    Create a mapper object that maps an expression string to a value.
    756794    An expression string is a regular string which contains placeholders
     
    772810
    773811    @param expression The string containing the mapping expression
     812    @param numberFormat The number format the mapper should use for
     813      parsing numbers, or null to use Float.valueOf or Double.valueOf
    774814    @return A mapper object
    775   */
    776   public Mapper getMapper(String expression)
     815    @since 2.2
     816  */
     817  public Mapper getMapper(String expression, NumberFormat numberFormat)
    777818  {
    778819    Mapper mapper = null;
    779820    if (expression == null || expression.length() == 0)
    780821    {
    781       mapper = new ConstantMapper(emptyIsNull ? null : "");
     822      mapper = new ConstantMapper(emptyIsNull ? null : "", numberFormat);
    782823    }
    783824    else if (expression.startsWith("="))
    784825    {
    785       mapper = new JepMapper(expression.substring(1), columnHeaders);
     826      mapper = new JepMapper(expression.substring(1), columnHeaders, numberFormat);
    786827    }
    787828    else
     
    795836        if (matchStart > nextStart)
    796837        {
    797           mappers.add(new ConstantMapper(expression.substring(nextStart, matchStart)));
     838          mappers.add(new ConstantMapper(expression.substring(nextStart, matchStart), numberFormat));
    798839        }
    799840        try
    800841        {
    801842          int column = Integer.parseInt(m.group(1));
    802           mappers.add(new ColumnMapper(column, null));
     843          mappers.add(new ColumnMapper(column, null, numberFormat));
    803844        }
    804845        catch (NumberFormatException ex)
     
    810851            if (column >= 0)
    811852            {
    812               mappers.add(new ColumnMapper(column, name));
     853              mappers.add(new ColumnMapper(column, name, numberFormat));
    813854            }
    814855            else
     
    822863      if (nextStart < expression.length())
    823864      {
    824         mappers.add(new ConstantMapper(expression.substring(nextStart)));
     865        mappers.add(new ConstantMapper(expression.substring(nextStart), numberFormat));
    825866      }
    826867      if (mappers.size() == 1)
  • trunk/src/core/net/sf/basedb/util/parser/JepMapper.java

    r2655 r3023  
    2424package net.sf.basedb.util.parser;
    2525
     26import java.text.NumberFormat;
    2627import java.util.List;
    2728import org.nfunk.jep.JEP;
     
    5657  private final JEP parser;
    5758  private final ColFunction colFunction;
     59  private final NumberFormat numberFormat;
    5860 
    5961  /**
     
    6466  public JepMapper(String expression, List<String> columnHeaders)
    6567  {
     68    this(expression, columnHeaders, null);
     69  }
     70 
     71  public JepMapper(String expression, List<String> columnHeaders, NumberFormat numberFormat)
     72  {
    6673    this.expression = expression;
    67     this.colFunction = new ColFunction(columnHeaders);
     74    this.numberFormat = numberFormat;
     75    this.colFunction = new ColFunction(columnHeaders, numberFormat);
    6876    // Replace: \ColumnName\ with: col('ColumnName')
    6977    expression = expression.replaceAll("\\\\([^\\\\]*)\\\\", "col('$1')");
     
    8896    colFunction.setData(data);
    8997    if (parser.hasError()) Jep.clearErrorList(parser);
    90     return new Integer((int)parser.getValue());
     98    return Integer.valueOf((int)parser.getValue());
    9199  }
    92100  public Float getFloat(Data data)
     
    94102    colFunction.setData(data);
    95103    if (parser.hasError()) Jep.clearErrorList(parser);
    96     return new Float(parser.getValue());
     104    return Float.valueOf((float)parser.getValue());
    97105  }
    98106  // -------------------------------------------
  • trunk/src/plugins/core/net/sf/basedb/plugins/AbstractFlatFileImporter.java

    r2992 r3023  
    5151import net.sf.basedb.core.plugin.Plugin;
    5252
     53import net.sf.basedb.util.NumberFormatUtil;
    5354import net.sf.basedb.util.error.ClassMapErrorHandler;
    5455import net.sf.basedb.util.error.ErrorHandler;
     
    5859import net.sf.basedb.util.parser.Mapper;
    5960
     61import java.text.NumberFormat;
    6062import java.util.ArrayList;
    6163import java.util.Arrays;
     
    272274    new StringParameterType(255, Config.getCharset(), false, 1, 0, 0,
    273275    new ArrayList<String>(Charset.availableCharsets().keySet()));
     276 
     277  /**
     278    Type for selecting decimal separator.
     279  */
     280  protected static final StringParameterType decimalSeparatorType =
     281    new StringParameterType(255, "dot", false, 1, 0, 0,
     282    Arrays.asList(new String[] { "dot", "comma" })
     283  );
    274284 
    275285  /**
     
    482492    FlatFileParser.Data dataline = null;
    483493    ffp.setInputStream(in, getCharset());
     494    ffp.setDefaultNumberFormat(getNumberFormat());
    484495    boolean isSuccess = false; // false until we have finished parsing
    485496    try
     
    714725 
    715726  /**
     727    Get the decimal separator used by numbers in the file. This method first
     728    checks the job parameters for a value, then the configuration parameters.
     729    If not found null is returned.
     730    @return The decimal separator or null to use Float.valueOf() or Double.valueOf()
     731    @since 2.2
     732  */
     733  protected String getDecimalSeparator()
     734  {
     735    String ds = null;
     736    if (job != null) ds = (String)job.getValue(DECIMAL_SEPARATOR);
     737    if (ds == null && configuration != null)
     738    {
     739      ds = (String)configuration.getValue(DECIMAL_SEPARATOR);
     740    }
     741    return ds;
     742  }
     743 
     744  /**
     745    Get a number formatter that is able to parse numbers with the
     746    specified decimal separator. Returns null if no decimal separator
     747    has been specified which causes numbers to be parsed with Float.valueOf()
     748    or Double.valueOf().
     749    @return The number format or null to use Float.valueOf() or Double.valueOf()
     750    @since 2.2
     751  */
     752  protected NumberFormat getNumberFormat()
     753  {
     754    String decimalSeparator = getDecimalSeparator();
     755    char ds = 0;
     756    if ("dot".equals(decimalSeparator))
     757    {
     758      ds = '.';
     759    }
     760    else if ("comma".equals(decimalSeparator))
     761    {
     762      ds = ',';
     763    }
     764    return ds == 0 ? null : NumberFormatUtil.getNumberFormat(ds, ';');
     765  }
     766 
     767  /**
    716768    The name of the parameter that asks for the character set.
    717769    @see #getCharsetParameter(String, String, String)
     
    740792    return new PluginParameter<String>(
    741793      CHARSET, label, description, defaultValue, charsetType
     794    );
     795  }
     796
     797  /**
     798    The name of the parameter that asks for the decimal separator.
     799    @see #getCharsetParameter(String, String, String)
     800  */
     801  protected static final String DECIMAL_SEPARATOR = "decimalSeparator";
     802 
     803  /**
     804    Parameter definition that asks for the decimal separator used by numeric values
     805    in the file that is imported. This can be both a job parameter and a configuration
     806    parameter. The implementation first checks in the job parameter and
     807    if not found in the configuration parameter. If still not found the
     808    no decimal separator is used and number parsing is done by Float.valueOf
     809    or Double.valueOf.
     810    @param label The label to use for the parameter or null to use the default label
     811      (Decimal separator)
     812    @param description The description to use for the parameter or null to use the
     813      default description
     814    @param defaultValue The default value for the decimal separator
     815    @since 2.2
     816  */
     817  protected PluginParameter<String> getDecimalSeparatorParameter(String label, String description, String defaultValue)
     818  {
     819    if (label == null) label = "Decimal separator";
     820    if (description == null)
     821    {
     822      description = "The decimal separator used in numeric values, " +
     823        "if not specified dot is assumed.";
     824    }
     825    return new PluginParameter<String>(
     826      DECIMAL_SEPARATOR, label, description, defaultValue, decimalSeparatorType
    742827    );
    743828  }
  • trunk/src/plugins/core/net/sf/basedb/plugins/RawDataFlatFileImporter.java

    r2992 r3023  
    5959import net.sf.basedb.core.Job;
    6060
     61import java.text.NumberFormat;
    6162import java.util.ArrayList;
    6263import java.util.Arrays;
     
    208209  private int numInserted;
    209210  private FlatFileParser ffp;
     211  private NumberFormat numberFormat;
    210212  private RawDataBatcher batcher;
    211213  private RawBioAssay rawBioAssay;
     
    374376        storeValue(configuration, request, maxDataColumnsParameter);
    375377        storeValue(configuration, request, ri.getParameter(CHARSET));
     378        storeValue(configuration, request, ri.getParameter(DECIMAL_SEPARATOR));
    376379       
    377380        // Column mappings
     
    396399        storeValue(job, request, fileParameter);
    397400        storeValue(job, request, ri.getParameter(CHARSET));
     401        storeValue(job, request, ri.getParameter(DECIMAL_SEPARATOR));
    398402       
    399403        // Error handling parameters
     
    437441    numInserted = 0;
    438442    this.ffp = ffp;
     443    this.numberFormat = ffp.getDefaultNumberFormat();
    439444   
    440445    // Set up error handling
     
    534539      RawDataProperty ep = entry.getKey();
    535540      Mapper m = entry.getValue();
    536       raw.setExtended(ep.getName(), ep.parseString(m.getValue(data)));
     541      raw.setExtended(ep.getName(), ep.parseString(m.getValue(data), numberFormat));
    537542    }
    538543
     
    613618      parameters.add(fileParameter);
    614619      parameters.add(getCharsetParameter(null, null, (String)configuration.getValue(CHARSET)));
     620      parameters.add(getDecimalSeparatorParameter(null, null, (String)configuration.getValue(DECIMAL_SEPARATOR)));
    615621     
    616622      // Error handling parameters
     
    712718      parameters.add(maxDataColumnsParameter);
    713719      parameters.add(getCharsetParameter(null, null, null));
     720      parameters.add(getDecimalSeparatorParameter(null, null, null));
    714721
    715722      // Column mappings
  • trunk/src/plugins/core/net/sf/basedb/plugins/ReporterFlatFileImporter.java

    r2992 r3023  
    6666import net.sf.basedb.util.parser.Mapper;
    6767
     68import java.text.NumberFormat;
    6869import java.util.Collection;
    6970import java.util.EnumSet;
     
    305306        storeValue(configuration, request, maxDataColumnsParameter);
    306307        storeValue(configuration, request, ri.getParameter(CHARSET));
     308        storeValue(configuration, request, ri.getParameter(DECIMAL_SEPARATOR));
    307309       
    308310        // Column mappings
     
    332334        storeValue(job, request, fileParameter);
    333335        storeValue(job, request, ri.getParameter(CHARSET));
     336        storeValue(job, request, ri.getParameter(DECIMAL_SEPARATOR));
    334337        storeValue(job, request, updateExistingParameter);
    335338       
     
    372375  private Map<String, Float> deferred;
    373376  private FlatFileParser ffp;
     377  private NumberFormat numberFormat;
    374378 
    375379  /**
     
    395399    updateExisting = (Boolean)job.getValue("updateExisting");
    396400    this.ffp = ffp;
     401    this.numberFormat = ffp.getDefaultNumberFormat();
    397402    numInserted = 0;
    398403    numUpdated = 0;
     
    508513        Float score = scoreMapper == null ?
    509514            reporterList.getScore(reporter) :
    510             (Float)Type.FLOAT.parseString(scoreMapper.getValue(data));
     515            (Float)Type.FLOAT.parseString(scoreMapper.getValue(data), numberFormat);
    511516        if (currentId == 0)
    512517        {
     
    542547          ExtendedProperty ep = entry.getKey();
    543548          Mapper m = entry.getValue();
    544           reporter.setExtended(ep.getName(), ep.parseString(m.getValue(data)));
     549          reporter.setExtended(ep.getName(), ep.parseString(m.getValue(data), numberFormat));
    545550        }
    546551        if (currentId != 0)
     
    620625      }
    621626      parameters.add(fileParameter);
     627      parameters.add(updateExistingParameter);
    622628      parameters.add(getCharsetParameter(null, null, (String)configuration.getValue(CHARSET)));
    623       parameters.add(updateExistingParameter);
     629      parameters.add(getDecimalSeparatorParameter(null, null, (String)configuration.getValue(DECIMAL_SEPARATOR)));
    624630     
    625631      parameters.add(errorSection);
     
    677683      parameters.add(maxDataColumnsParameter);
    678684      parameters.add(getCharsetParameter(null, null, null));
     685      parameters.add(getDecimalSeparatorParameter(null, null, null));
    679686
    680687      // Column mappings
Note: See TracChangeset for help on using the changeset viewer.