View Javadoc
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  
19  import java.util.List;
20  import java.util.ArrayList;
21  import java.util.Set;
22  import java.util.Map;
23  import java.io.IOException;
24  import java.io.Serializable;
25  import java.io.ObjectOutputStream;
26  import java.io.ObjectInputStream;
27  import java.util.HashMap;
28  import java.text.MessageFormat;
29  import java.util.Locale;
30  
31  /**
32   * Class that contains the actual implementation of the functionality mandated
33   * by the {@link ExceptionContext} interface.
34   * All Commons Math exceptions delegate the interface's methods to this class.
35   *
36   * @since 3.0
37   */
38  public class ExceptionContext implements Serializable {
39      /** Serializable version Id. */
40      private static final long serialVersionUID = -6024911025449780478L;
41      /**
42       * The throwable to which this context refers to.
43       */
44      private Throwable throwable;
45      /**
46       * Various informations that enrich the informative message.
47       */
48      private List<Localizable> msgPatterns;
49      /**
50       * Various informations that enrich the informative message.
51       * The arguments will replace the corresponding place-holders in
52       * {@link #msgPatterns}.
53       */
54      private List<Object[]> msgArguments;
55      /**
56       * Arbitrary context information.
57       */
58      private Map<String, Object> context;
59  
60      /** Simple constructor.
61       * @param throwable the exception this context refers too
62       */
63      public ExceptionContext(final Throwable throwable) {
64          this.throwable = throwable;
65          msgPatterns    = new ArrayList<>();
66          msgArguments   = new ArrayList<>();
67          context        = new HashMap<>();
68      }
69  
70      /** Get a reference to the exception to which the context relates.
71       * @return a reference to the exception to which the context relates
72       */
73      public Throwable getThrowable() {
74          return throwable;
75      }
76  
77      /**
78       * Adds a message.
79       *
80       * @param pattern Message pattern.
81       * @param arguments Values for replacing the placeholders in the message
82       * pattern.
83       */
84      public void addMessage(Localizable pattern,
85                             Object... arguments) {
86          msgPatterns.add(pattern);
87          msgArguments.add(ArgUtils.flatten(arguments));
88      }
89  
90      /**
91       * Sets the context (key, value) pair.
92       * Keys are assumed to be unique within an instance. If the same key is
93       * assigned a new value, the previous one will be lost.
94       *
95       * @param key Context key (not null).
96       * @param value Context value.
97       */
98      public void setValue(String key, Object value) {
99          context.put(key, value);
100     }
101 
102     /**
103      * Gets the value associated to the given context key.
104      *
105      * @param key Context key.
106      * @return the context value or {@code null} if the key does not exist.
107      */
108     public Object getValue(String key) {
109         return context.get(key);
110     }
111 
112     /**
113      * Gets all the keys stored in the exception.
114      *
115      * @return the set of keys.
116      */
117     public Set<String> getKeys() {
118         return context.keySet();
119     }
120 
121     /**
122      * Gets the default message.
123      *
124      * @return the message.
125      */
126     public String getMessage() {
127         return getMessage(Locale.US);
128     }
129 
130     /**
131      * Gets the message in the default locale.
132      *
133      * @return the localized message.
134      */
135     public String getLocalizedMessage() {
136         return getMessage(Locale.getDefault());
137     }
138 
139     /**
140      * Gets the message in a specified locale.
141      *
142      * @param locale Locale in which the message should be translated.
143      * @return the localized message.
144      */
145     public String getMessage(final Locale locale) {
146         return buildMessage(locale, ": ");
147     }
148 
149     /**
150      * Gets the message in a specified locale.
151      *
152      * @param locale Locale in which the message should be translated.
153      * @param separator Separator inserted between the message parts.
154      * @return the localized message.
155      */
156     public String getMessage(final Locale locale,
157                              final String separator) {
158         return buildMessage(locale, separator);
159     }
160 
161     /**
162      * Builds a message string.
163      *
164      * @param locale Locale in which the message should be translated.
165      * @param separator Message separator.
166      * @return a localized message string.
167      */
168     private String buildMessage(Locale locale,
169                                 String separator) {
170         final StringBuilder sb = new StringBuilder();
171         int count = 0;
172         final int len = msgPatterns.size();
173         for (int i = 0; i < len; i++) {
174             final Localizable pat = msgPatterns.get(i);
175             final Object[] args = msgArguments.get(i);
176             final MessageFormat fmt = new MessageFormat(pat.getLocalizedString(locale),
177                                                         locale);
178             sb.append(fmt.format(args));
179             if (++count < len) {
180                 // Add a separator if there are other messages.
181                 sb.append(separator);
182             }
183         }
184 
185         return sb.toString();
186     }
187 
188     /**
189      * Serialize this object to the given stream.
190      *
191      * @param out Stream.
192      * @throws IOException This should never happen.
193      */
194     private void writeObject(ObjectOutputStream out)
195         throws IOException {
196         out.writeObject(throwable);
197         serializeMessages(out);
198         serializeContext(out);
199     }
200     /**
201      * Deserialize this object from the given stream.
202      *
203      * @param in Stream.
204      * @throws IOException This should never happen.
205      * @throws ClassNotFoundException This should never happen.
206      */
207     private void readObject(ObjectInputStream in)
208         throws IOException,
209                ClassNotFoundException {
210         throwable = (Throwable) in.readObject();
211         deSerializeMessages(in);
212         deSerializeContext(in);
213     }
214 
215     /**
216      * Serialize  {@link #msgPatterns} and {@link #msgArguments}.
217      *
218      * @param out Stream.
219      * @throws IOException This should never happen.
220      */
221     private void serializeMessages(ObjectOutputStream out)
222         throws IOException {
223         // Step 1.
224         final int len = msgPatterns.size();
225         out.writeInt(len);
226         // Step 2.
227         for (int i = 0; i < len; i++) {
228             final Localizable pat = msgPatterns.get(i);
229             // Step 3.
230             out.writeObject(pat);
231             final Object[] args = msgArguments.get(i);
232             final int aLen = args.length;
233             // Step 4.
234             out.writeInt(aLen);
235             for (int j = 0; j < aLen; j++) {
236                 if (args[j] instanceof Serializable) {
237                     // Step 5a.
238                     out.writeObject(args[j]);
239                 } else {
240                     // Step 5b.
241                     out.writeObject(nonSerializableReplacement(args[j]));
242                 }
243             }
244         }
245     }
246 
247     /**
248      * Deserialize {@link #msgPatterns} and {@link #msgArguments}.
249      *
250      * @param in Stream.
251      * @throws IOException This should never happen.
252      * @throws ClassNotFoundException This should never happen.
253      */
254     private void deSerializeMessages(ObjectInputStream in)
255         throws IOException,
256                ClassNotFoundException {
257         // Step 1.
258         final int len = in.readInt();
259         msgPatterns = new ArrayList<>(len);
260         msgArguments = new ArrayList<>(len);
261         // Step 2.
262         for (int i = 0; i < len; i++) {
263             // Step 3.
264             final Localizable pat = (Localizable) in.readObject();
265             msgPatterns.add(pat);
266             // Step 4.
267             final int aLen = in.readInt();
268             final Object[] args = new Object[aLen];
269             for (int j = 0; j < aLen; j++) {
270                 // Step 5.
271                 args[j] = in.readObject();
272             }
273             msgArguments.add(args);
274         }
275     }
276 
277     /**
278      * Serialize {@link #context}.
279      *
280      * @param out Stream.
281      * @throws IOException This should never happen.
282      */
283     private void serializeContext(ObjectOutputStream out)
284         throws IOException {
285         // Step 1.
286         final int len = context.size();
287         out.writeInt(len);
288         for (Map.Entry<String, Object> entry : context.entrySet()) {
289             // Step 2.
290             out.writeObject(entry.getKey());
291             final Object value = entry.getValue();
292             if (value instanceof Serializable) {
293                 // Step 3a.
294                 out.writeObject(value);
295             } else {
296                 // Step 3b.
297                 out.writeObject(nonSerializableReplacement(value));
298             }
299         }
300     }
301 
302     /**
303      * Deserialize {@link #context}.
304      *
305      * @param in Stream.
306      * @throws IOException This should never happen.
307      * @throws ClassNotFoundException This should never happen.
308      */
309     private void deSerializeContext(ObjectInputStream in)
310         throws IOException,
311                ClassNotFoundException {
312         // Step 1.
313         final int len = in.readInt();
314         context = new HashMap<>();
315         for (int i = 0; i < len; i++) {
316             // Step 2.
317             final String key = (String) in.readObject();
318             // Step 3.
319             final Object value = in.readObject();
320             context.put(key, value);
321         }
322     }
323 
324     /**
325      * Replaces a non-serializable object with an error message string.
326      *
327      * @param obj Object that does not implement the {@code Serializable}
328      * interface.
329      * @return a string that mentions which class could not be serialized.
330      */
331     private static String nonSerializableReplacement(Object obj) {
332         return "[Object could not be serialized: " + obj.getClass().getName() + "]";
333     }
334 }