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;
025
026import org.eclipse.aether.transfer.AbstractTransferListener;
027import org.eclipse.aether.transfer.TransferCancelledException;
028import org.eclipse.aether.transfer.TransferEvent;
029import org.eclipse.aether.transfer.TransferListener;
030
031import static java.util.Objects.requireNonNull;
032
033/**
034 * A transfer listener that delegates to zero or more other listeners (multicast). The list of target listeners is
035 * thread-safe, i.e. target listeners can be added or removed by any thread at any time.
036 */
037public final class ChainedTransferListener extends AbstractTransferListener {
038
039    private final List<TransferListener> listeners = new CopyOnWriteArrayList<>();
040
041    /**
042     * Creates a new multicast listener that delegates to the specified listeners. In contrast to the constructor, this
043     * factory method will avoid creating an actual chained listener if one of the specified readers is actually
044     * {@code null}.
045     *
046     * @param listener1 The first listener, may be {@code null}.
047     * @param listener2 The second listener, may be {@code null}.
048     * @return The chained listener or {@code null} if no listener was supplied.
049     */
050    public static TransferListener newInstance(TransferListener listener1, TransferListener listener2) {
051        if (listener1 == null) {
052            return listener2;
053        } else if (listener2 == null) {
054            return listener1;
055        }
056        return new ChainedTransferListener(listener1, listener2);
057    }
058
059    /**
060     * Creates a new multicast listener that delegates to the specified listeners.
061     *
062     * @param listeners The listeners to delegate to, may be {@code null} or empty.
063     */
064    public ChainedTransferListener(TransferListener... listeners) {
065        if (listeners != null) {
066            add(Arrays.asList(listeners));
067        }
068    }
069
070    /**
071     * Creates a new multicast listener that delegates to the specified listeners.
072     *
073     * @param listeners The listeners to delegate to, may be {@code null} or empty.
074     */
075    public ChainedTransferListener(Collection<? extends TransferListener> listeners) {
076        add(listeners);
077    }
078
079    /**
080     * Adds the specified listeners to the end of the multicast chain.
081     *
082     * @param listeners The listeners to add, may be {@code null} or empty.
083     */
084    public void add(Collection<? extends TransferListener> listeners) {
085        if (listeners != null) {
086            for (TransferListener listener : listeners) {
087                add(listener);
088            }
089        }
090    }
091
092    /**
093     * Adds the specified listener to the end of the multicast chain.
094     *
095     * @param listener The listener to add, may be {@code null}.
096     */
097    public void add(TransferListener listener) {
098        if (listener != null) {
099            listeners.add(listener);
100        }
101    }
102
103    /**
104     * Removes the specified listener from the multicast chain. Trying to remove a non-existing listener has no effect.
105     *
106     * @param listener The listener to remove, may be {@code null}.
107     */
108    public void remove(TransferListener listener) {
109        if (listener != null) {
110            listeners.remove(listener);
111        }
112    }
113
114    @SuppressWarnings("EmptyMethod")
115    protected void handleError(TransferEvent event, TransferListener listener, RuntimeException error) {
116        // default just swallows errors
117    }
118
119    @Override
120    public void transferInitiated(TransferEvent event) throws TransferCancelledException {
121        requireNonNull(event, "event cannot be null");
122        for (TransferListener listener : listeners) {
123            try {
124                listener.transferInitiated(event);
125            } catch (RuntimeException e) {
126                handleError(event, listener, e);
127            }
128        }
129    }
130
131    @Override
132    public void transferStarted(TransferEvent event) throws TransferCancelledException {
133        requireNonNull(event, "event cannot be null");
134        for (TransferListener listener : listeners) {
135            try {
136                listener.transferStarted(event);
137            } catch (RuntimeException e) {
138                handleError(event, listener, e);
139            }
140        }
141    }
142
143    @Override
144    public void transferProgressed(TransferEvent event) throws TransferCancelledException {
145        requireNonNull(event, "event cannot be null");
146        for (TransferListener listener : listeners) {
147            try {
148                listener.transferProgressed(event);
149            } catch (RuntimeException e) {
150                handleError(event, listener, e);
151            }
152        }
153    }
154
155    @Override
156    public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
157        requireNonNull(event, "event cannot be null");
158        for (TransferListener listener : listeners) {
159            try {
160                listener.transferCorrupted(event);
161            } catch (RuntimeException e) {
162                handleError(event, listener, e);
163            }
164        }
165    }
166
167    @Override
168    public void transferSucceeded(TransferEvent event) {
169        requireNonNull(event, "event cannot be null");
170        for (TransferListener listener : listeners) {
171            try {
172                listener.transferSucceeded(event);
173            } catch (RuntimeException e) {
174                handleError(event, listener, e);
175            }
176        }
177    }
178
179    @Override
180    public void transferFailed(TransferEvent event) {
181        requireNonNull(event, "event cannot be null");
182        for (TransferListener listener : listeners) {
183            try {
184                listener.transferFailed(event);
185            } catch (RuntimeException e) {
186                handleError(event, listener, e);
187            }
188        }
189    }
190}