/* Copyright (c) 2011 Danish Maritime Authority.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dk.dma.ais.packet;
import dk.dma.ais.message.AisMessage;
import dk.dma.commons.util.io.OutputStreamSink;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static java.util.Objects.requireNonNull;
/**
* A stream of packets.
*
* @author Kasper Nielsen
*/
public abstract class AisPacketStream {
/** Thrown by a subscriber to indicate that the subscription should be cancelled. */
public static final RuntimeException CANCEL = new RuntimeException();
/**
* Adds the specified packet to the stream
*
* @param packet
* the packet to add
* @throws UnsupportedOperationException
* if the stream is immutable
*/
public void add(AisPacket p) {
throw new UnsupportedOperationException("Stream is immutable");// default stream is immutable.
}
/**
* Returns a new stream that only streams packets accepted by the specified predicate.
*
* @param predicate
* the predicate to test against each element
* @return the new stream
*/
public abstract AisPacketStream filter(Predicate<? super AisPacket> predicate);
public AisPacketStream filter(String expression) {
return filter(AisPacketFilters.parseExpressionFilter(expression));
}
public AisPacketStream filterOnMessageType(int... messageTypes) {
return filter(AisPacketFilters.filterOnMessageType(messageTypes));
}
public final AisPacketStream immutableStream() {
return this instanceof ImmutableAisPacketStream ? this : new ImmutableAisPacketStream(this);
}
public AisPacketStream limit(long limit) {
if (limit < 1) {
throw new IllegalArgumentException("Limit must be at least 1, was: " + limit);
}
final AtomicLong l = new AtomicLong(limit);
return filter(new Predicate<AisPacket>() {
public boolean test(AisPacket element) {
if (l.getAndDecrement() <= 0) {
throw AisPacketStream.CANCEL;
}
return true;
}
});
}
public abstract Subscription subscribe(Consumer<AisPacket> c);
public Subscription subscribeMessages(final Consumer<AisMessage> c) {
requireNonNull(c);
if (c instanceof AisPacketStream.StreamConsumer) {
final AisPacketStream.StreamConsumer<AisMessage> sc = (StreamConsumer<AisMessage>) c;
return subscribe(new AisPacketStream.StreamConsumer<AisPacket>() {
public void accept(AisPacket t) {
AisMessage m = t.tryGetAisMessage();
if (m != null) {
c.accept(m);
}
}
public void begin() {
sc.begin();
}
public void end(Throwable cause) {
sc.end(cause);
}
});
} else {
return subscribe(new Consumer<AisPacket>() {
public void accept(AisPacket t) {
AisMessage m = t.tryGetAisMessage();
if (m != null) {
c.accept(m);
}
}
});
}
}
public Subscription subscribeSink(final OutputStreamSink<AisPacket> sink, final OutputStream os) {
requireNonNull(sink);
requireNonNull(os);
final AtomicLong l = new AtomicLong();
return subscribe(new StreamConsumer<AisPacket>() {
@Override
public void accept(AisPacket t) {
try {
sink.process(os, t, l.incrementAndGet());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void begin() {
try {
sink.header(os);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void end(Throwable cause) {
try {
sink.footer(os, l.get());
} catch (IOException e) {
if (cause == null) {// dont throw if already on exception path
throw new RuntimeException(e);
}
}
}
});
}
/**
* Returns a new stream.
*
* @return the new stream
*/
public static AisPacketStream newStream() {
return new AisPacketStreamImpl();
}
public abstract static class StreamConsumer<T> implements Consumer<T> {
/** Invoked immediately before the first message is delivered. */
public void begin() {}
/**
* Invoked immediately after the last message has been delivered.
*
* @param cause
* if an exception caused the consumer to be unsubscribed
*/
public void end(Throwable cause) {}
}
/** A subcription is created each time a new consumer is added to the stream. */
public interface Subscription {
void awaitCancelled() throws InterruptedException;
boolean awaitCancelled(long timeout, TimeUnit unit) throws InterruptedException;
/**
* Cancels this subscription. After this subscription has been cancelled. No more packets will be delivered to
* the consumer.
*/
void cancel();
/** Returns whether or not the subscription has been cancelled. */
boolean isCancelled();
}
static class DelegatingAisPacketStream extends AisPacketStream {
final AisPacketStream stream;
/**
* @param stream
*/
public DelegatingAisPacketStream(AisPacketStream stream) {
this.stream = requireNonNull(stream);
}
public void add(AisPacket p) {
stream.add(p);
}
public AisPacketStream filter(Predicate<? super AisPacket> predicate) {
return stream.filter(predicate);
}
public AisPacketStream filter(String expression) {
return stream.filter(expression);
}
public AisPacketStream filterOnMessageType(int... messageTypes) {
return stream.filterOnMessageType(messageTypes);
}
public AisPacketStream limit(long limit) {
return stream.limit(limit);
}
public Subscription subscribe(Consumer<AisPacket> c) {
return stream.subscribe(c);
}
public Subscription subscribeMessages(Consumer<AisMessage> c) {
return stream.subscribeMessages(c);
}
/** {@inheritDoc} */
@Override
public Subscription subscribeSink(OutputStreamSink<AisPacket> sink, OutputStream os) {
return stream.subscribeSink(sink, os);
}
}
static class ImmutableAisPacketStream extends DelegatingAisPacketStream {
/**
* @param stream
*/
public ImmutableAisPacketStream(AisPacketStream stream) {
super(stream);
}
/** {@inheritDoc} */
@Override
public void add(AisPacket p) {
throw new UnsupportedOperationException("Stream is immutable");
}
}
}