content: /*
   * Copyright (C) 2014 The Android Open Source Project
   * Copyright (c) 2000, 2016, 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.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * 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.
   */
  
  package java.util.logging;
  import dalvik.system.VMStack;
  import java.util.*;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.concurrent.atomic.AtomicLong;
  import java.io.*;
  
  /**
   * LogRecord objects are used to pass logging requests between
   * the logging framework and individual log Handlers.
   * <p>
   * When a LogRecord is passed into the logging framework it
   * logically belongs to the framework and should no longer be
   * used or updated by the client application.
   * <p>
   * Note that if the client application has not specified an
   * explicit source method name and source class name, then the
   * LogRecord class will infer them automatically when they are
   * first accessed (due to a call on getSourceMethodName or
   * getSourceClassName) by analyzing the call stack.  Therefore,
   * if a logging Handler wants to pass off a LogRecord to another
   * thread, or to transmit it over RMI, and if it wishes to subsequently
   * obtain method name or class name information it should call
   * one of getSourceClassName or getSourceMethodName to force
   * the values to be filled in.
   * <p>
   * <b> Serialization notes:</b>
   * <ul>
   * <li>The LogRecord class is serializable.
   *
   * <li> Because objects in the parameters array may not be serializable,
   * during serialization all objects in the parameters array are
   * written as the corresponding Strings (using Object.toString).
   *
   * <li> The ResourceBundle is not transmitted as part of the serialized
   * form, but the resource bundle name is, and the recipient object's
   * readObject method will attempt to locate a suitable resource bundle.
   *
   * </ul>
   *
   * @since 1.4
   */
  
  public class LogRecord implements Serializable {
      private static final AtomicLong globalSequenceNumber
          = new AtomicLong(0);
  
      /**
       * The default value of threadID will be the current thread's
       * thread id, for ease of correlation, unless it is greater than
       * MIN_SEQUENTIAL_THREAD_ID, in which case we try harder to keep
       * our promise to keep threadIDs unique by avoiding collisions due
       * to 32-bit wraparound.  Unfortunately, LogRecord.getThreadID()
       * returns int, while Thread.getId() returns long.
       */
      private static final int MIN_SEQUENTIAL_THREAD_ID = Integer.MAX_VALUE / 2;
  
      private static final AtomicInteger nextThreadId
          = new AtomicInteger(MIN_SEQUENTIAL_THREAD_ID);
  
      private static final ThreadLocal<Integer> threadIds = new ThreadLocal<>();
  
      /**
       * @serial Logging message level
       */
      private Level level;
  
      /**
       * @serial Sequence number
       */
      private long sequenceNumber;
  
      /**
       * @serial Class that issued logging call
       */
      private String sourceClassName;
  
      /**
       * @serial Method that issued logging call
       */
      private String sourceMethodName;
  
      /**
       * @serial Non-localized raw message text
       */
      private String message;
  
      /**
       * @serial Thread ID for thread that issued logging call.
       */
      private int threadID;
  
      /**
       * @serial Event time in milliseconds since 1970
       */
      private long millis;
  
      /**
       * @serial The Throwable (if any) associated with log message
       */
      private Throwable thrown;
  
      /**
       * @serial Name of the source Logger.
       */
      private String loggerName;
  
      /**
       * @serial Resource bundle name to localized log message.
       */
      private String resourceBundleName;
  
      private transient boolean needToInferCaller;
      private transient Object parameters[];
      private transient ResourceBundle resourceBundle;
  
      /**
       * Returns the default value for a new LogRecord's threadID.
       */
      private int defaultThreadID() {
          long tid = Thread.currentThread().getId();
          if (tid < MIN_SEQUENTIAL_THREAD_ID) {
              return (int) tid;
          } else {
              Integer id = threadIds.get();
              if (id == null) {
                  id = nextThreadId.getAndIncrement();
                  threadIds.set(id);
              }
              return id;
          }
      }
  
      /**
       * Construct a LogRecord with the given level and message values.
       * <p>
       * The sequence property will be initialized with a new unique value.
       * These sequence values are allocated in increasing order within a VM.
       * <p>
       * The millis property will be initialized to the current time.
       * <p>
       * The thread ID property will be initialized with a unique ID for
       * the current thread.
       * <p>
       * All other properties will be initialized to "null".
       *
       * @param level  a logging level value
       * @param msg  the raw non-localized logging message (may be null)
       */
      public LogRecord(Level level, String msg) {
          // Make sure level isn't null, by calling random method.
          level.getClass();
          this.level = level;
          message = msg;
          // Assign a thread ID and a unique sequence number.
          sequenceNumber = globalSequenceNumber.getAndIncrement();
          threadID = defaultThreadID();
          millis = System.currentTimeMillis();
          needToInferCaller = true;
     }
  
      /**
       * Get the source Logger's name.
       *
       * @return source logger name (may be null)
       */
      public String getLoggerName() {
          return loggerName;
      }
  
      /**
       * Set the source Logger's name.
       *
       * @param name   the source logger name (may be null)
       */
      public void setLoggerName(String name) {
          loggerName = name;
      }
  
      /**
       * Get the localization resource bundle
       * <p>
       * This is the ResourceBundle that should be used to localize
       * the message string before formatting it.  The result may
       * be null if the message is not localizable, or if no suitable
       * ResourceBundle is available.
       * @return the localization resource bundle
       */
      public ResourceBundle getResourceBundle() {
          return resourceBundle;
      }
  
      /**
       * Set the localization resource bundle.
       *
       * @param bundle  localization bundle (may be null)
       */
      public void setResourceBundle(ResourceBundle bundle) {
          resourceBundle = bundle;
      }
  
      /**
       * Get the localization resource bundle name
       * <p>
       * This is the name for the ResourceBundle that should be
       * used to localize the message string before formatting it.
       * The result may be null if the message is not localizable.
       * @return the localization resource bundle name
       */
      public String getResourceBundleName() {
          return resourceBundleName;
      }
  
      /**
       * Set the localization resource bundle name.
       *
       * @param name  localization bundle name (may be null)
       */
      public void setResourceBundleName(String name) {
          resourceBundleName = name;
      }
  
      /**
       * Get the logging message level, for example Level.SEVERE.
       * @return the logging message level
       */
      public Level getLevel() {
          return level;
      }
  
      /**
       * Set the logging message level, for example Level.SEVERE.
       * @param level the logging message level
       */
      public void setLevel(Level level) {
          if (level == null) {
              throw new NullPointerException();
          }
          this.level = level;
      }
  
      /**
       * Get the sequence number.
       * <p>
       * Sequence numbers are normally assigned in the LogRecord
       * constructor, which assigns unique sequence numbers to
       * each new LogRecord in increasing order.
       * @return the sequence number
       */
      public long getSequenceNumber() {
          return sequenceNumber;
      }
  
      /**
       * Set the sequence number.
       * <p>
       * Sequence numbers are normally assigned in the LogRecord constructor,
       * so it should not normally be necessary to use this method.
       * @param seq the sequence number
       */
      public void setSequenceNumber(long seq) {
          sequenceNumber = seq;
      }
  
      /**
       * Get the  name of the class that (allegedly) issued the logging request.
       * <p>
       * Note that this sourceClassName is not verified and may be spoofed.
       * This information may either have been provided as part of the
       * logging call, or it may have been inferred automatically by the
       * logging framework.  In the latter case, the information may only
       * be approximate and may in fact describe an earlier call on the
       * stack frame.
       * <p>
       * May be null if no information could be obtained.
       *
       * @return the source class name
       */
      public String getSourceClassName() {
          if (needToInferCaller) {
              inferCaller();
          }
          return sourceClassName;
      }
  
      /**
       * Set the name of the class that (allegedly) issued the logging request.
       *
       * @param sourceClassName the source class name (may be null)
       */
      public void setSourceClassName(String sourceClassName) {
          this.sourceClassName = sourceClassName;
          needToInferCaller = false;
      }
  
      /**
       * Get the  name of the method that (allegedly) issued the logging request.
       * <p>
       * Note that this sourceMethodName is not verified and may be spoofed.
       * This information may either have been provided as part of the
       * logging call, or it may have been inferred automatically by the
       * logging framework.  In the latter case, the information may only
       * be approximate and may in fact describe an earlier call on the
       * stack frame.
       * <p>
       * May be null if no information could be obtained.
       *
       * @return the source method name
       */
      public String getSourceMethodName() {
          if (needToInferCaller) {
              inferCaller();
          }
          return sourceMethodName;
      }
  
      /**
       * Set the name of the method that (allegedly) issued the logging request.
       *
       * @param sourceMethodName the source method name (may be null)
       */
      public void setSourceMethodName(String sourceMethodName) {
          this.sourceMethodName = sourceMethodName;
          needToInferCaller = false;
      }
  
      /**
       * Get the "raw" log message, before localization or formatting.
       * <p>
       * May be null, which is equivalent to the empty string "".
       * <p>
       * This message may be either the final text or a localization key.
       * <p>
       * During formatting, if the source logger has a localization
       * ResourceBundle and if that ResourceBundle has an entry for
       * this message string, then the message string is replaced
       * with the localized value.
       *
       * @return the raw message string
       */
      public String getMessage() {
          return message;
      }
  
      /**
       * Set the "raw" log message, before localization or formatting.
       *
       * @param message the raw message string (may be null)
       */
      public void setMessage(String message) {
          this.message = message;
      }
  
      /**
       * Get the parameters to the log message.
       *
       * @return the log message parameters.  May be null if
       *                  there are no parameters.
       */
      public Object[] getParameters() {
          return parameters;
      }
  
      /**
       * Set the parameters to the log message.
       *
       * @param parameters the log message parameters. (may be null)
       */
      public void setParameters(Object parameters[]) {
          this.parameters = parameters;
      }
  
      /**
       * Get an identifier for the thread where the message originated.
       * <p>
       * This is a thread identifier within the Java VM and may or
       * may not map to any operating system ID.
       *
       * @return thread ID
       */
      public int getThreadID() {
          return threadID;
      }
  
      /**
       * Set an identifier for the thread where the message originated.
       * @param threadID  the thread ID
       */
      public void setThreadID(int threadID) {
          this.threadID = threadID;
      }
  
      /**
       * Get event time in milliseconds since 1970.
       *
       * @return event time in millis since 1970
       */
      public long getMillis() {
          return millis;
      }
  
      /**
       * Set event time.
       *
       * @param millis event time in millis since 1970
       */
      public void setMillis(long millis) {
          this.millis = millis;
      }
  
      /**
       * Get any throwable associated with the log record.
       * <p>
       * If the event involved an exception, this will be the
       * exception object. Otherwise null.
       *
       * @return a throwable
       */
      public Throwable getThrown() {
          return thrown;
      }
  
      /**
       * Set a throwable associated with the log event.
       *
       * @param thrown  a throwable (may be null)
       */
      public void setThrown(Throwable thrown) {
          this.thrown = thrown;
      }
  
      private static final long serialVersionUID = 5372048053134512534L;
  
      /**
       * @serialData Default fields, followed by a two byte version number
       * (major byte, followed by minor byte), followed by information on
       * the log record parameter array.  If there is no parameter array,
       * then -1 is written.  If there is a parameter array (possible of zero
       * length) then the array length is written as an integer, followed
       * by String values for each parameter.  If a parameter is null, then
       * a null String is written.  Otherwise the output of Object.toString()
       * is written.
       */
      private void writeObject(ObjectOutputStream out) throws IOException {
          // We have to call defaultWriteObject first.
          out.defaultWriteObject();
  
          // Write our version number.
          out.writeByte(1);
          out.writeByte(0);
          if (parameters == null) {
              out.writeInt(-1);
              return;
          }
          out.writeInt(parameters.length);
          // Write string values for the parameters.
          for (int i = 0; i < parameters.length; i++) {
              if (parameters[i] == null) {
                  out.writeObject(null);
              } else {
                  out.writeObject(parameters[i].toString());
              }
          }
      }
  
      private void readObject(ObjectInputStream in)
                          throws IOException, ClassNotFoundException {
          // We have to call defaultReadObject first.
          in.defaultReadObject();
  
          // Read version number.
          byte major = in.readByte();
          byte minor = in.readByte();
          if (major != 1) {
              throw new IOException("LogRecord: bad version: " + major + "." + minor);
          }
          int len = in.readInt();
          if (len < -1) {
              throw new NegativeArraySizeException();
          } else if (len == -1) {
              parameters = null;
          } else if (len < 255) {
              parameters = new Object[len];
              for (int i = 0; i < parameters.length; i++) {
                  parameters[i] = in.readObject();
              }
          } else {
              List<Object> params = new ArrayList<>(Math.min(len, 1024));
              for (int i = 0; i < len; i++) {
                  params.add(in.readObject());
              }
              parameters = params.toArray(new Object[params.size()]);
          }
          // If necessary, try to regenerate the resource bundle.
          if (resourceBundleName != null) {
              try {
                  // use system class loader to ensure the ResourceBundle
                  // instance is a different instance than null loader uses
                  final ResourceBundle bundle =
                          ResourceBundle.getBundle(resourceBundleName,
                                  Locale.getDefault(),
                                  ClassLoader.getSystemClassLoader());
                  resourceBundle = bundle;
              } catch (MissingResourceException ex) {
                  // Android-changed: Fall back to context classloader before giving up.
                  /*
                  // This is not a good place to throw an exception,
                  // so we simply leave the resourceBundle null.
                  resourceBundle = null;
                  */
                  try {
                      resourceBundle = ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(),
                              Thread.currentThread().getContextClassLoader());
                  } catch (MissingResourceException innerE){
                      // This is not a good place to throw an exception,
                      // so we simply leave the resourceBundle null.
                      resourceBundle = null;
                  }
              }
          }
  
          needToInferCaller = false;
      }
  
      // Private method to infer the caller's class and method names
      private void inferCaller() {
          needToInferCaller = false;
          // BEGIN Android-changed: Use VMStack.getThreadStackTrace.
          /*
          JavaLangAccess access = SharedSecrets.getJavaLangAccess();
          Throwable throwable = new Throwable();
          int depth = access.getStackTraceDepth(throwable);
          */
          StackTraceElement[] stack = VMStack.getThreadStackTrace(Thread.currentThread());
          int depth = stack.length;
          // END Android-changed: Use VMStack.getThreadStackTrace.
  
          boolean lookingForLogger = true;
          for (int ix = 0; ix < depth; ix++) {
              // Calling getStackTraceElement directly prevents the VM
              // from paying the cost of building the entire stack frame.
              //
              // Android-changed: Use value from previous getThreadStackTrace call.
              // StackTraceElement frame =
              //     access.getStackTraceElement(throwable, ix);
              StackTraceElement frame = stack[ix];
              String cname = frame.getClassName();
              boolean isLoggerImpl = isLoggerImplFrame(cname);
              if (lookingForLogger) {
                  // Skip all frames until we have found the first logger frame.
                  if (isLoggerImpl) {
                      lookingForLogger = false;
                  }
              } else {
                  if (!isLoggerImpl) {
                      // skip reflection call
                      if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
                         // We've found the relevant frame.
                         setSourceClassName(cname);
                         setSourceMethodName(frame.getMethodName());
                         return;
                      }
                  }
              }
          }
          // We haven't found a suitable frame, so just punt.  This is
          // OK as we are only committed to making a "best effort" here.
      }
  
      private boolean isLoggerImplFrame(String cname) {
          // the log record could be created for a platform logger
          return (cname.equals("java.util.logging.Logger") ||
                  cname.startsWith("java.util.logging.LoggingProxyImpl") ||
                  cname.startsWith("sun.util.logging."));
      }
  }
  