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.lang3.exception;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Set;
025
026import org.apache.commons.lang3.StringUtils;
027import org.apache.commons.lang3.tuple.ImmutablePair;
028import org.apache.commons.lang3.tuple.Pair;
029
030/**
031 * Default implementation of the context storing the label-value pairs for contexted exceptions.
032 * <p>
033 * This implementation is serializable, however this is dependent on the values that
034 * are added also being serializable.
035 * </p>
036 *
037 * @see ContextedException
038 * @see ContextedRuntimeException
039 * @since 3.0
040 */
041public class DefaultExceptionContext implements ExceptionContext, Serializable {
042
043    /** The serialization version. */
044    private static final long serialVersionUID = 20110706L;
045
046    /** The list storing the label-data pairs. */
047    private final List<Pair<String, Object>> contextValues = new ArrayList<>();
048
049    /**
050     * {@inheritDoc}
051     */
052    @Override
053    public DefaultExceptionContext addContextValue(final String label, final Object value) {
054        contextValues.add(new ImmutablePair<>(label, value));
055        return this;
056    }
057
058    /**
059     * {@inheritDoc}
060     */
061    @Override
062    public DefaultExceptionContext setContextValue(final String label, final Object value) {
063        for (final Iterator<Pair<String, Object>> iter = contextValues.iterator(); iter.hasNext();) {
064            final Pair<String, Object> p = iter.next();
065            if (StringUtils.equals(label, p.getKey())) {
066                iter.remove();
067            }
068        }
069        addContextValue(label, value);
070        return this;
071    }
072
073    /**
074     * {@inheritDoc}
075     */
076    @Override
077    public List<Object> getContextValues(final String label) {
078        final List<Object> values = new ArrayList<>();
079        for (final Pair<String, Object> pair : contextValues) {
080            if (StringUtils.equals(label, pair.getKey())) {
081                values.add(pair.getValue());
082            }
083        }
084        return values;
085    }
086
087    /**
088     * {@inheritDoc}
089     */
090    @Override
091    public Object getFirstContextValue(final String label) {
092        for (final Pair<String, Object> pair : contextValues) {
093            if (StringUtils.equals(label, pair.getKey())) {
094                return pair.getValue();
095            }
096        }
097        return null;
098    }
099
100    /**
101     * {@inheritDoc}
102     */
103    @Override
104    public Set<String> getContextLabels() {
105        final Set<String> labels = new HashSet<>();
106        for (final Pair<String, Object> pair : contextValues) {
107            labels.add(pair.getKey());
108        }
109        return labels;
110    }
111
112    /**
113     * {@inheritDoc}
114     */
115    @Override
116    public List<Pair<String, Object>> getContextEntries() {
117        return contextValues;
118    }
119
120    /**
121     * Builds the message containing the contextual information.
122     *
123     * @param baseMessage  the base exception message <b>without</b> context information appended
124     * @return the exception message <b>with</b> context information appended, never null
125     */
126    @Override
127    public String getFormattedExceptionMessage(final String baseMessage){
128        final StringBuilder buffer = new StringBuilder(256);
129        if (baseMessage != null) {
130            buffer.append(baseMessage);
131        }
132
133        if (!contextValues.isEmpty()) {
134            if (buffer.length() > 0) {
135                buffer.append('\n');
136            }
137            buffer.append("Exception Context:\n");
138
139            int i = 0;
140            for (final Pair<String, Object> pair : contextValues) {
141                buffer.append("\t[");
142                buffer.append(++i);
143                buffer.append(':');
144                buffer.append(pair.getKey());
145                buffer.append("=");
146                final Object value = pair.getValue();
147                if (value == null) {
148                    buffer.append("null");
149                } else {
150                    String valueStr;
151                    try {
152                        valueStr = value.toString();
153                    } catch (final Exception e) {
154                        valueStr = "Exception thrown on toString(): " + ExceptionUtils.getStackTrace(e);
155                    }
156                    buffer.append(valueStr);
157                }
158                buffer.append("]\n");
159            }
160            buffer.append("---------------------------------");
161        }
162        return buffer.toString();
163    }
164
165}