content: package net.anotheria.moskito.core.context;
  
  import net.anotheria.moskito.core.producers.IStatsProducer;
  import net.anotheria.moskito.core.tag.TagRepository;
  import net.anotheria.moskito.core.tag.TagType;
  import net.anotheria.util.StringUtils;
  
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Stack;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.concurrent.atomic.AtomicLong;
  
  /**
   * MoSKito Context is a thread local class, that is used to contain some information about current execution, for example tags.
   *
   * @author lrosenberg
   * @since 22.05.17 01:36
   */
  public class MoSKitoContext {
  
  	/**
  	 * Instance counter.
  	 */
  	private static final AtomicLong instanceCounter = new AtomicLong();
  
  	private Stack<IStatsProducer> producerStack = new Stack<>();
  
  	private static final InheritableThreadLocal<MoSKitoContext> currentContext = new InheritableThreadLocal<MoSKitoContext>(){
  
  		@Override
  		protected MoSKitoContext childValue(MoSKitoContext parentValue) {
  			MoSKitoContext child = new MoSKitoContext();
  			child.reset();
  			child.tags.putAll(getTags());
  			return child;
  		}
  
  		@Override
  		protected MoSKitoContext initialValue() {
  			return new MoSKitoContext();
  		}
  	};
  
  
  	public MoSKitoContext(){
  		instanceNumber = instanceCounter.incrementAndGet();
  	}
  
  	public static MoSKitoContext get(){
  		return currentContext.get();
  	}
  
  	/**
  	 * Map with tags assigned to this context.
  	 */
  	private HashMap<String, String> tags = new HashMap<>();
  
  	private HashSet<Integer> seenErrors = new HashSet<>();
  
  	private volatile IStatsProducer lastProducer;
  
  	/**
  	 * First producer ever seen in this passing of the code. Usually its the first component/aspect/filter the code encounters on user's request in your app.
  	 */
  	private volatile IStatsProducer firstProducer;
  
  	/**
  	 * Number of current instance.
  	 */
  	private final long instanceNumber;
  
  	/**
  	 * If true an error has occured in this thread already. This is useful to separate from initial errors in the processing and followup errors.
  	 */
  	private AtomicBoolean errorOccured = new AtomicBoolean(false);
  
  	/**
  	 * If true, a tracer already reacted to this thread, don't activate any additional tracers.
  	 */
  	private boolean tracerFired;
  
  	public static void addTag(String tagName, String tagValue, TagType type, String source) {
  		if (StringUtils.isEmpty(tagName) || StringUtils.isEmpty(tagValue)) {
  			return;
  		}
  
  		// Adding tag to repo, used on Tags page
  		TagRepository.INSTANCE.addTag(tagName, tagValue, type, source);
  
  		// Setting newest tag value for current MoSKito context
  		get().tags.put(tagName, tagValue);
  	}
  
  	/**
  	 * Returns number of this instance, this can be used to distinguish calls. 
  	 * @return
  	 */
  	public long getInstanceNumber(){
  		return instanceNumber;
  	}
  
  
  	@SuppressWarnings("unchecked")
  	public static Map<String, String> getTags(){
  		return (Map<String, String>) get().tags.clone();
  	}
  
  	/**
  	 * Marks current context as erroneous (having one error occurred already) and returns the previous value. This method is threadsafe.
  	 * @return
  	 */
  	public boolean markErrorAndReturnIfErrorAlreadyHappenedBefore(){
  		return errorOccured.getAndSet(true);
  	}
  
  	/**
  	 * Resets the current context. Especially useful for unit-testing.
  	 */
  	public void reset(){
  		tags = new HashMap<>();
  		errorOccured = new AtomicBoolean(false);
  		seenErrors = new HashSet<>();
  		tracerFired = false;
  		producerStack = new Stack<>();
  		firstProducer = null;
  	}
  
  
  	public static void cleanup() {
  		get().reset();
  		currentContext.remove();
  	}
  
  	public boolean seenErrorAlready(Throwable throwable) {
  		Integer key = getHashKey(throwable);
  		if (seenErrors.contains(key))
  			return true;
  		seenErrors.add(key);
  		return false;
  	}
  
  	public boolean hasTracerFired(){
  		return tracerFired;
  	}
  
  	public void setTracerFired(){
  		tracerFired = true;
  	}
  
  	/* test visibility */ static Integer getHashKey(Throwable t){
  		return t.hashCode();
  	}
  
  	public IStatsProducer getLastProducer() {
  		return lastProducer;
  	}
  
  	public CurrentMeasurement notifyProducerEntry(IStatsProducer aProducer){
  		CurrentMeasurement measurement = new CurrentMeasurement(aProducer, firstProducer==null);
  		if (firstProducer==null) {
  			firstProducer = aProducer;
  		}
  		producerStack.push(lastProducer);
  		lastProducer = aProducer;
  		return measurement;
  	}
  
  	public void notifyProducerExit(IStatsProducer aProducer){
  		if (producerStack.size()==0)
  			return;
  		try{
  			lastProducer = producerStack.pop();
  		}catch(Exception any){
  			//we don't expect anything to happen here, but just in case, we at least log this out.
  			any.printStackTrace();
  		}
  	}
  }
  