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.logging.log4j.spi;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024import java.util.NoSuchElementException;
025
026/**
027 * A copy-on-write thread-safe variant of
028 * {@code org.apache.logging.log4j.spi.ThreadContextStack} in which all mutative
029 * operations (add, pop, and so on) are implemented by making a fresh copy of
030 * the underlying list.
031 */
032public class DefaultThreadContextStack implements ThreadContextStack {
033
034    private static final long serialVersionUID = 5050501L;
035
036    private static ThreadLocal<List<String>> stack = new ThreadLocal<List<String>>();
037
038    private final boolean useStack;
039
040    public DefaultThreadContextStack(final boolean useStack) {
041        this.useStack = useStack;
042    }
043
044    @Override
045    public String pop() {
046        if (!useStack) {
047            return "";
048        }
049        final List<String> list = stack.get();
050        if (list == null || list.size() == 0) {
051            throw new NoSuchElementException("The ThreadContext stack is empty");
052        }
053        final List<String> copy = new ArrayList<String>(list);
054        final int last = copy.size() - 1;
055        final String result = copy.remove(last);
056        stack.set(Collections.unmodifiableList(copy));
057        return result;
058    }
059
060    @Override
061    public String peek() {
062        final List<String> list = stack.get();
063        if (list == null || list.size() == 0) {
064            return null;
065        }
066        final int last = list.size() - 1;
067        return list.get(last);
068    }
069
070    @Override
071    public void push(final String message) {
072        if (!useStack) {
073            return;
074        }
075        add(message);
076    }
077
078    @Override
079    public int getDepth() {
080        final List<String> list = stack.get();
081        return list == null ? 0 : list.size();
082    }
083
084    @Override
085    public List<String> asList() {
086        final List<String> list = stack.get();
087        if (list == null) {
088            return Collections.emptyList();
089        }
090        return list;
091    }
092
093    @Override
094    public void trim(final int depth) {
095        if (depth < 0) {
096            throw new IllegalArgumentException(
097                    "Maximum stack depth cannot be negative");
098        }
099        final List<String> list = stack.get();
100        if (list == null) {
101            return;
102        }
103        final List<String> copy = new ArrayList<String>();
104        final int count = Math.min(depth, list.size());
105        for (int i = 0; i < count; i++) {
106            copy.add(list.get(i));
107        }
108        stack.set(copy);
109    }
110
111    @Override
112    public ThreadContextStack copy() {
113        List<String> result = null;
114        if (!useStack || (result = stack.get()) == null) {
115            return new MutableThreadContextStack(new ArrayList<String>());
116        }
117        return new MutableThreadContextStack(result);
118    }
119
120    @Override
121    public void clear() {
122        stack.remove();
123    }
124
125    @Override
126    public int size() {
127        final List<String> result = stack.get();
128        return result == null ? 0 : result.size();
129    }
130
131    @Override
132    public boolean isEmpty() {
133        final List<String> result = stack.get();
134        return result == null || result.isEmpty();
135    }
136
137    @Override
138    public boolean contains(final Object o) {
139        final List<String> result = stack.get();
140        return result != null && result.contains(o);
141    }
142
143    @Override
144    public Iterator<String> iterator() {
145        final List<String> immutable = stack.get();
146        if (immutable == null) {
147            final List<String> empty = Collections.emptyList();
148            return empty.iterator();
149        }
150        return immutable.iterator();
151    }
152
153    @Override
154    public Object[] toArray() {
155        final List<String> result = stack.get();
156        if (result == null) {
157            return new String[0];
158        }
159        return result.toArray(new Object[result.size()]);
160    }
161
162    @Override
163    public <T> T[] toArray(final T[] ts) {
164        final List<String> result = stack.get();
165        if (result == null) {
166            if (ts.length > 0) { // as per the contract of j.u.List#toArray(T[])
167                ts[0] = null;
168            }
169            return ts;
170        }
171        return result.toArray(ts);
172    }
173
174    @Override
175    public boolean add(final String s) {
176        if (!useStack) {
177            return false;
178        }
179        final List<String> list = stack.get();
180        final List<String> copy = list == null ? new ArrayList<String>()
181                : new ArrayList<String>(list);
182        copy.add(s);
183        stack.set(Collections.unmodifiableList(copy));
184        return true;
185    }
186
187    @Override
188    public boolean remove(final Object o) {
189        if (!useStack) {
190            return false;
191        }
192        final List<String> list = stack.get();
193        if (list == null || list.size() == 0) {
194            return false;
195        }
196        final List<String> copy = new ArrayList<String>(list);
197        final boolean result = copy.remove(o);
198        stack.set(Collections.unmodifiableList(copy));
199        return result;
200    }
201
202    @Override
203    public boolean containsAll(final Collection<?> objects) {
204        if (objects.isEmpty()) { // quick check before accessing the ThreadLocal
205            return true; // looks counter-intuitive, but see
206                         // j.u.AbstractCollection
207        }
208        final List<String> list = stack.get();
209        return list != null && list.containsAll(objects);
210    }
211
212    @Override
213    public boolean addAll(final Collection<? extends String> strings) {
214        if (!useStack || strings.isEmpty()) {
215            return false;
216        }
217        final List<String> list = stack.get();
218        final List<String> copy = list == null ? new ArrayList<String>()
219                : new ArrayList<String>(list);
220        copy.addAll(strings);
221        stack.set(Collections.unmodifiableList(copy));
222        return true;
223    }
224
225    @Override
226    public boolean removeAll(final Collection<?> objects) {
227        if (!useStack || objects.isEmpty()) {
228            return false;
229        }
230        final List<String> list = stack.get();
231        if (list == null || list.isEmpty()) {
232            return false;
233        }
234        final List<String> copy = new ArrayList<String>(list);
235        final boolean result = copy.removeAll(objects);
236        stack.set(Collections.unmodifiableList(copy));
237        return result;
238    }
239
240    @Override
241    public boolean retainAll(final Collection<?> objects) {
242        if (!useStack || objects.isEmpty()) {
243            return false;
244        }
245        final List<String> list = stack.get();
246        if (list == null || list.isEmpty()) {
247            return false;
248        }
249        final List<String> copy = new ArrayList<String>(list);
250        final boolean result = copy.retainAll(objects);
251        stack.set(Collections.unmodifiableList(copy));
252        return result;
253    }
254
255    @Override
256    public String toString() {
257        final List<String> list = stack.get();
258        return list == null ? "[]" : list.toString();
259    }
260}