/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Properties; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.XMLFormatter; /** * @test * @bug 8072645 * @summary tests that XmlFormatter will print out dates with the new * nanosecond precision. * @run main/othervm XmlFormatterNanos * @author danielfuchs */ public class XmlFormatterNanos { static final int MILLIS_IN_SECOND = 1000; static final int NANOS_IN_MILLI = 1000_000; static final int NANOS_IN_MICRO = 1000; static final int NANOS_IN_SECOND = 1000_000_000; static final boolean verbose = true; static final class TestAssertException extends RuntimeException { TestAssertException(String msg) { super(msg); } } private static void assertEquals(long expected, long received, String msg) { if (expected != received) { throw new TestAssertException("Unexpected result for " + msg + ".\n\texpected: " + expected + "\n\tactual: " + received); } else if (verbose) { System.out.println("Got expected " + msg + ": " + received); } } private static void assertEquals(Object expected, Object received, String msg) { if (!Objects.equals(expected, received)) { throw new TestAssertException("Unexpected result for " + msg + ".\n\texpected: " + expected + "\n\tactual: " + received); } else if (verbose) { System.out.println("Got expected " + msg + ": " + received); } } static class CustomXMLFormatter extends XMLFormatter {} static class Configuration { final Properties propertyFile; private Configuration(Properties properties) { propertyFile = properties; } public Configuration apply() { try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); propertyFile.store(bytes, testName()); ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); LogManager.getLogManager().readConfiguration(bais); } catch (IOException ex) { throw new RuntimeException(ex); } return this; } public static String useInstantProperty(Class<?> type) { return type.getName()+".useInstant"; } public boolean useInstant(XMLFormatter formatter) { return Boolean.parseBoolean(propertyFile.getProperty( formatter.getClass().getName()+".useInstant", "true")); } public String testName() { return propertyFile.getProperty("test.name", "????"); } public static Configuration of(Properties props) { return new Configuration(props); } public static Configuration apply(Properties props) { return of(props).apply(); } } static final List<Properties> properties; static { Properties props1 = new Properties(); props1.setProperty("test.name", "with XML nano element (default)"); Properties props2 = new Properties(); props2.setProperty("test.name", "with XML nano element (standard=true, custom=false)"); props2.setProperty(Configuration.useInstantProperty(XMLFormatter.class), "true"); props2.setProperty(Configuration.useInstantProperty(CustomXMLFormatter.class), "false"); Properties props3 = new Properties(); props3.setProperty("test.name", "with XML nano element (standard=false, custom=true)"); props3.setProperty(Configuration.useInstantProperty(XMLFormatter.class), "false"); props3.setProperty(Configuration.useInstantProperty(CustomXMLFormatter.class), "true"); properties = Collections.unmodifiableList(Arrays.asList( props1, props2)); } public static void main(String[] args) throws Exception { Locale.setDefault(Locale.ENGLISH); properties.stream().forEach(XmlFormatterNanos::test); } static int getNanoAdjustment(LogRecord record) { return record.getInstant().getNano() % NANOS_IN_MILLI; } static void setNanoAdjustment(LogRecord record, int nanos) { record.setInstant(Instant.ofEpochSecond(record.getInstant().getEpochSecond(), (record.getInstant().getNano() / NANOS_IN_MILLI) * NANOS_IN_MILLI + nanos)); } public static void test(Properties props) { Configuration conf = Configuration.apply(props); LogRecord record = new LogRecord(Level.INFO, "Test Name: {0}"); record.setLoggerName("test"); record.setParameters(new Object[] {conf.testName()}); int nanos = record.getInstant().getNano() % NANOS_IN_MILLI; long millis = record.getMillis(); // make sure we don't have leading zeros when printing below // the second precision if (millis % MILLIS_IN_SECOND < 100) millis = millis + 100; // make sure we some nanos - and make sure we don't have // trailing zeros if (nanos % 10 == 0) nanos = nanos + 7; record.setMillis(millis); setNanoAdjustment(record, nanos); final Instant instant = record.getInstant(); if (nanos < 0) { throw new RuntimeException("Unexpected negative nano adjustment: " + getNanoAdjustment(record)); } if (nanos >= NANOS_IN_MILLI) { throw new RuntimeException("Nano adjustment exceeds 1ms: " + getNanoAdjustment(record)); } if (millis != record.getMillis()) { throw new RuntimeException("Unexpected millis: " + millis + " != " + record.getMillis()); } if (millis != record.getInstant().toEpochMilli()) { throw new RuntimeException("Unexpected millis: " + record.getInstant().toEpochMilli()); } long expectedNanos = (millis % MILLIS_IN_SECOND) * NANOS_IN_MILLI + nanos; assertEquals(expectedNanos, instant.getNano(), "Instant.getNano()"); XMLFormatter formatter = new XMLFormatter(); testMatching(formatter, record, instant, expectedNanos, conf.useInstant(formatter)); XMLFormatter custom = new CustomXMLFormatter(); testMatching(custom, record, instant, expectedNanos, conf.useInstant(custom)); } private static void testMatching(XMLFormatter formatter, LogRecord record, Instant instant, long expectedNanos, boolean useInstant) { ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); int zdtNanos = zdt.getNano(); assertEquals(expectedNanos, zdtNanos, "ZonedDateTime.getNano()"); String str = formatter.format(record); String match = "."+expectedNanos; if (str.contains(match) != useInstant) { throw new RuntimeException(formatter.getClass().getSimpleName() + ".format()" + " string does not contain expected nanos: " + "\n\texpected match for: '" + match + "'" + "\n\tin: \n" + str); } System.out.println("Found expected match for '"+match+"' in \n"+str); match = "<millis>"+instant.toEpochMilli()+"</millis>"; if (!str.contains(match)) { throw new RuntimeException(formatter.getClass().getSimpleName() + ".format()" + " string does not contain expected millis: " + "\n\texpected match for: '" + match + "'" + "\n\tin: \n" + str); } System.out.println("Found expected match for '"+match+"' in \n"+str); match = "<nanos>"; if (str.contains(match) != useInstant) { throw new RuntimeException(formatter.getClass().getSimpleName() + ".format()" + " string " + (useInstant ? "does not contain expected nanos: " : "contains unexpected nanos: ") + "\n\t" + (useInstant ? "expected" : "unexpected") + " match for: '" + match + "'" + "\n\tin: \n" + str); } match = "<nanos>"+getNanoAdjustment(record)+"</nanos>"; if (str.contains(match) != useInstant) { throw new RuntimeException(formatter.getClass().getSimpleName() + ".format()" + " string " + (useInstant ? "does not contain expected nanos: " : "contains unexpected nanos: ") + "\n\t" + (useInstant ? "expected" : "unexpected") + " match for: '" + match + "'" + "\n\tin: \n" + str); } if (useInstant) { System.out.println("Found expected match for '"+match+"' in \n"+str); } else { System.out.println("As expected '"+match+"' is not present in \n"+str); } match = useInstant ? DateTimeFormatter.ISO_INSTANT.format(instant) : zdt.truncatedTo(ChronoUnit.SECONDS) .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); match = "<date>"+match+"</date>"; if (!str.contains(match)) { throw new RuntimeException(formatter.getClass().getSimpleName() + ".format()" + " string does not contain expected date: " + "\n\texpected match for: '" + match + "'" + "\n\tin: \n" + str); } System.out.println("Found expected match for '"+match+"' in \n"+str); } }