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