All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.daisy.common.messaging.MessageImpl Maven / Gradle / Ivy

The newest version!
package org.daisy.common.messaging;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.function.Consumer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.MDC;

class MessageImpl extends ProgressMessage implements MessageAppender {

	final String ownerId;
	private final MessageImpl parent;
	private final BigDecimal portion;
	private final List> callbacks;
	private final Thread thread;
	private final MessageThread messageThread;
	private final List children = new ArrayList();
	private final List unmodifiableChildren = new ArrayList();
	private final List unmodifiableListOfUnmodifiableChildren = Collections.unmodifiableList(unmodifiableChildren);

	private boolean closed = false;
	private int closeSequence = 0;

	static final ThreadLocal activeBlock = new ThreadLocal<>();
	static final Map activeBlockInThread = new HashMap<>();

	public MessageImpl(Throwable throwable, String text, Level level, int line,
	                   int column, Date timeStamp, Integer sequence, String ownerId,
	                   String file, MessageImpl parent, BigDecimal portion,
	                   List> callbacks) {
		super(throwable, text, level, line, column, timeStamp, sequence, ownerId, file);
		this.ownerId = ownerId;
		this.parent = parent;
		this.portion = portion;
		this.callbacks = callbacks;
		thread = Thread.currentThread();
		if (parent != null && parent.closed)
			throw new IllegalArgumentException("Parent must be open");
		if (parent != null && !ownerId.equals(parent.ownerId))
			throw new IllegalArgumentException("Message must belong to same owner as parent");
		MessageImpl active = activeBlock.get();
		if (active != null && active != parent)
			throw new RuntimeException("Only one active ProgressMessage allowed in the same thread");
		activeBlock.set(this);
		if (parent == null) {
			MessageThread t = new MessageThread(ownerId, null);
			if (activeBlockInThread.containsKey(t))
				messageThread = new MessageThread(ownerId, this.sequence);
			else
				messageThread = t;
			MDC.put("message-thread", messageThread.toString());
		} else if (thread == parent.thread)
			messageThread = parent.messageThread;
		else {
			messageThread = new MessageThread(ownerId, this.sequence);
			MDC.put("message-thread", messageThread.toString());
		}
		activeBlockInThread.put(messageThread, this);
	}

	public synchronized void close() {
		if (closed)
			throw new UnsupportedOperationException("Already closed");
		if (this != activeBlock.get()) {
			for (MessageImpl m : children)
				if (!m.closed)
					throw new UnsupportedOperationException("All children blocks must be closed before the parent can be closed");
			if (thread != Thread.currentThread())
				throw new IllegalStateException("A ProgressMessage must be created and closed in the same thread");
			else
				throw new RuntimeException("???");
		}
		BigDecimal lastProgress = getProgress();
		closed = true;
		closeSequence = messageCounts.get(ownerId);
		if (parent != null && thread == parent.thread) {
			activeBlock.set(parent);
			activeBlockInThread.put(parent.messageThread, parent);
		} else {
			activeBlock.remove();
			MDC.remove("message-thread");
			activeBlockInThread.remove(messageThread);
		}
		// a notification needs to be sent only if closing the message changes its progress
		if (lastProgress.compareTo(BigDecimal.ONE) < 0)
			updated(closeSequence, false);
	}

	public synchronized MessageAppender append(MessageBuilder message) {
		if (closed)
			throw new UnsupportedOperationException("Closed");
		synchronized(MUTEX) {
			MessageImpl m = message.build(this);
			children.add(m);
			unmodifiableChildren.add(unmodifiable(m));
			// if the message has no text, it is irrelevant so we don't have to send any notifications yet
			// however we don't ignore it completely because it may receive child messages later
			// the child messages will trigger their parent's updated() method
			// for now we don't notify at all about text-less messages, not even postponed,
			// because in practice notifying about the relevant child messages will be enough
			if (m.getText() != null)
				updated(m.getSequence(), true);
			return m;
		}
	}

	boolean isOpen() {
		return !closed;
	}

	int getCloseSequence() {
		if (!closed)
			throw new UnsupportedOperationException("Not closed yet");
		else
			return closeSequence;
	}

	public String getOwnerId() {
		return ownerId;
	}

	public BigDecimal getPortion() {
		return portion;
	}

	private BigDecimal progress = BigDecimal.ZERO;

	public BigDecimal getProgress() {
		if (closed || portion.compareTo(BigDecimal.ZERO) == 0 || progress.compareTo(BigDecimal.ONE) >= 0)
			return BigDecimal.ONE;
		progress = BigDecimal.ZERO;
		synchronized(MUTEX) {
			for (MessageImpl m : children) {
				progress = progress.add(m.closed ? m.portion : m.portion.multiply(m.getProgress()));
				if (progress.compareTo(BigDecimal.ONE) >= 0)
					return BigDecimal.ONE;
			}
		}
		return progress;
	}

	void updated(int sequence, boolean textMessageAdded) {
		// either a descendant text message was added, or the progress has changed
		// if a text messages was added, we consider it relevant for all ancestors
		// if only the progress has changed, it is relevant for the parent only if the portion with the parent is non-zero
		if (callbacks != null && !callbacks.isEmpty()) {
			for (Consumer callback : callbacks)
				callback.accept(sequence); }
		if (parent != null)
			if (textMessageAdded || portion.compareTo(BigDecimal.ZERO) > 0)
				parent.updated(sequence, textMessageAdded);
	}

	/**
	 * Returns read-only (but not immutable) elements. The iterator does not implement the remove()
	 * method.
	 */
	public Iterator __iterator() {
		return unmodifiableListOfUnmodifiableChildren.iterator();
	}

	static class MessageThread {
		final String uid;
		MessageThread(String ownerId, Integer sequence) {
			this.uid = ownerId + "-" + (sequence == null ? "default" : ("thread-" + sequence));
		}
		MessageThread(String uid) {
			this.uid = uid;
		}
		@Override
		public String toString() {
			return uid;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + uid.hashCode();
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			MessageThread other = (MessageThread)obj;
			return uid.equals(other.uid);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy