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.core.config;
018
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.Objects;
023import java.util.concurrent.atomic.AtomicReference;
024
025import org.apache.logging.log4j.core.Appender;
026import org.apache.logging.log4j.util.PerformanceSensitive;
027
028/**
029 * Data structure with similar semantics to CopyOnWriteArraySet, but giving direct access to the underlying array.
030 *
031 * @since 2.6
032 */
033@PerformanceSensitive
034public class AppenderControlArraySet {
035    private final AtomicReference<AppenderControl[]> appenderArray = new AtomicReference<>(new AppenderControl[0]);
036
037    /**
038     * Adds an AppenderControl to this set. If this set already contains the element, the call leaves the set unchanged
039     * and returns false.
040     *
041     * @param control The AppenderControl to add.
042     * @return true if this set did not already contain the specified element
043     */
044    public boolean add(final AppenderControl control) {
045        boolean success;
046        do {
047            final AppenderControl[] original = appenderArray.get();
048            for (final AppenderControl existing : original) {
049                if (existing.equals(control)) {
050                    return false; // the appender is already in the list
051                }
052            }
053            final AppenderControl[] copy = Arrays.copyOf(original, original.length + 1);
054            copy[copy.length - 1] = control;
055            success = appenderArray.compareAndSet(original, copy);
056        } while (!success); // could not swap: array was modified by another thread
057        return true; // successfully added
058    }
059
060    /**
061     * Removes the AppenderControl with the specific name and returns it (or {@code null} if no such appender existed).
062     *
063     * @param name The name of the AppenderControl to remove
064     * @return the removed AppenderControl or {@code null}
065     */
066    public AppenderControl remove(final String name) {
067        boolean success;
068        do {
069            success = true;
070            final AppenderControl[] original = appenderArray.get();
071            for (int i = 0; i < original.length; i++) {
072                final AppenderControl appenderControl = original[i];
073                if (Objects.equals(name, appenderControl.getAppenderName())) {
074                    final AppenderControl[] copy = removeElementAt(i, original);
075                    if (appenderArray.compareAndSet(original, copy)) {
076                        return appenderControl; // successfully removed
077                    }
078                    success = false; // could not swap: array was modified by another thread
079                    break;
080                }
081            }
082        } while (!success);
083        return null; // not found
084    }
085
086    private AppenderControl[] removeElementAt(final int i, final AppenderControl[] array) {
087        final AppenderControl[] result = Arrays.copyOf(array, array.length - 1);
088        System.arraycopy(array, i + 1, result, i, result.length - i);
089        return result;
090    }
091
092    /**
093     * Returns all Appenders as a Map.
094     *
095     * @return a Map with the Appender name as the key and the Appender as the value.
096     */
097    public Map<String, Appender> asMap() {
098        final Map<String, Appender> result = new HashMap<>();
099        for (final AppenderControl appenderControl : appenderArray.get()) {
100            result.put(appenderControl.getAppenderName(), appenderControl.getAppender());
101        }
102        return result;
103    }
104
105    /**
106     * Atomically sets the values to an empty array and returns the old array.
107     *
108     * @return the contents before this collection was cleared.
109     */
110    public AppenderControl[] clear() {
111        return appenderArray.getAndSet(new AppenderControl[0]);
112    }
113
114    public boolean isEmpty() {
115        return appenderArray.get().length == 0;
116    }
117
118    /**
119     * Returns the underlying array.
120     *
121     * @return the array supporting this collection
122     */
123    public AppenderControl[] get() {
124        return appenderArray.get();
125    }
126
127    @Override
128    public String toString() {
129        return "AppenderControlArraySet [appenderArray=" + appenderArray + "]";
130    }
131}