/* * 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.FormatController; import org.freeplane.features.format.FormattedDate; import org.freeplane.features.format.FormattedNumber; import org.freeplane.features.format.IFormattedObject; import org.freeplane.features.format.ScannerController; 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 ScannerTest { private static final String TYPE_DATE = IFormattedObject.TYPE_DATE; private static final String TYPE_DATETIME = IFormattedObject.TYPE_DATETIME; private static ScannerController scannerController; private static String datePattern; private static String datetimePattern; private static FormatController formatController; @BeforeClass public static void initStatics() throws Exception { // 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(); scannerController = ScannerController.getController(); datePattern = ((SimpleDateFormat) formatController.getDefaultFormat(TYPE_DATE)).toPattern(); datetimePattern = ((SimpleDateFormat) formatController.getDefaultFormat(TYPE_DATETIME)).toPattern(); } @Test public void testNumberLiterals() throws Exception { scannerController.selectScanner(new Locale("en")); ScannerController parser = scannerController; // 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 chars assertParseFailure("parser should not skip invalid chars", parser, "999x.12345"); assertParseFailure("parser should not skip invalid chars", parser, "x0.12345"); assertParseFailure("parser should not skip invalid chars", parser, "-1.12345x"); // whitespace assertParseFailure("parser should not ignore whitespace", parser, "999 .12345"); assertParseFailure("parser should not ignore whitespace", parser, "-1.12345 "); assertParseFailure("parser should not ignore whitespace", parser, " 0.12345"); } private void assertParseFailure(String message, ScannerController parser, String input) { assertEquals(message, input, parser.parse(input)); } private void testDecimalParserImpl(final Locale locale, final char sep, final char thousand_sep) { scannerController.selectScanner(locale); ScannerController parser = scannerController; // 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 assertParseFailure("parser should not skip invalid chars", parser, "999x" + sep + "12345"); assertParseFailure("parser should not skip invalid chars", parser, "x0" + sep + "12345"); assertParseFailure("parser should not skip invalid chars", parser, "-1" + sep + "12345x"); // whitespace assertParseFailure("parser should not ignore whitespace", parser, "999 " + sep + "12345"); assertParseFailure("parser should not ignore whitespace", parser, "-1" + sep + "12345 "); assertParseFailure("parser should not ignore whitespace", parser, " 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) { assertIsFormattedNumber(null, actual); if (expected != actual) assertEquals(expected, toDouble(actual), 1e-7); } private void assertFormattedNumberEquals(String message, Double expected, Object actual) { assertIsFormattedNumber(message, actual); if (expected != actual) assertEquals(message, expected, toDouble(actual), 1e-7); } private void assertIsFormattedNumber(String message, Object actual) { if (actual != null) { message = message == null ? "" : message + ": "; assertEquals(message + "not a FormattedNumber: " + actual, FormattedNumber.class, actual.getClass()); } } // 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 { testIsoDateParserImpl(new Locale("en")); testIsoDateParserImpl(new Locale("de")); } private void testIsoDateParserImpl(Locale locale) { scannerController.selectScanner(locale); ScannerController parser = scannerController; // null safe assertFormattedDateEquals(null, parser.parse(null), datePattern); 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); // ambiguous: not a date but a number: // 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_en() { scannerController.selectScanner(new Locale("en")); ScannerController parser = scannerController; // // "M/d" // // 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); // // "M/d/y" // // 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 assertParseFailure("whitespace should not be ignored", parser, " 2/21/2011"); assertParseFailure("whitespace should not be ignored", parser, "2 /21/2011"); assertParseFailure("whitespace should not be ignored", parser, "2/21/2011 "); } @Test public void testDateFormatParser_de() { scannerController.selectScanner(new Locale("de")); ScannerController parser = scannerController; // // "d.M" // // short month assertFormattedDateEquals(String.format("%tY-02-21 00:00:00", new Date()), parser.parse("21.2"), datePattern); // full month assertFormattedDateEquals(String.format("%tY-02-21 00:00:00", new Date()), parser.parse("21.02"), datePattern); // // "d.M.y" // // short month and year assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("21.2.10"), datePattern); // full year assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("21.2.2010"), datePattern); // full assertFormattedDateEquals("2010-02-21 00:00:00", parser.parse("21.02.2010"), datePattern); // SimpleDateFormat ignores leading whitespace but DateFormatParser does not // SimpleDateFormat ignores leading whitespace but DateFormatParser does not assertParseFailure("whitespace should not be ignored", parser, " 21.2.2011"); assertParseFailure("whitespace should not be ignored", parser, "21 .2.2011"); assertParseFailure("whitespace should not be ignored", parser, "21.2.2011 "); } }