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.Iterator;
022import java.util.List;
023import java.util.Objects;
024
025import org.apache.logging.log4j.ThreadContext.ContextStack;
026import org.apache.logging.log4j.util.StringBuilderFormattable;
027
028/**
029 * TODO
030 */
031public class MutableThreadContextStack implements ThreadContextStack, StringBuilderFormattable {
032
033    private static final long serialVersionUID = 50505011L;
034
035    /**
036     * The underlying list (never null).
037     */
038    private final List<String> list;
039    private boolean frozen;
040
041    /**
042     * Constructs an empty MutableThreadContextStack.
043     */
044    public MutableThreadContextStack() {
045        this(new ArrayList<String>());
046    }
047
048    /**
049     * Constructs a new instance.
050     * @param list
051     */
052    public MutableThreadContextStack(final List<String> list) {
053        this.list = new ArrayList<>(list);
054    }
055
056    private MutableThreadContextStack(final MutableThreadContextStack stack) {
057        this.list = new ArrayList<>(stack.list);
058    }
059
060    private void checkInvariants() {
061        if (frozen) {
062            throw new UnsupportedOperationException("context stack has been frozen");
063        }
064    }
065
066    @Override
067    public String pop() {
068        checkInvariants();
069        if (list.isEmpty()) {
070            return null;
071        }
072        final int last = list.size() - 1;
073        final String result = list.remove(last);
074        return result;
075    }
076
077    @Override
078    public String peek() {
079        if (list.isEmpty()) {
080            return null;
081        }
082        final int last = list.size() - 1;
083        return list.get(last);
084    }
085
086    @Override
087    public void push(final String message) {
088        checkInvariants();
089        list.add(message);
090    }
091
092    @Override
093    public int getDepth() {
094        return list.size();
095    }
096
097    @Override
098    public List<String> asList() {
099        return list;
100    }
101
102    @Override
103    public void trim(final int depth) {
104        checkInvariants();
105        if (depth < 0) {
106            throw new IllegalArgumentException("Maximum stack depth cannot be negative");
107        }
108        if (list == null) {
109            return;
110        }
111        final List<String> copy = new ArrayList<>(list.size());
112        final int count = Math.min(depth, list.size());
113        for (int i = 0; i < count; i++) {
114            copy.add(list.get(i));
115        }
116        list.clear();
117        list.addAll(copy);
118    }
119
120    @Override
121    public ThreadContextStack copy() {
122        return new MutableThreadContextStack(this);
123    }
124
125    @Override
126    public void clear() {
127        checkInvariants();
128        list.clear();
129    }
130
131    @Override
132    public int size() {
133        return list.size();
134    }
135
136    @Override
137    public boolean isEmpty() {
138        return list.isEmpty();
139    }
140
141    @Override
142    public boolean contains(final Object o) {
143        return list.contains(o);
144    }
145
146    @Override
147    public Iterator<String> iterator() {
148        return list.iterator();
149    }
150
151    @Override
152    public Object[] toArray() {
153        return list.toArray();
154    }
155
156    @Override
157    public <T> T[] toArray(final T[] ts) {
158        return list.toArray(ts);
159    }
160
161    @Override
162    public boolean add(final String s) {
163        checkInvariants();
164        return list.add(s);
165    }
166
167    @Override
168    public boolean remove(final Object o) {
169        checkInvariants();
170        return list.remove(o);
171    }
172
173    @Override
174    public boolean containsAll(final Collection<?> objects) {
175        return list.containsAll(objects);
176    }
177
178    @Override
179    public boolean addAll(final Collection<? extends String> strings) {
180        checkInvariants();
181        return list.addAll(strings);
182    }
183
184    @Override
185    public boolean removeAll(final Collection<?> objects) {
186        checkInvariants();
187        return list.removeAll(objects);
188    }
189
190    @Override
191    public boolean retainAll(final Collection<?> objects) {
192        checkInvariants();
193        return list.retainAll(objects);
194    }
195
196    @Override
197    public String toString() {
198        return String.valueOf(list);
199    }
200
201    @Override
202    public void formatTo(final StringBuilder buffer) {
203        buffer.append('[');
204        for (int i = 0; i < list.size(); i++) {
205            if (i > 0) {
206                buffer.append(',').append(' ');
207            }
208            buffer.append(list.get(i));
209        }
210        buffer.append(']');
211    }
212
213    @Override
214    public int hashCode() {
215        return 31 + Objects.hashCode(list);
216    }
217
218    @Override
219    public boolean equals(final Object obj) {
220        if (this == obj) {
221            return true;
222        }
223        if (obj == null) {
224            return false;
225        }
226        if (!(obj instanceof ThreadContextStack)) {
227            return false;
228        }
229        final ThreadContextStack other = (ThreadContextStack) obj;
230        final List<String> otherAsList = other.asList();
231        return Objects.equals(this.list, otherAsList);
232    }
233
234    @Override
235    public ContextStack getImmutableStackOrNull() {
236        return copy();
237    }
238
239    /**
240     * "Freezes" this context stack so it becomes immutable: all mutator methods will throw an exception from now on.
241     */
242    public void freeze() {
243        frozen = true;
244    }
245
246    /**
247     * Returns whether this context stack is frozen.
248     * @return whether this context stack is frozen.
249     */
250    public boolean isFrozen() {
251        return frozen;
252    }
253}