package com.github.triceo.splitlog;
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
import it.unimi.dsi.fastutil.objects.ObjectSortedSet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import com.github.triceo.splitlog.api.Follower;
import com.github.triceo.splitlog.api.LogWatch;
import com.github.triceo.splitlog.api.MergingFollower;
import com.github.triceo.splitlog.api.Message;
import com.github.triceo.splitlog.api.MessageComparator;
import com.github.triceo.splitlog.api.MessageDeliveryStatus;
import com.github.triceo.splitlog.api.MessageFormatter;
import com.github.triceo.splitlog.api.MessageMeasure;
import com.github.triceo.splitlog.api.SimpleMessageCondition;
import com.github.triceo.splitlog.formatters.NoopMessageFormatter;
import com.github.triceo.splitlog.logging.SplitlogLoggerFactory;
import com.github.triceo.splitlog.util.LogUtil;
import com.github.triceo.splitlog.util.LogUtil.Level;
/**
* This is a log follower that holds no message data, just the tags. For message
* data, it will always turn to the underlying {@link LogWatch}.
*
* This class assumes that LogWatch and user code are the only two threads that
* use it. Never use one instance of this class from two or more user threads.
* Otherwise, unpredictable behavior from waitFor() methods is possible.
*
* Will use {@link NoopMessageFormatter} as default message formatter.
*/
final class DefaultFollower extends AbstractCommonFollower<Follower, LogWatch> implements Follower {
private static final Logger LOGGER = SplitlogLoggerFactory.getLogger(DefaultFollower.class);
private final ConsumerManager<Follower> consumers = new ConsumerManager<>(this);
private final AtomicBoolean isStopped = new AtomicBoolean(false);
private final Collection<Message> tags = new TreeSet<>();
private final DefaultLogWatch watch;
public DefaultFollower(final DefaultLogWatch watch,
final Iterable<Pair<String, MessageMeasure<? extends Number, Follower>>> measuresHandedDown) {
for (final Pair<String, MessageMeasure<? extends Number, Follower>> pair : measuresHandedDown) {
this.startMeasuring(pair.getValue(), pair.getKey());
}
this.watch = watch;
}
@Override
protected ConsumerManager<Follower> getConsumerManager() {
return this.consumers;
}
@Override
protected MessageFormatter getDefaultFormatter() {
return NoopMessageFormatter.INSTANCE;
}
@Override
public LogWatch getFollowed() {
return this.getWatch();
}
@Override
public SortedSet<Message> getMessages(final SimpleMessageCondition condition,
final MessageComparator order) {
final SortedSet<Message> messages = new ObjectRBTreeSet<>(order);
for (final Message msg : this.getWatch().getAllMessages(this)) {
if (!condition.accept(msg)) {
continue;
}
messages.add(msg);
}
messages.addAll(this.tags);
return Collections.unmodifiableSortedSet(messages);
}
protected DefaultLogWatch getWatch() {
return this.watch;
}
@Override
public synchronized boolean isStopped() {
if (this.isStopped.get()) {
return true;
} else if (this.getFollowed().isFollowedBy(this)) {
return false;
} else {
this.isStopped.set(true);
return true;
}
}
@Override
public MergingFollower mergeWith(final Follower f) {
if (f == null) {
throw new IllegalArgumentException("Cannot merge with null.");
} else if (f == this) {
throw new IllegalArgumentException("Cannot merge with self.");
}
return new DefaultMergingFollower(this, f);
}
@Override
public MergingFollower mergeWith(final MergingFollower f) {
if (f == null) {
throw new IllegalArgumentException("Cannot merge with null.");
}
final Set<Follower> followers = new HashSet<>(f.getMerged());
followers.add(this);
return new DefaultMergingFollower(followers.toArray(new Follower[followers.size()]));
}
@Override
public void messageReceived(final Message msg, final MessageDeliveryStatus status,
final LogWatch source) {
if (this.isStopped()) {
throw new IllegalStateException("Follower already stopped.");
} else if (source != this.getWatch()) {
throw new IllegalArgumentException("Forbidden notification source: " + source);
}
LogUtil.newMessage(DefaultFollower.LOGGER, Level.INFO, "New message received:", msg, status, source, this);
this.getExpectationManager().messageReceived(msg, status, source);
this.getConsumerManager().messageReceived(msg, status, this);
}
@Override
public boolean stop() {
if (this.isStopped()) {
return false;
}
DefaultFollower.LOGGER.info("Stopping {}.", this);
this.getFollowed().stopFollowing(this);
this.getConsumerManager().stop();
this.getExpectationManager().stop();
DefaultFollower.LOGGER.info("Stopped {}.", this);
return true;
}
@Override
public Message tag(final String tagLine) {
final Message message = new MessageBuilder(tagLine).buildTag();
this.tags.add(message);
return message;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("DefaultFollower [getUniqueId()=").append(this.getUniqueId()).append(", ");
if (this.getFollowed() != null) {
builder.append("getFollowed()=").append(this.getFollowed()).append(", ");
}
builder.append("isStopped()=").append(this.isStopped()).append("]");
return builder.toString();
}
@Override
public boolean write(final OutputStream stream, final SimpleMessageCondition condition,
final MessageComparator order, final MessageFormatter formatter) {
if (stream == null) {
throw new IllegalArgumentException("Stream may not be null.");
} else if (condition == null) {
throw new IllegalArgumentException("Condition may not be null.");
} else if (order == null) {
throw new IllegalArgumentException("Comparator may not be null.");
}
BufferedWriter w = null;
try {
w = new BufferedWriter(new OutputStreamWriter(stream, "UTF-8"));
for (final Message msg : this.getMessages(condition, order)) {
w.write(formatter.format(msg, this.getFollowed().getWatchedFile()));
w.newLine();
}
return true;
} catch (final IOException ex) {
return false;
} finally {
IOUtils.closeQuietly(w);
}
}
}