001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.math3.exception.util;
018
019 import java.util.List;
020 import java.util.ArrayList;
021 import java.util.Set;
022 import java.util.Map;
023 import java.io.IOException;
024 import java.io.Serializable;
025 import java.io.ObjectOutputStream;
026 import java.io.ObjectInputStream;
027 import java.util.HashMap;
028 import java.text.MessageFormat;
029 import java.util.Locale;
030
031 /**
032 * Class that contains the actual implementation of the functionality mandated
033 * by the {@link ExceptionContext} interface.
034 * All Commons Math exceptions delegate the interface's methods to this class.
035 *
036 * @since 3.0
037 * @version $Id: ExceptionContext.java 1364388 2012-07-22 18:16:43Z tn $
038 */
039 public class ExceptionContext implements Serializable {
040 /** Serializable version Id. */
041 private static final long serialVersionUID = -6024911025449780478L;
042 /**
043 * The throwable to which this context refers to.
044 */
045 private Throwable throwable;
046 /**
047 * Various informations that enrich the informative message.
048 */
049 private List<Localizable> msgPatterns;
050 /**
051 * Various informations that enrich the informative message.
052 * The arguments will replace the corresponding place-holders in
053 * {@link #msgPatterns}.
054 */
055 private List<Object[]> msgArguments;
056 /**
057 * Arbitrary context information.
058 */
059 private Map<String, Object> context;
060
061 /** Simple constructor.
062 * @param throwable the exception this context refers too
063 */
064 public ExceptionContext(final Throwable throwable) {
065 this.throwable = throwable;
066 msgPatterns = new ArrayList<Localizable>();
067 msgArguments = new ArrayList<Object[]>();
068 context = new HashMap<String, Object>();
069 }
070
071 /** Get a reference to the exception to which the context relates.
072 * @return a reference to the exception to which the context relates
073 */
074 public Throwable getThrowable() {
075 return throwable;
076 }
077
078 /**
079 * Adds a message.
080 *
081 * @param pattern Message pattern.
082 * @param arguments Values for replacing the placeholders in the message
083 * pattern.
084 */
085 public void addMessage(Localizable pattern,
086 Object ... arguments) {
087 msgPatterns.add(pattern);
088 msgArguments.add(ArgUtils.flatten(arguments));
089 }
090
091 /**
092 * Sets the context (key, value) pair.
093 * Keys are assumed to be unique within an instance. If the same key is
094 * assigned a new value, the previous one will be lost.
095 *
096 * @param key Context key (not null).
097 * @param value Context value.
098 */
099 public void setValue(String key, Object value) {
100 context.put(key, value);
101 }
102
103 /**
104 * Gets the value associated to the given context key.
105 *
106 * @param key Context key.
107 * @return the context value or {@code null} if the key does not exist.
108 */
109 public Object getValue(String key) {
110 return context.get(key);
111 }
112
113 /**
114 * Gets all the keys stored in the exception
115 *
116 * @return the set of keys.
117 */
118 public Set<String> getKeys() {
119 return context.keySet();
120 }
121
122 /**
123 * Gets the default message.
124 *
125 * @return the message.
126 */
127 public String getMessage() {
128 return getMessage(Locale.US);
129 }
130
131 /**
132 * Gets the message in the default locale.
133 *
134 * @return the localized message.
135 */
136 public String getLocalizedMessage() {
137 return getMessage(Locale.getDefault());
138 }
139
140 /**
141 * Gets the message in a specified locale.
142 *
143 * @param locale Locale in which the message should be translated.
144 * @return the localized message.
145 */
146 public String getMessage(final Locale locale) {
147 return buildMessage(locale, ": ");
148 }
149
150 /**
151 * Gets the message in a specified locale.
152 *
153 * @param locale Locale in which the message should be translated.
154 * @param separator Separator inserted between the message parts.
155 * @return the localized message.
156 */
157 public String getMessage(final Locale locale,
158 final String separator) {
159 return buildMessage(locale, separator);
160 }
161
162 /**
163 * Builds a message string.
164 *
165 * @param locale Locale in which the message should be translated.
166 * @param separator Message separator.
167 * @return a localized message string.
168 */
169 private String buildMessage(Locale locale,
170 String separator) {
171 final StringBuilder sb = new StringBuilder();
172 int count = 0;
173 final int len = msgPatterns.size();
174 for (int i = 0; i < len; i++) {
175 final Localizable pat = msgPatterns.get(i);
176 final Object[] args = msgArguments.get(i);
177 final MessageFormat fmt = new MessageFormat(pat.getLocalizedString(locale),
178 locale);
179 sb.append(fmt.format(args));
180 if (++count < len) {
181 // Add a separator if there are other messages.
182 sb.append(separator);
183 }
184 }
185
186 return sb.toString();
187 }
188
189 /**
190 * Serialize this object to the given stream.
191 *
192 * @param out Stream.
193 * @throws IOException This should never happen.
194 */
195 private void writeObject(ObjectOutputStream out)
196 throws IOException {
197 out.writeObject(throwable);
198 serializeMessages(out);
199 serializeContext(out);
200 }
201 /**
202 * Deserialize this object from the given stream.
203 *
204 * @param in Stream.
205 * @throws IOException This should never happen.
206 * @throws ClassNotFoundException This should never happen.
207 */
208 private void readObject(ObjectInputStream in)
209 throws IOException,
210 ClassNotFoundException {
211 throwable = (Throwable) in.readObject();
212 deSerializeMessages(in);
213 deSerializeContext(in);
214 }
215
216 /**
217 * Serialize {@link #msgPatterns} and {@link #msgArguments}.
218 *
219 * @param out Stream.
220 * @throws IOException This should never happen.
221 */
222 private void serializeMessages(ObjectOutputStream out)
223 throws IOException {
224 // Step 1.
225 final int len = msgPatterns.size();
226 out.writeInt(len);
227 // Step 2.
228 for (int i = 0; i < len; i++) {
229 final Localizable pat = msgPatterns.get(i);
230 // Step 3.
231 out.writeObject(pat);
232 final Object[] args = msgArguments.get(i);
233 final int aLen = args.length;
234 // Step 4.
235 out.writeInt(aLen);
236 for (int j = 0; j < aLen; j++) {
237 if (args[j] instanceof Serializable) {
238 // Step 5a.
239 out.writeObject(args[j]);
240 } else {
241 // Step 5b.
242 out.writeObject(nonSerializableReplacement(args[j]));
243 }
244 }
245 }
246 }
247
248 /**
249 * Deserialize {@link #msgPatterns} and {@link #msgArguments}.
250 *
251 * @param in Stream.
252 * @throws IOException This should never happen.
253 * @throws ClassNotFoundException This should never happen.
254 */
255 private void deSerializeMessages(ObjectInputStream in)
256 throws IOException,
257 ClassNotFoundException {
258 // Step 1.
259 final int len = in.readInt();
260 msgPatterns = new ArrayList<Localizable>(len);
261 msgArguments = new ArrayList<Object[]>(len);
262 // Step 2.
263 for (int i = 0; i < len; i++) {
264 // Step 3.
265 final Localizable pat = (Localizable) in.readObject();
266 msgPatterns.add(pat);
267 // Step 4.
268 final int aLen = in.readInt();
269 final Object[] args = new Object[aLen];
270 for (int j = 0; j < aLen; j++) {
271 // Step 5.
272 args[j] = in.readObject();
273 }
274 msgArguments.add(args);
275 }
276 }
277
278 /**
279 * Serialize {@link #context}.
280 *
281 * @param out Stream.
282 * @throws IOException This should never happen.
283 */
284 private void serializeContext(ObjectOutputStream out)
285 throws IOException {
286 // Step 1.
287 final int len = context.keySet().size();
288 out.writeInt(len);
289 for (String key : context.keySet()) {
290 // Step 2.
291 out.writeObject(key);
292 final Object value = context.get(key);
293 if (value instanceof Serializable) {
294 // Step 3a.
295 out.writeObject(value);
296 } else {
297 // Step 3b.
298 out.writeObject(nonSerializableReplacement(value));
299 }
300 }
301 }
302
303 /**
304 * Deserialize {@link #context}.
305 *
306 * @param in Stream.
307 * @throws IOException This should never happen.
308 * @throws ClassNotFoundException This should never happen.
309 */
310 private void deSerializeContext(ObjectInputStream in)
311 throws IOException,
312 ClassNotFoundException {
313 // Step 1.
314 final int len = in.readInt();
315 context = new HashMap<String, Object>();
316 for (int i = 0; i < len; i++) {
317 // Step 2.
318 final String key = (String) in.readObject();
319 // Step 3.
320 final Object value = in.readObject();
321 context.put(key, value);
322 }
323 }
324
325 /**
326 * Replaces a non-serializable object with an error message string.
327 *
328 * @param obj Object that does not implement the {@code Serializable}
329 * interface.
330 * @return a string that mentions which class could not be serialized.
331 */
332 private String nonSerializableReplacement(Object obj) {
333 return "[Object could not be serialized: " + obj.getClass().getName() + "]";
334 }
335 }