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 }