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 }