001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.listener;
020
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.List;
024import java.util.concurrent.CopyOnWriteArrayList;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import org.eclipse.aether.transfer.AbstractTransferListener;
029import org.eclipse.aether.transfer.TransferCancelledException;
030import org.eclipse.aether.transfer.TransferEvent;
031import org.eclipse.aether.transfer.TransferListener;
032
033import static java.util.Objects.requireNonNull;
034
035/**
036 * A transfer listener that delegates to zero or more other listeners (multicast). The list of target listeners is
037 * thread-safe, i.e. target listeners can be added or removed by any thread at any time.
038 */
039public class ChainedTransferListener extends AbstractTransferListener {
040
041    private final List<TransferListener> listeners = new CopyOnWriteArrayList<>();
042
043    /**
044     * Creates a new multicast listener that delegates to the specified listeners. In contrast to the constructor, this
045     * factory method will avoid creating an actual chained listener if one of the specified readers is actually
046     * {@code null}.
047     *
048     * @param listener1 the first listener, may be {@code null}
049     * @param listener2 the second listener, may be {@code null}
050     * @return the chained listener or {@code null} if no listener was supplied
051     */
052    public static TransferListener newInstance(TransferListener listener1, TransferListener listener2) {
053        if (listener1 == null) {
054            return listener2;
055        } else if (listener2 == null) {
056            return listener1;
057        }
058        return new ChainedTransferListener(listener1, listener2);
059    }
060
061    /**
062     * Creates a new multicast listener that delegates to the specified listeners.
063     *
064     * @param listeners the listeners to delegate to, may be {@code null} or empty
065     */
066    public ChainedTransferListener(TransferListener... listeners) {
067        if (listeners != null) {
068            add(Arrays.asList(listeners));
069        }
070    }
071
072    /**
073     * Creates a new multicast listener that delegates to the specified listeners.
074     *
075     * @param listeners the listeners to delegate to, may be {@code null} or empty
076     */
077    public ChainedTransferListener(Collection<? extends TransferListener> listeners) {
078        add(listeners);
079    }
080
081    /**
082     * Adds the specified listeners to the end of the multicast chain.
083     *
084     * @param listeners the listeners to add, may be {@code null} or empty
085     */
086    public void add(Collection<? extends TransferListener> listeners) {
087        if (listeners != null) {
088            for (TransferListener listener : listeners) {
089                add(listener);
090            }
091        }
092    }
093
094    /**
095     * Adds the specified listener to the end of the multicast chain.
096     *
097     * @param listener the listener to add, may be {@code null}
098     */
099    public void add(TransferListener listener) {
100        if (listener != null) {
101            listeners.add(listener);
102        }
103    }
104
105    /**
106     * Removes the specified listener from the multicast chain. Trying to remove a non-existing listener has no effect.
107     *
108     * @param listener the listener to remove, may be {@code null}
109     */
110    public void remove(TransferListener listener) {
111        if (listener != null) {
112            listeners.remove(listener);
113        }
114    }
115
116    private static final Logger LOGGER = Logger.getLogger(ChainedTransferListener.class.getName());
117
118    /**
119     * Invoked when any listener throws, by default logs a warning, extend if required.
120     */
121    protected void handleError(TransferEvent event, TransferListener listener, RuntimeException error) {
122        LOGGER.log(Level.WARNING, "Exception in transfer listener " + listener, error);
123    }
124
125    @Override
126    public void transferInitiated(TransferEvent event) throws TransferCancelledException {
127        requireNonNull(event, "event cannot be null");
128        for (TransferListener listener : listeners) {
129            try {
130                listener.transferInitiated(event);
131            } catch (RuntimeException e) {
132                handleError(event, listener, e);
133            }
134        }
135    }
136
137    @Override
138    public void transferStarted(TransferEvent event) throws TransferCancelledException {
139        requireNonNull(event, "event cannot be null");
140        for (TransferListener listener : listeners) {
141            try {
142                listener.transferStarted(event);
143            } catch (RuntimeException e) {
144                handleError(event, listener, e);
145            }
146        }
147    }
148
149    @Override
150    public void transferProgressed(TransferEvent event) throws TransferCancelledException {
151        requireNonNull(event, "event cannot be null");
152        for (TransferListener listener : listeners) {
153            try {
154                listener.transferProgressed(event);
155            } catch (RuntimeException e) {
156                handleError(event, listener, e);
157            }
158        }
159    }
160
161    @Override
162    public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
163        requireNonNull(event, "event cannot be null");
164        for (TransferListener listener : listeners) {
165            try {
166                listener.transferCorrupted(event);
167            } catch (RuntimeException e) {
168                handleError(event, listener, e);
169            }
170        }
171    }
172
173    @Override
174    public void transferSucceeded(TransferEvent event) {
175        requireNonNull(event, "event cannot be null");
176        for (TransferListener listener : listeners) {
177            try {
178                listener.transferSucceeded(event);
179            } catch (RuntimeException e) {
180                handleError(event, listener, e);
181            }
182        }
183    }
184
185    @Override
186    public void transferFailed(TransferEvent event) {
187        requireNonNull(event, "event cannot be null");
188        for (TransferListener listener : listeners) {
189            try {
190                listener.transferFailed(event);
191            } catch (RuntimeException e) {
192                handleError(event, listener, e);
193            }
194        }
195    }
196}