ExceptionContext.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.math4.legacy.exception.util;

  18. import java.util.List;
  19. import java.util.ArrayList;
  20. import java.util.Set;
  21. import java.util.Map;
  22. import java.io.IOException;
  23. import java.io.Serializable;
  24. import java.io.ObjectOutputStream;
  25. import java.io.ObjectInputStream;
  26. import java.util.HashMap;
  27. import java.text.MessageFormat;
  28. import java.util.Locale;

  29. /**
  30.  * Class that contains the actual implementation of the functionality mandated
  31.  * by the {@link ExceptionContext} interface.
  32.  * All Commons Math exceptions delegate the interface's methods to this class.
  33.  *
  34.  * @since 3.0
  35.  */
  36. public class ExceptionContext implements Serializable {
  37.     /** Serializable version Id. */
  38.     private static final long serialVersionUID = -6024911025449780478L;
  39.     /**
  40.      * The throwable to which this context refers to.
  41.      */
  42.     private Throwable throwable;
  43.     /**
  44.      * Various informations that enrich the informative message.
  45.      */
  46.     private List<Localizable> msgPatterns;
  47.     /**
  48.      * Various informations that enrich the informative message.
  49.      * The arguments will replace the corresponding place-holders in
  50.      * {@link #msgPatterns}.
  51.      */
  52.     private List<Object[]> msgArguments;
  53.     /**
  54.      * Arbitrary context information.
  55.      */
  56.     private Map<String, Object> context;

  57.     /** Simple constructor.
  58.      * @param throwable the exception this context refers too
  59.      */
  60.     public ExceptionContext(final Throwable throwable) {
  61.         this.throwable = throwable;
  62.         msgPatterns    = new ArrayList<>();
  63.         msgArguments   = new ArrayList<>();
  64.         context        = new HashMap<>();
  65.     }

  66.     /** Get a reference to the exception to which the context relates.
  67.      * @return a reference to the exception to which the context relates
  68.      */
  69.     public Throwable getThrowable() {
  70.         return throwable;
  71.     }

  72.     /**
  73.      * Adds a message.
  74.      *
  75.      * @param pattern Message pattern.
  76.      * @param arguments Values for replacing the placeholders in the message
  77.      * pattern.
  78.      */
  79.     public void addMessage(Localizable pattern,
  80.                            Object... arguments) {
  81.         msgPatterns.add(pattern);
  82.         msgArguments.add(ArgUtils.flatten(arguments));
  83.     }

  84.     /**
  85.      * Sets the context (key, value) pair.
  86.      * Keys are assumed to be unique within an instance. If the same key is
  87.      * assigned a new value, the previous one will be lost.
  88.      *
  89.      * @param key Context key (not null).
  90.      * @param value Context value.
  91.      */
  92.     public void setValue(String key, Object value) {
  93.         context.put(key, value);
  94.     }

  95.     /**
  96.      * Gets the value associated to the given context key.
  97.      *
  98.      * @param key Context key.
  99.      * @return the context value or {@code null} if the key does not exist.
  100.      */
  101.     public Object getValue(String key) {
  102.         return context.get(key);
  103.     }

  104.     /**
  105.      * Gets all the keys stored in the exception.
  106.      *
  107.      * @return the set of keys.
  108.      */
  109.     public Set<String> getKeys() {
  110.         return context.keySet();
  111.     }

  112.     /**
  113.      * Gets the default message.
  114.      *
  115.      * @return the message.
  116.      */
  117.     public String getMessage() {
  118.         return getMessage(Locale.US);
  119.     }

  120.     /**
  121.      * Gets the message in the default locale.
  122.      *
  123.      * @return the localized message.
  124.      */
  125.     public String getLocalizedMessage() {
  126.         return getMessage(Locale.getDefault());
  127.     }

  128.     /**
  129.      * Gets the message in a specified locale.
  130.      *
  131.      * @param locale Locale in which the message should be translated.
  132.      * @return the localized message.
  133.      */
  134.     public String getMessage(final Locale locale) {
  135.         return buildMessage(locale, ": ");
  136.     }

  137.     /**
  138.      * Gets the message in a specified locale.
  139.      *
  140.      * @param locale Locale in which the message should be translated.
  141.      * @param separator Separator inserted between the message parts.
  142.      * @return the localized message.
  143.      */
  144.     public String getMessage(final Locale locale,
  145.                              final String separator) {
  146.         return buildMessage(locale, separator);
  147.     }

  148.     /**
  149.      * Builds a message string.
  150.      *
  151.      * @param locale Locale in which the message should be translated.
  152.      * @param separator Message separator.
  153.      * @return a localized message string.
  154.      */
  155.     private String buildMessage(Locale locale,
  156.                                 String separator) {
  157.         final StringBuilder sb = new StringBuilder();
  158.         int count = 0;
  159.         final int len = msgPatterns.size();
  160.         for (int i = 0; i < len; i++) {
  161.             final Localizable pat = msgPatterns.get(i);
  162.             final Object[] args = msgArguments.get(i);
  163.             final MessageFormat fmt = new MessageFormat(pat.getLocalizedString(locale),
  164.                                                         locale);
  165.             sb.append(fmt.format(args));
  166.             if (++count < len) {
  167.                 // Add a separator if there are other messages.
  168.                 sb.append(separator);
  169.             }
  170.         }

  171.         return sb.toString();
  172.     }

  173.     /**
  174.      * Serialize this object to the given stream.
  175.      *
  176.      * @param out Stream.
  177.      * @throws IOException This should never happen.
  178.      */
  179.     private void writeObject(ObjectOutputStream out)
  180.         throws IOException {
  181.         out.writeObject(throwable);
  182.         serializeMessages(out);
  183.         serializeContext(out);
  184.     }
  185.     /**
  186.      * Deserialize this object from the given stream.
  187.      *
  188.      * @param in Stream.
  189.      * @throws IOException This should never happen.
  190.      * @throws ClassNotFoundException This should never happen.
  191.      */
  192.     private void readObject(ObjectInputStream in)
  193.         throws IOException,
  194.                ClassNotFoundException {
  195.         throwable = (Throwable) in.readObject();
  196.         deSerializeMessages(in);
  197.         deSerializeContext(in);
  198.     }

  199.     /**
  200.      * Serialize  {@link #msgPatterns} and {@link #msgArguments}.
  201.      *
  202.      * @param out Stream.
  203.      * @throws IOException This should never happen.
  204.      */
  205.     private void serializeMessages(ObjectOutputStream out)
  206.         throws IOException {
  207.         // Step 1.
  208.         final int len = msgPatterns.size();
  209.         out.writeInt(len);
  210.         // Step 2.
  211.         for (int i = 0; i < len; i++) {
  212.             final Localizable pat = msgPatterns.get(i);
  213.             // Step 3.
  214.             out.writeObject(pat);
  215.             final Object[] args = msgArguments.get(i);
  216.             final int aLen = args.length;
  217.             // Step 4.
  218.             out.writeInt(aLen);
  219.             for (int j = 0; j < aLen; j++) {
  220.                 if (args[j] instanceof Serializable) {
  221.                     // Step 5a.
  222.                     out.writeObject(args[j]);
  223.                 } else {
  224.                     // Step 5b.
  225.                     out.writeObject(nonSerializableReplacement(args[j]));
  226.                 }
  227.             }
  228.         }
  229.     }

  230.     /**
  231.      * Deserialize {@link #msgPatterns} and {@link #msgArguments}.
  232.      *
  233.      * @param in Stream.
  234.      * @throws IOException This should never happen.
  235.      * @throws ClassNotFoundException This should never happen.
  236.      */
  237.     private void deSerializeMessages(ObjectInputStream in)
  238.         throws IOException,
  239.                ClassNotFoundException {
  240.         // Step 1.
  241.         final int len = in.readInt();
  242.         msgPatterns = new ArrayList<>(len);
  243.         msgArguments = new ArrayList<>(len);
  244.         // Step 2.
  245.         for (int i = 0; i < len; i++) {
  246.             // Step 3.
  247.             final Localizable pat = (Localizable) in.readObject();
  248.             msgPatterns.add(pat);
  249.             // Step 4.
  250.             final int aLen = in.readInt();
  251.             final Object[] args = new Object[aLen];
  252.             for (int j = 0; j < aLen; j++) {
  253.                 // Step 5.
  254.                 args[j] = in.readObject();
  255.             }
  256.             msgArguments.add(args);
  257.         }
  258.     }

  259.     /**
  260.      * Serialize {@link #context}.
  261.      *
  262.      * @param out Stream.
  263.      * @throws IOException This should never happen.
  264.      */
  265.     private void serializeContext(ObjectOutputStream out)
  266.         throws IOException {
  267.         // Step 1.
  268.         final int len = context.size();
  269.         out.writeInt(len);
  270.         for (Map.Entry<String, Object> entry : context.entrySet()) {
  271.             // Step 2.
  272.             out.writeObject(entry.getKey());
  273.             final Object value = entry.getValue();
  274.             if (value instanceof Serializable) {
  275.                 // Step 3a.
  276.                 out.writeObject(value);
  277.             } else {
  278.                 // Step 3b.
  279.                 out.writeObject(nonSerializableReplacement(value));
  280.             }
  281.         }
  282.     }

  283.     /**
  284.      * Deserialize {@link #context}.
  285.      *
  286.      * @param in Stream.
  287.      * @throws IOException This should never happen.
  288.      * @throws ClassNotFoundException This should never happen.
  289.      */
  290.     private void deSerializeContext(ObjectInputStream in)
  291.         throws IOException,
  292.                ClassNotFoundException {
  293.         // Step 1.
  294.         final int len = in.readInt();
  295.         context = new HashMap<>();
  296.         for (int i = 0; i < len; i++) {
  297.             // Step 2.
  298.             final String key = (String) in.readObject();
  299.             // Step 3.
  300.             final Object value = in.readObject();
  301.             context.put(key, value);
  302.         }
  303.     }

  304.     /**
  305.      * Replaces a non-serializable object with an error message string.
  306.      *
  307.      * @param obj Object that does not implement the {@code Serializable}
  308.      * interface.
  309.      * @return a string that mentions which class could not be serialized.
  310.      */
  311.     private static String nonSerializableReplacement(Object obj) {
  312.         return "[Object could not be serialized: " + obj.getClass().getName() + "]";
  313.     }
  314. }