/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2013-2016 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013-2016 Jason Mehrens. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.mail.util.logging; import java.io.Serializable; import java.util.Comparator; import java.util.logging.Level; import java.util.logging.LogRecord; /** * Orders log records by level, thrown, sequence, and time. * * This comparator orders LogRecords by how severely each is attributed to * failures in a program. The primary ordering is determined by the use of the * logging API throughout a program by specifying a level to each log message. * The secondary ordering is determined at runtime by the type of errors and * exceptions generated by the program. The remaining ordering assumes that * older log records are less severe than newer log records. * *

* The following LogRecord properties determine severity ordering: *

    *
  1. The natural comparison of the LogRecord * {@linkplain Level#intValue level}. *
  2. The expected recovery order of {@linkplain LogRecord#getThrown() thrown} * property of a LogRecord and its cause chain. This ordering is derived from * the JLS 11.1.1. The Kinds of Exceptions and JLS 11.5 The Exception Hierarchy. * This is performed by {@linkplain #apply(java.lang.Throwable) finding} the * throwable that best describes the entire cause chain. Once a specific * throwable of each chain is identified it is then ranked lowest to highest by * the following rules: * * *
  3. The natural comparison of the LogRecord * {@linkplain LogRecord#getSequenceNumber() sequence}. *
  4. The natural comparison of the LogRecord * {@linkplain LogRecord#getMillis() millis}. *
* * @author Jason Mehrens * @since JavaMail 1.5.2 */ public class SeverityComparator implements Comparator, Serializable { /** * The generated serial version UID. */ private static final long serialVersionUID = -2620442245251791965L; /** * A single instance that is shared among the logging package. * The field is declared as java.util.Comparator so * WebappClassLoader.clearReferencesStaticFinal() method will ignore this * field. */ private static final Comparator INSTANCE = new SeverityComparator(); /** * A shared instance of a SeverityComparator. This is package private so the * public API is not polluted with more methods. * * @return a shared instance of a SeverityComparator. */ static SeverityComparator getInstance() { return (SeverityComparator) INSTANCE; } /** * Identifies a single throwable that best describes the given throwable and * the entire {@linkplain Throwable#getCause() cause} chain. This method can * be overridden to change the behavior of * {@link #compare(java.util.logging.LogRecord, java.util.logging.LogRecord)}. * * @param chain the throwable or null. * @return null if null was given, otherwise the throwable that best * describes the entire chain. * @see #isNormal(java.lang.Throwable) */ public Throwable apply(final Throwable chain) { //Matches the j.u.f.UnaryOperator interface. int limit = 0; Throwable root = chain; Throwable high = null; Throwable normal = null; for (Throwable cause = chain; cause != null; cause = cause.getCause()) { root = cause; //Find the deepest cause. //Find the deepest nomral occurrance. if (isNormal(cause)) { normal = cause; } //Find the deepest error that happened before a normal occurance. if (normal == null && cause instanceof Error) { high = cause; } //Deal with excessive cause chains and cyclic throwables. if (++limit == (1 << 16)) { break; //Give up. } } return high != null ? high : normal != null ? normal : root; } /** * {@link #apply(java.lang.Throwable) Reduces} each throwable chain argument * then compare each throwable result. * * @param tc1 the first throwable chain or null. * @param tc2 the second throwable chain or null. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @see #apply(java.lang.Throwable) * @see #compareThrowable(java.lang.Throwable, java.lang.Throwable) */ public final int applyThenCompare(Throwable tc1, Throwable tc2) { return tc1 == tc2 ? 0 : compareThrowable(apply(tc1), apply(tc2)); } /** * Compares two throwable objects or null. This method does not * {@link #apply(java.lang.Throwable) reduce} each argument before * comparing. This is method can be overridden to change the behavior of * {@linkplain #compare(LogRecord, LogRecord)}. * * @param t1 the first throwable or null. * @param t2 the second throwable or null. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @see #isNormal(java.lang.Throwable) */ public int compareThrowable(final Throwable t1, final Throwable t2) { if (t1 == t2) { //Reflexive test including null. return 0; } else { //Only one or the other is null at this point. //Force normal occurrence to be lower than null. if (t1 == null) { return isNormal(t2) ? 1 : -1; } else { if (t2 == null) { return isNormal(t1) ? -1 : 1; } } //From this point on neither are null. //Follow the shortcut if we can. if (t1.getClass() == t2.getClass()) { return 0; } //Ensure normal occurrence flow control is ordered low. if (isNormal(t1)) { return isNormal(t2) ? 0 : -1; } else { if (isNormal(t2)) { return 1; } } //Rank the two unidenticial throwables using the rules from //JLS 11.1.1. The Kinds of Exceptions and //JLS 11.5 The Exception Hierarchy. if (t1 instanceof Error) { return t2 instanceof Error ? 0 : 1; } else if (t1 instanceof RuntimeException) { return t2 instanceof Error ? -1 : t2 instanceof RuntimeException ? 0 : 1; } else { return t2 instanceof Error || t2 instanceof RuntimeException ? -1 : 0; } } } /** * Compares two log records based on severity. * * @param o1 the first log record. * @param o2 the second log record. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @throws NullPointerException if either argument is null. */ @SuppressWarnings("override") //JDK-6954234 public int compare(final LogRecord o1, final LogRecord o2) { if (o1 == null || o2 == null) { //Don't allow null. throw new NullPointerException(toString(o1, o2)); } /** * LogRecords are mutable so a reflexive relationship test is a safety * requirement. */ if (o1 == o2) { return 0; } int cmp = compare(o1.getLevel(), o2.getLevel()); if (cmp == 0) { cmp = applyThenCompare(o1.getThrown(), o2.getThrown()); if (cmp == 0) { cmp = compare(o1.getSequenceNumber(), o2.getSequenceNumber()); if (cmp == 0) { cmp = compare(o1.getMillis(), o2.getMillis()); } } } return cmp; } /** * Determines if the given object is also a comparator and it imposes the * same ordering as this comparator. * * @param o the reference object with which to compare. * @return true if this object equal to the argument; false otherwise. */ @Override public boolean equals(final Object o) { return o == null ? false : o.getClass() == getClass(); } /** * Returns a hash code value for the object. * * @return Returns a hash code value for the object. */ @Override public int hashCode() { return 31 * getClass().hashCode(); } /** * Determines if the given throwable instance is "normal occurrence". This * is any checked or unchecked exception with 'Interrupt' in the class name * or ancestral class name. Any {@code java.lang.ThreadDeath} object or * subclasses. * * This method can be overridden to change the behavior of the * {@linkplain #apply(java.lang.Throwable)} method. * * @param t a throwable or null. * @return true the given throwable is a "normal occurrence". */ public boolean isNormal(final Throwable t) { if (t == null) { //This is only needed when called directly. return false; } /** * Use the class names to avoid loading more classes. */ final Class root = Throwable.class; final Class error = Error.class; for (Class c = t.getClass(); c != root; c = c.getSuperclass()) { if (error.isAssignableFrom(c)) { if (c.getName().equals("java.lang.ThreadDeath")) { return true; } } else { //Interrupt, Interrupted or Interruption. if (c.getName().contains("Interrupt")) { return true; } } } return false; } /** * Compare two level objects. * * @param a the first level. * @param b the second level. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. */ private int compare(final Level a, final Level b) { return a == b ? 0 : compare(a.intValue(), b.intValue()); } /** * Outline the message create string. * * @param o1 argument one. * @param o2 argument two. * @return the message string. */ private static String toString(final Object o1, final Object o2) { return o1 + ", " + o2; } /** * Compare two longs. Can be removed when JDK 1.7 is required. * * @param x the first long. * @param y the second long. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. */ private int compare(final long x, final long y) { return x < y ? -1 : x > y ? 1 : 0; } }