package io.github.privacystreams.core; import io.github.privacystreams.utils.Logging; import org.greenrobot.eventbus.EventBus; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Stream is one of the essential classes used in PrivacyStreams. * Most personal data access/process operation in PrivacyStreams use Stream as the intermediate. * * A Stream is consist of one or multiple items. * The items are produced by PStreamProvider functions (like LocationUpdatesProvider, CallLogProvider, etc.), * transformed by PStreamTransformation functions (like filter, reorder, map, etc.), * and outputted by ItemsOperator functions (like print, toList, etc.). * * Stream producer functions (including PStreamProvider and PStreamTransformation) * should make sure the stream is not closed before writing items to it, using: * stream.isClosed() * Stream consumer functions (including PStreamTransformation and ItemsOperator) * should stop reading from Stream if the stream is ended. * If stream.read() returns a null, it means the stream is ended. */ public abstract class Stream { private final UQI uqi; transient volatile int receiverCount; private transient List<Item> streamItems; private transient final List<Function<? extends Stream, ?>> receivers; private transient final List<EventBus> eventBuses; private transient final List<Integer> numSents; Stream(UQI uqi) { this.uqi = uqi; this.receiverCount = 1; this.streamItems = new ArrayList<>(); this.receivers = new ArrayList<>(); this.eventBuses = new ArrayList<>(); this.numSents = new ArrayList<>(); } /** * Write an item to the stream, * or write a null to end the stream. * @param item the item to write to the stream, null indicates the end of the stream * @param streamProvider the function that provide current stream */ public void write(Item item, Function<?, ? extends Stream> streamProvider) { if (streamProvider != this.getStreamProvider() && streamProvider != this.getStreamProvider().getTail()) { Logging.warn("Illegal StreamProvider trying to write stream!"); return; } int numExistingItems = this.streamItems.size(); if (numExistingItems == 0 || this.streamItems.get(numExistingItems - 1) != Item.EOS) { this.streamItems.add(item); } this.syncItems(); } private synchronized void syncItems() { int numReceived = this.streamItems.size(); for (int i = 0; i < this.receivers.size(); i++) { int numSent = this.numSents.get(i); if (numSent < numReceived) { this.numSents.set(i, numReceived); EventBus eventBus = this.eventBuses.get(i); int currentReceiverCount = this.receiverCount; for (int itemId = numSent; itemId < numReceived; itemId++) { eventBus.post(streamItems.get(itemId)); if (currentReceiverCount != this.receiverCount) break; } } } } /** * Register a function to current stream. * @param streamReceiver the function that receives stream items */ public synchronized void register(Function<? extends Stream, ?> streamReceiver) { if (this.receivers.size() >= this.receiverCount) { Logging.warn("Unknown StreamProvider trying to subscribe to stream!"); return; } EventBus eventBus = new EventBus(); eventBus.register(streamReceiver); this.receivers.add(streamReceiver); this.eventBuses.add(eventBus); this.numSents.add(0); Stream.this.syncItems(); } /** * Unregister a function from current stream. * @param streamReceiver the function that receives stream items */ public synchronized void unregister(Function<? extends Stream, ?> streamReceiver) { if (!this.receivers.contains(streamReceiver)) return; int receiverId = this.receivers.indexOf(streamReceiver); this.eventBuses.get(receiverId).unregister(streamReceiver); this.receivers.remove(receiverId); this.eventBuses.remove(receiverId); this.numSents.remove(receiverId); this.receiverCount--; if (this.isClosed()) { this.getStreamProvider().cancel(this.uqi); this.streamItems.clear(); } } /** * Check whether the stream is closed, * Stream generator functions should make sure the stream is not closed this writing items to it. * @return true if the stream is closed, meaning the stream does not accept new items */ public boolean isClosed() { return this.receiverCount <= 0; } public abstract Function<Void, ? extends Stream> getStreamProvider(); public Map<String, Object> toMap() { Map<String, Object> outputMap = new HashMap<>(); outputMap.put("streamProvider", this.getStreamProvider().toString()); return outputMap; } public String toString() { return this.toMap().toString(); } public UQI getUQI() { return this.uqi; } public Stream reuse(int numOfReuses) { this.receiverCount = numOfReuses; return this; } }