/*
* Freeplane - mind map editor
* Copyright (C) 2011 Volker Boerchers
*
* This file author is Volker Boerchers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.features.format;
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.freeplane.features.format.DateFormatParser;
import org.freeplane.features.format.DecimalFormatParser;
import org.freeplane.features.format.FormatController;
import org.freeplane.features.format.FormattedDate;
import org.freeplane.features.format.FormattedNumber;
import org.freeplane.features.format.IFormattedObject;
import org.freeplane.features.format.IsoDateParser;
import org.freeplane.features.format.NumberLiteralParser;
import org.freeplane.main.application.FreeplaneStarter;
import org.freeplane.main.mindmapmode.MModeControllerFactory;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* @author Volker Boerchers
*/
public class ParserTest {
private static final String TYPE_DATE = IFormattedObject.TYPE_DATE;
private static final String TYPE_DATETIME = IFormattedObject.TYPE_DATETIME;
private static String datePattern;
private static String datetimePattern;
private static FormatController formatController;
@BeforeClass
public static void initStatics() {
// FIXME: we have to start Freeplane to create a Controller for script execution
System.setProperty("org.freeplane.nosplash", "true");
new FreeplaneStarter().createController();
MModeControllerFactory.createModeController();
formatController = FormatController.getController();
datePattern = ((SimpleDateFormat) formatController.getDefaultFormat(TYPE_DATE)).toPattern();
datetimePattern = ((SimpleDateFormat) formatController.getDefaultFormat(TYPE_DATETIME)).toPattern();
}
@Test
public void testNumberLiteralParser() throws Exception {
NumberLiteralParser parser = new NumberLiteralParser();
// null safe
assertFormattedNumberEquals(null, parser.parse(null));
// integer
assertFormattedNumberEquals(0., parser.parse("0"));
assertFormattedNumberEquals(-1., parser.parse("-1"));
assertFormattedNumberEquals(999., parser.parse("999"));
// floats
assertFormattedNumberEquals(0.12345, parser.parse("0.12345"));
assertFormattedNumberEquals(-1.12345, parser.parse("-1.12345"));
assertFormattedNumberEquals(999.12345, parser.parse("999.12345"));
// leading decimal separator
assertFormattedNumberEquals(.12345, parser.parse(".12345"));
assertFormattedNumberEquals(-0.12345, parser.parse("-.12345"));
// invalid symbols
assertFormattedNumberEquals("parser should not recognize thousand separator", null, parser.parse("99,999"));
assertFormattedNumberEquals("parser should not recognize thousand separator", null, parser.parse("999,999.99"));
// invalid chars
assertFormattedNumberEquals("parser should not skip invalid chars", null, parser.parse("999x.12345"));
assertFormattedNumberEquals("parser should not skip invalid chars", null, parser.parse("x0.12345"));
assertFormattedNumberEquals("parser should not skip invalid chars", null, parser.parse("-1.12345x"));
// whitespace
assertFormattedNumberEquals("parser should not ignore whitespace", null, parser.parse("999 .12345"));
assertFormattedNumberEquals("parser should not ignore whitespace", null, parser.parse("-1.12345 "));
assertFormattedNumberEquals("parser should not ignore whitespace", null, parser.parse(" 0.12345"));
}
private void testDecimalParserImpl(final Locale locale, final char sep, final char thousand_sep) {
DecimalFormatParser parser = new DecimalFormatParser(locale);
// null safe
assertFormattedNumberEquals(null, parser.parse(null));
// integer
assertFormattedNumberEquals(0., parser.parse("0"));
assertFormattedNumberEquals(-1., parser.parse("-1"));
assertFormattedNumberEquals(999., parser.parse("999"));
// floats
assertFormattedNumberEquals("decimal separator is the comma", 0.12345, parser.parse("0" + sep + "12345"));
assertFormattedNumberEquals("decimal separator is the comma", -1.12345, parser.parse("-1" + sep + "12345"));
assertFormattedNumberEquals("decimal separator is the comma", 999.12345, parser.parse("999" + sep + "12345"));
// leading decimal separator
assertFormattedNumberEquals(.12345, parser.parse("" + sep + "12345"));
assertFormattedNumberEquals(-0.12345, parser.parse("-" + sep + "12345"));
// invalid symbols
assertFormattedNumberEquals("thousand sep. not recognized", 99999., parser.parse("99" + thousand_sep + "999"));
assertFormattedNumberEquals("thousand sep. not recognized", 999999.99,
parser.parse("999" + thousand_sep + "999" + sep + "99"));
// invalid chars
assertFormattedNumberEquals("parser should not skip invalid chars", null, parser.parse("999x" + sep + "12345"));
assertFormattedNumberEquals("parser should not skip invalid chars", null, parser.parse("x0" + sep + "12345"));
assertFormattedNumberEquals("parser should not skip invalid chars", null, parser.parse("-1" + sep + "12345x"));
// whitespace
assertFormattedNumberEquals("parser should not ignore whitespace", null, parser.parse("999 " + sep + "12345"));
assertFormattedNumberEquals("parser should not ignore whitespace", null, parser.parse("-1" + sep + "12345 "));
assertFormattedNumberEquals("parser should not ignore whitespace", null, parser.parse(" 0" + sep + "12345"));
}
@Test
public void testDecimalParser_en() throws Exception {
final Locale locale = new Locale("en");
final char sep = '.';
final char thousand_sep = ',';
testDecimalParserImpl(locale, sep, thousand_sep);
}
@Test
public void testDecimalParser_de() throws Exception {
final Locale locale = new Locale("de");
final char sep = ',';
final char thousand_sep = '.';
testDecimalParserImpl(locale, sep, thousand_sep);
}
private void assertFormattedNumberEquals(Double expected, Object actual) {
if (expected != actual)
assertEquals(expected, toDouble(actual), 1e-7);
}
private void assertFormattedNumberEquals(String message, Double expected, Object actual) {
if (expected != actual)
assertEquals(message, expected, toDouble(actual), 1e-7);
}
// Parsers return formatted objects -> unwrap them
private Double toDouble(Object parseResult) {
if (parseResult == null)
return null;
final FormattedNumber formattedNumber = (FormattedNumber) parseResult;
return formattedNumber.doubleValue();
}
@Test
public void testIsoDateParser() throws Exception {
IsoDateParser parser = new IsoDateParser();
// null safe
assertFormattedDateEquals(null, parser.parse(null), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:55.123+0530", parser.parse("2010-08-16 22:31:55.123+0530"),
datetimePattern);
// note that SimpleDateFormat uses local time zone for conversion - use DateFormatUtils instead!
final String Z = getLocalTimeZoneOffsetString();
assertFormattedDateEquals(("2010-08-16 22:31:55.123" + Z), parser.parse("2010-08-16 22:31:55.123"),
datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("2010-08-16 22:31:55.000"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("2010-08-16 22:31:55"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:00", parser.parse("2010-08-16 22:31"), datetimePattern);
assertFormattedDateEquals("2010-08-16 00:00:00", parser.parse("2010-08-16"), datePattern);
//
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("2010-08-16T22:31:55.000"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("2010-08-16T22:31:55"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:00", parser.parse("2010-08-16T22:31"), datetimePattern);
assertFormattedDateEquals("2010-08-16 00:00:00", parser.parse("2010-08-16"), datePattern);
//
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("20100816223155.000"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("20100816223155"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:00", parser.parse("201008162231"), datetimePattern);
assertFormattedDateEquals("2010-08-16 00:00:00", parser.parse("20100816"), datePattern);
//
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("20100816T223155.000"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:55", parser.parse("20100816T223155"), datetimePattern);
assertFormattedDateEquals("2010-08-16 22:31:00", parser.parse("20100816T2231"), datetimePattern);
assertFormattedDateEquals("2010-08-16 00:00:00", parser.parse("20100816"), datePattern);
}
private void assertFormattedDateEquals(String expected, Object actual, String expectedPattern) {
final FormattedDate formattedDate = (FormattedDate) actual;
final Date expectedDate = date(expected);
final String message = "expected: " + (expectedDate == null ? null : FormattedDate.toStringISO(expectedDate))
+ "!=" + (formattedDate == null ? null : FormattedDate.toStringISO(formattedDate));
final Date actualDate = formattedDate == null ? null : new Date(formattedDate.getTime());
assertEquals(message, expectedDate, actualDate);
if (actual != null)
assertEquals("wrong formatter pattern", expectedPattern, formattedDate.getPattern());
}
private Date date(String string) {
try {
if (string == null)
return null;
else if (string.matches(".*[-+]\\d{4}"))
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ").parse(string);
else
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(string);
}
catch (ParseException e) {
throw new RuntimeException(e);
}
}
private String getLocalTimeZoneOffsetString() {
final TimeZone timeZone = TimeZone.getDefault();
int offsetMinutes = (timeZone.getRawOffset() + timeZone.getDSTSavings()) / 60000;
return String.format("%+03d%02d", offsetMinutes / 60, offsetMinutes % 60);
}
@Test
public void testDateFormatParser() throws Exception {
//
// "M/d"
//
DateFormatParser parser = new DateFormatParser("M/d");
// null safe
assertFormattedDateEquals(null, parser.parse(null), datePattern);
// short month
assertFormattedDateEquals(String.format("%tY-02-21 00:00:00", new Date()), parser.parse("2/21"), datePattern);
// full month
assertFormattedDateEquals(String.format("%tY-02-21 00:00:00", new Date()), parser.parse("02/21"), datePattern);
// parser should parse the whole input
assertFormattedDateEquals(null, parser.parse("2/21/2011"), datePattern);
//
// "M/d/y"
//
parser = new DateFormatParser("M/d/y");
// null safe
assertFormattedDateEquals(null, parser.parse(null), datePattern);
// short month and year
assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("2/21/10"), datePattern);
// full year
assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("2/21/2010"), datePattern);
// full
assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("02/21/2010"), datePattern);
// SimpleDateFormat ignores leading whitespace but DateFormatParser does not
assertFormattedDateEquals(null, parser.parse(" 2/21/2011"), datePattern);
assertFormattedDateEquals(null, parser.parse("2 /21/2011"), datePattern);
assertFormattedDateEquals(null, parser.parse("2/21/2011 "), datePattern);
// illegal dates
assertFormattedDateEquals(null, parser.parse("0/21/2010"), datePattern);
assertFormattedDateEquals(null, parser.parse("13/21/2010"), datePattern);
assertFormattedDateEquals(null, parser.parse("12/32/2010"), datePattern);
//
// leading spaces: " M/d/y"
//
parser = new DateFormatParser(" M/d/y");
assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse(" 02/21/2010"), datePattern);
assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("02/21/2010"), datePattern);
assertFormattedDateEquals(null, parser.parse("02/21/2010 "), datePattern);
}
}