Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CompactFormatter support for LogRecord::getLongThreadID #116

Merged
merged 2 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/src/main/resources/docs/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ for the Eclipse EE4J Angus Mail project:
----------------------------
The following bugs have been fixed in the 2.0.3 release.

52: CompactFormatter support for LogRecord::getLongThreadID
107: java.io.UnsupportedEncodingException: en_US.iso885915 if charset is "en_US.iso885915"
110: WildFly support for MailHandler

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -367,21 +367,22 @@ public String formatLoggerName(final LogRecord record) {
}

/**
* Formats the thread id property of the given log record. By default this
* is formatted as a {@code long} by an unsigned conversion.
* Formats the thread id property of the given log record. Long thread ids
* are preferred if supported. Otherwise, the integer thread id is
* formatted as a {@code long} by an unsigned conversion.
*
* @param record the record.
* @return the formatted thread id as a number.
* @throws NullPointerException if the given record is null.
* @since JavaMail 1.5.4
*/
@SuppressWarnings("deprecation") //See JDK-8245302
public Number formatThreadID(final LogRecord record) {
/**
* Thread.getID is defined as long and LogRecord.getThreadID is defined
* as int. Convert to unsigned as a means to better map the two types of
* thread identifiers.
*/
return (((long) record.getThreadID()) & 0xffffffffL);
Long id = LogManagerProperties.getLongThreadID(record);
if (id == null) {
id = Integer.toUnsignedLong(record.getThreadID());
}
return id;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2009, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2009, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -76,6 +76,12 @@ final class LogManagerProperties extends Properties {
*/
private static final Method LR_GET_INSTANT;

/**
* Holds the method used to get the long thread id if running on JDK 16 or
* later.
*/
private static final Method LR_GET_LONG_TID;

/**
* Holds the method used to get the default time zone if running on JDK 9 or
* later.
Expand All @@ -88,7 +94,21 @@ final class LogManagerProperties extends Properties {
*/
private static final Method ZDT_OF_INSTANT;

static {
/**
* MethodHandle is available starting at JDK7 and Android API 26.
*/
static { //Added in JDK16 see JDK-8245302
Method lrtid = null;
try {
lrtid = LogRecord.class.getMethod("getLongThreadID");
} catch (final RuntimeException ignore) {
} catch (final Exception ignore) { //No need for specific catch.
} catch (final LinkageError ignore) {
}
LR_GET_LONG_TID = lrtid;
}

static { //Added in JDK9 see JDK-8072645
Method lrgi = null;
Method zisd = null;
Method zdtoi = null;
Expand Down Expand Up @@ -339,6 +359,39 @@ static Comparable<?> getZonedDateTime(LogRecord record) {
return null;
}

/**
* Gets the long thread id from the given log record.
*
* @param record used to get the long thread id.
* @return null if LogRecord doesn't support long thread ids.
* @throws NullPointerException if record is null.
* @since Angus Mail 2.0.3
*/
static Long getLongThreadID(final LogRecord record) {
if (record == null) {
throw new NullPointerException();
}

final Method m = LR_GET_LONG_TID;
if (m != null) {
try {
return (Long) m.invoke(record);
} catch (final InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (cause instanceof Error) {
throw (Error) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else { //Should never happen.
throw new UndeclaredThrowableException(ite);
}
} catch (final RuntimeException ignore) {
} catch (final Exception ignore) {
}
}
return null;
}

/**
* Gets the local host name from the given service.
*
Expand Down Expand Up @@ -376,6 +429,7 @@ static String getLocalHost(final Object s) throws Exception {
*
* @param value an ISO-8601 duration character sequence.
* @return the number of milliseconds parsed from the duration.
* @throws ArithmeticException if the duration is too large or too small.
* @throws ClassNotFoundException if the java.time classes are not present.
* @throws IllegalAccessException if the method is inaccessible.
* @throws InvocationTargetException if the method throws an exception.
Expand All @@ -389,6 +443,10 @@ static String getLocalHost(final Object s) throws Exception {
* @since JavaMail 1.5.5
*/
static long parseDurationToMillis(final CharSequence value) throws Exception {
if (value == null) {
throw new NullPointerException();
}

try {
final Class<?> k = findClass("java.time.Duration");
final Method parse = k.getMethod("parse", CharSequence.class);
Expand All @@ -406,7 +464,12 @@ static long parseDurationToMillis(final CharSequence value) throws Exception {
} catch (final ExceptionInInitializerError EIIE) {
throw wrapOrThrow(EIIE);
} catch (final InvocationTargetException ite) {
throw paramOrError(ite);
final Throwable cause = ite.getCause();
if (cause instanceof ArithmeticException) {
throw (ArithmeticException) cause;
} else {
throw paramOrError(ite);
}
}
}

Expand Down Expand Up @@ -528,9 +591,9 @@ static <T> Comparator<T> reverseOrder(final Comparator<T> c) {
}

Comparator<T> reverse = null;
//Comparator in Java 1.8 has 'reversed' as a default method.
//Comparator in JDK8 has 'reversed' as a default method.
//This code calls that method first to allow custom
//code to define what reverse order means.
//code to define what reverse order means in versions older than JDK8.
try {
//assert Modifier.isPublic(c.getClass().getModifiers()) :
// Modifier.toString(c.getClass().getModifiers());
Expand Down Expand Up @@ -651,6 +714,8 @@ private static String[] reflectionClassNames() throws Exception {
final Class<?> thisClass = LogManagerProperties.class;
assert Modifier.isFinal(thisClass.getModifiers()) : thisClass;
try {
//This code must use reflection to capture extra frames.
//The invoke API doesn't produce the frames needed.
final HashSet<String> traces = new HashSet<>();
Throwable t = Throwable.class.getConstructor().newInstance();
for (StackTraceElement ste : t.getStackTrace()) {
Expand All @@ -661,6 +726,8 @@ private static String[] reflectionClassNames() throws Exception {
}
}

//This code must use reflection to capture extra frames.
//The invoke API doesn't produce the frames needed.
Throwable.class.getMethod("fillInStackTrace").invoke(t);
for (StackTraceElement ste : t.getStackTrace()) {
if (!thisClass.getName().equals(ste.getClassName())) {
Expand Down Expand Up @@ -765,10 +832,10 @@ private static InvocationTargetException wrapOrThrow(
}

/**
* This code is modified from the LogManager, which explictly states
* This code is modified from the LogManager, which explicitly states
* searching the system class loader first, then the context class loader.
* There is resistance (compatibility) to change this behavior to simply
* searching the context class loader.
* searching the context class loader. See JDK-6878454.
*
* @param name full class name
* @return the class.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -158,6 +158,48 @@ static boolean hasJavaTimeModule() {
return false;
}

/**
* Sets the int thread id for the given log record.
*
* @param record a non null log record.
* @param id the thread id.
* @throws NullPointerException if the given record is null.
*/
@SuppressWarnings("deprecation") //See JDK-8245302
static void setIntThreadID(final LogRecord record, int id) {
record.setThreadID(id);
}

/**
* Gets the int thread id for the given log record.
*
* @param record a non null log record.
* @param id the thread id.
* @throws NullPointerException if the given record is null.
*/
@SuppressWarnings("deprecation") //See JDK-8245302
static int getIntThreadID(final LogRecord record) {
return record.getThreadID();
}

/**
* Sets the long thread id for the given log record if it is supported.
*
* @param record a non-null record.
* @param id the long thread id.
* @throws Exception if there is a problem.
* @throws NoSuchMethodException if JDK is older than JDK 16.
* @throws NullPointerException if the given record is null.
*/
static void setLongThreadID(final LogRecord record, long id)
throws Exception {
if (record == null) {
throw new NullPointerException();
}
LogRecord.class.getMethod("setLongThreadID", Long.TYPE)
.invoke(record, id);
}

/**
* Fails if any declared types are outside of the logging-mailhandler.jar.
* This includes classes from the Jakarta Mail spec.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023 Jason Mehrens. All rights reserved.
* Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -1603,23 +1603,105 @@ public void testFormatThrownNullRecord() {
assertNotNull(cf.formatThrown((LogRecord) null));
}

@Test
public void testFormatIntThreadIDReturnType() throws Exception {
LogRecord record = new LogRecord(Level.SEVERE, "");
setIntThreadID(record, 10);
CompactFormatter cf = new CompactFormatter("%10$d");
Number id = cf.formatThreadID(record);

//Default should be long or wider.
if (id.getClass() != Long.class) {
fail(id.getClass().toString());
}
}

@Test
public void testFormatLongThreadIDReturnType() throws Exception {
LogRecord record = new LogRecord(Level.SEVERE, "");
try {
setLongThreadID(record, 11L);
CompactFormatter cf = new CompactFormatter("%10$d");
Number id = cf.formatThreadID(record);
//Default should be long or wider.
if (id.getClass() != Long.class) {
fail(id.getClass().toString());
}
} catch (NoSuchMethodException JDK8245302) {
try {
Method m = LogRecord.class.getMethod("getLongThreadID");
fail(m.toString());
} catch (NoSuchMethodException expect) {
}
}
}

@Test
public void testFormatLongThreadID() throws Exception {
LogRecord record = new LogRecord(Level.SEVERE, "");
try {
long expected = 10L;
if (Thread.currentThread().getId() == expected) {
++expected;
}
setLongThreadID(record, expected);
assertNotEquals(expected, Thread.currentThread().getId());

CompactFormatter cf = new CompactFormatter("%10$d");
String output = cf.format(record);
String expect = Long.toString(expected);
assertEquals(expect, output);

setLongThreadID(record, -1L);
output = cf.format(record);
expect = Long.toString(-1L);
assertEquals(expect, output);

//Test that downcast works right.
Number id = cf.formatThreadID(record);
assertEquals(-1, id.intValue());
assertEquals(expect, Long.toString(id.longValue()));

setLongThreadID(record, Long.MAX_VALUE >>> 1L);
output = cf.format(record);
expect = Long.toString(Long.MAX_VALUE >>> 1L);
assertEquals(expect, output);

int tid = getIntThreadID(record);
assertTrue(String.valueOf(tid), tid < 0);
} catch (NoSuchMethodException JDK8245302) {
try {
Method m = LogRecord.class.getMethod("getLongThreadID");
fail(m.toString());
} catch (NoSuchMethodException expect) {
assertNull(LogManagerProperties.getLongThreadID(record));
}
}
}

@Test
public void testFormatThreadID() {
CompactFormatter cf = new CompactFormatter("%10$d");
LogRecord record = new LogRecord(Level.SEVERE, "");
record.setThreadID(10);
setIntThreadID(record, 10);
String output = cf.format(record);
String expect = Long.toString(record.getThreadID());
assertEquals(expect, output);

record.setThreadID(-1); //Largest value for the CompactFormatter.
setIntThreadID(record, -1);
output = cf.format(record);
expect = Long.toString((1L << 32L) - 1L);
assertEquals(expect, output);
Long ltid = LogManagerProperties.getLongThreadID(record);
if (ltid == null) {
expect = Long.toString((1L << 32L) - 1L);
assertEquals(expect, output);
} else {
expect = Long.toString(-1L);
assertEquals(expect, output);
}

//Test that downcast works right.
Number id = cf.formatThreadID(record);
assertEquals(record.getThreadID(), id.intValue());
assertEquals(getIntThreadID(record), id.intValue());
assertEquals(expect, Long.toString(id.longValue()));
}

Expand Down Expand Up @@ -1794,7 +1876,7 @@ public void testFormatExample5() {
String p = "[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n";
LogRecord r = new LogRecord(Level.SEVERE, "Unable to send notification.");
r.setSequenceNumber(125);
r.setThreadID(38);
setIntThreadID(r, 38);
r.setSourceClassName("MyClass");
r.setSourceMethodName("fatal");
setEpochMilli(r, 1248203502449L);
Expand Down
Loading