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 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    /**
115     * Invoked when any listener throws, by default is no op, extend if required.
116     */
117    @SuppressWarnings("EmptyMethod")
118    protected void handleError(TransferEvent event, TransferListener listener, RuntimeException error) {
119        // default just swallows errors
120    }
121
122    @Override
123    public void transferInitiated(TransferEvent event) throws TransferCancelledException {
124        requireNonNull(event, "event cannot be null");
125        for (TransferListener listener : listeners) {
126            try {
127                listener.transferInitiated(event);
128            } catch (RuntimeException e) {
129                handleError(event, listener, e);
130            }
131        }
132    }
133
134    @Override
135    public void transferStarted(TransferEvent event) throws TransferCancelledException {
136        requireNonNull(event, "event cannot be null");
137        for (TransferListener listener : listeners) {
138            try {
139                listener.transferStarted(event);
140            } catch (RuntimeException e) {
141                handleError(event, listener, e);
142            }
143        }
144    }
145
146    @Override
147    public void transferProgressed(TransferEvent event) throws TransferCancelledException {
148        requireNonNull(event, "event cannot be null");
149        for (TransferListener listener : listeners) {
150            try {
151                listener.transferProgressed(event);
152            } catch (RuntimeException e) {
153                handleError(event, listener, e);
154            }
155        }
156    }
157
158    @Override
159    public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
160        requireNonNull(event, "event cannot be null");
161        for (TransferListener listener : listeners) {
162            try {
163                listener.transferCorrupted(event);
164            } catch (RuntimeException e) {
165                handleError(event, listener, e);
166            }
167        }
168    }
169
170    @Override
171    public void transferSucceeded(TransferEvent event) {
172        requireNonNull(event, "event cannot be null");
173        for (TransferListener listener : listeners) {
174            try {
175                listener.transferSucceeded(event);
176            } catch (RuntimeException e) {
177                handleError(event, listener, e);
178            }
179        }
180    }
181
182    @Override
183    public void transferFailed(TransferEvent event) {
184        requireNonNull(event, "event cannot be null");
185        for (TransferListener listener : listeners) {
186            try {
187                listener.transferFailed(event);
188            } catch (RuntimeException e) {
189                handleError(event, listener, e);
190            }
191        }
192    }
193}