    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    }