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 */ 017package org.apache.commons.math3.exception.util; 018 019import java.util.List; 020import java.util.ArrayList; 021import java.util.Set; 022import java.util.Map; 023import java.io.IOException; 024import java.io.Serializable; 025import java.io.ObjectOutputStream; 026import java.io.ObjectInputStream; 027import java.util.HashMap; 028import java.text.MessageFormat; 029import 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 */ 038public class ExceptionContext implements Serializable { 039 /** Serializable version Id. */ 040 private static final long serialVersionUID = -6024911025449780478L; 041 /** 042 * The throwable to which this context refers to. 043 */ 044 private Throwable throwable; 045 /** 046 * Various informations that enrich the informative message. 047 */ 048 private List<Localizable> msgPatterns; 049 /** 050 * Various informations that enrich the informative message. 051 * The arguments will replace the corresponding place-holders in 052 * {@link #msgPatterns}. 053 */ 054 private List<Object[]> msgArguments; 055 /** 056 * Arbitrary context information. 057 */ 058 private Map<String, Object> context; 059 060 /** Simple constructor. 061 * @param throwable the exception this context refers too 062 */ 063 public ExceptionContext(final Throwable throwable) { 064 this.throwable = throwable; 065 msgPatterns = new ArrayList<Localizable>(); 066 msgArguments = new ArrayList<Object[]>(); 067 context = new HashMap<String, Object>(); 068 } 069 070 /** Get a reference to the exception to which the context relates. 071 * @return a reference to the exception to which the context relates 072 */ 073 public Throwable getThrowable() { 074 return throwable; 075 } 076 077 /** 078 * Adds a message. 079 * 080 * @param pattern Message pattern. 081 * @param arguments Values for replacing the placeholders in the message 082 * pattern. 083 */ 084 public void addMessage(Localizable pattern, 085 Object ... arguments) { 086 msgPatterns.add(pattern); 087 msgArguments.add(ArgUtils.flatten(arguments)); 088 } 089 090 /** 091 * Sets the context (key, value) pair. 092 * Keys are assumed to be unique within an instance. If the same key is 093 * assigned a new value, the previous one will be lost. 094 * 095 * @param key Context key (not null). 096 * @param value Context value. 097 */ 098 public void setValue(String key, Object value) { 099 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<Localizable>(len); 260 msgArguments = new ArrayList<Object[]>(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<String, Object>(); 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 String nonSerializableReplacement(Object obj) { 332 return "[Object could not be serialized: " + obj.getClass().getName() + "]"; 333 } 334}