package com.github.triceo.splitlog;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import com.github.triceo.splitlog.api.Message;
import com.github.triceo.splitlog.api.TailSplitter;
import com.github.triceo.splitlog.splitters.SimpleTailSplitter;

final class MessageBuilder {

    private static final TailSplitter DEFAULT_TAIL_SPLITTER = new SimpleTailSplitter();
    private static final AtomicLong MESSAGE_ID_GENERATOR = new AtomicLong(0);
    private static final long NO_MESSAGE_ID_SET = -1;

    private long futureMessageId = MessageBuilder.NO_MESSAGE_ID_SET;
    private final List<String> lines = new LinkedList<String>();
    private Message previousMessage;
    private long timestamp;

    /**
     * Construct a message builder.
     *
     * @param firstLine
     *            Untreated, unprocessed first line of the new message retrieved
     *            from the log.
     */
    public MessageBuilder(final String firstLine) {
        if (firstLine == null) {
            throw new IllegalArgumentException("First line may not be null.");
        }
        this.timestamp = System.currentTimeMillis();
        this.lines.add(firstLine);
    }

    /**
     * Add a bunch of lines that will become part of the message when built.
     *
     * @param lines
     *            Add untreated, unprocessed lines retrieved from the log.
     * @return This.
     */
    public MessageBuilder add(final Collection<String> lines) {
        this.lines.addAll(lines);
        return this;
    }

    /**
     * Add another line that will become part of the message when built.
     *
     * @param line
     *            Add an untreated, unprocessed line retrieved from the log.
     * @return This.
     */
    public MessageBuilder add(final String line) {
        this.lines.add(line);
        return this;
    }

    public Message buildFinal() {
        return this.buildFinal(MessageBuilder.DEFAULT_TAIL_SPLITTER);
    }

    public synchronized Message buildFinal(final TailSplitter splitter) {
        if (this.futureMessageId == MessageBuilder.NO_MESSAGE_ID_SET) {
            // no ID acquired yet
            this.futureMessageId = MessageBuilder.MESSAGE_ID_GENERATOR.getAndIncrement();
        }
        final Message msg = new DefaultMessage(this.futureMessageId, this.getLines(), this.getTimestamp(), splitter,
                this.previousMessage);
        // next message will have to acquire new ID
        this.futureMessageId = MessageBuilder.NO_MESSAGE_ID_SET;
        return msg;
    }

    public Message buildIntermediate() {
        return this.buildIntermediate(MessageBuilder.DEFAULT_TAIL_SPLITTER);
    }

    public synchronized Message buildIntermediate(final TailSplitter splitter) {
        if (this.futureMessageId == MessageBuilder.NO_MESSAGE_ID_SET) {
            // no ID acquired yet
            this.futureMessageId = MessageBuilder.MESSAGE_ID_GENERATOR.getAndIncrement();
        }
        return new DefaultMessage(this.futureMessageId, this.getLines(), this.getTimestamp(), splitter,
                this.previousMessage);
    }

    public synchronized Message buildTag() {
        if (this.futureMessageId == MessageBuilder.NO_MESSAGE_ID_SET) {
            // no ID acquired yet
            this.futureMessageId = MessageBuilder.MESSAGE_ID_GENERATOR.getAndIncrement();
        }
        final Message msg = new DefaultMessage(this.futureMessageId, this.getFirstLine());
        // next message will have to acquire new ID
        this.futureMessageId = MessageBuilder.NO_MESSAGE_ID_SET;
        return msg;
    }

    /**
     *
     * @return First line from the server log, no pre-processing.
     */
    public String getFirstLine() {
        return this.getLines().get(0);
    }

    /**
     *
     * @return Raw lines from the server log that have had no pre-processing.
     */
    public List<String> getLines() {
        return this.lines;
    }

    public Message getPreviousMessage() {
        return this.previousMessage;
    }

    public long getTimestamp() {
        return this.timestamp;
    }

    public MessageBuilder setPreviousMessage(final Message previousMessage) {
        this.previousMessage = previousMessage;
        return this;
    }

    /**
     * Assign a timestamp to this message.
     *
     * @param timestamp
     *            Timestamp to assign to the message; number of millis since
     *            January 1st 1970.
     * @return This.
     */
    public MessageBuilder setTimestamp(final long timestamp) {
        this.timestamp = timestamp;
        return this;
    }

}

@@ -59,16 +59,46 @@
 ection;%0A
+import java.util.Collections;%0A
 import j
@@ -1236,30 +1236,24 @@
        this.
-lines.
 add(firstLin
@@ -1482,32 +1482,45 @@
   */%0A    public 
+synchronized 
 MessageBuilder a
@@ -1894,49 +1894,56 @@
     
-this.lines.add(line);%0A        return this
+return this.add(Collections.singletonList(line))
 ;%0A  
@@ -3780,21 +3780,22 @@
 */%0A    p
-ublic
+rivate
  String 
@@ -3831,26 +3831,21 @@
 rn this.
-getL
+l
 ines
-()
 .get(0);
@@ -3948,69 +3948,284 @@
 ing.
-%0A     */%0A    public List%3CString%3E getLines() %7B%0A        return 
+ This will create a new list every time it%0A     * is called, so that %7B@link #lines%7D can be modified independently of this new collection's iteration.%0A     */%0A    private synchronized List%3CString%3E getLines() %7B%0A        return Collections.unmodifiableList(new LinkedList%3CString%3E(
 this
@@ -4230,16 +4230,18 @@
 is.lines
+))
 ;%0A    %7D%0A

Fix a concurrency issue