/**
 * Copyright (c) 2017 NumberFour AG.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   NumberFour AG - Initial API and implementation
 */
package org.eclipse.n4js.smith;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import com.google.common.base.Strings;

/**
 * Singleton managing {@link DataCollector} instances. Should be used for creating and obtaining instances of the
 * collectors. Data collectors obtained by other means will not be reachable by this class APIs.
 * <p>
 * Using injector causes issues in the SharedContributions, i.e. N4JS*ClusteringBuilderState gets different instance
 * than the rest of the system.
 */
// TODO replace with Singleton once injection in shared ui contribution is clarified
public enum DataCollectors {
	/** The instance. */
	INSTANCE;

	private final Map<String, DataCollector> collectors = new HashMap<>();
	private final AtomicBoolean pauseAllCollectors = new AtomicBoolean(true);

	/**
	 * returns existing collector for the provided key. If there is no collector corresponding to the key creates new
	 * one and returns it. Any further call with the same key will return previously created instance.
	 */
	public DataCollector getOrCreateDataCollector(String key) {
		if (Strings.isNullOrEmpty(key)) {
			throw new RuntimeException("DataCollector key cannot be null or empty");
		}

		return get(key, null);
	}

	/**
	 * returns collector for a given key, that is child of a parent collector specified by the provided keys. Parent are
	 * looked up according to the order of the provided keys. Parents are <b>NOT</b> created. If parent with given key
	 * cannot be located exception is thrown. If no keys are provided returns behaves as
	 * {@link #getOrCreateDataCollector(String)}
	 */
	public DataCollector getOrCreateDataCollector(String key, String... parentKeys) {

		if (Strings.isNullOrEmpty(key)) {
			throw new RuntimeException("DataCollector key cannot be null or empty");
		}

		DataCollector parent = null;
		if (parentKeys != null) {
			String parentKey = parentKeys[0];
			String prevParentKey = parentKey;
			parent = collectors.get(parentKey);
			if (parent == null)
				throw new RuntimeException("Cannot find parent for key " + parentKey);
			// we have root parent, iterate over its children
			for (int i = 1; i < parentKeys.length; i++) {
				parentKey = parentKeys[i];
				parent = parent.getChild(parentKey);
				// since we have parent keys we break at key with no mapping to parent
				if (parent == null)
					throw new RuntimeException(
							"Cannot find collector for key " + parentKey + " and parent " + prevParentKey);
				prevParentKey = parentKey;
			}
		}

		return get(key, parent);
	}

	/**
	 * returns collector for a given key, that is child of the provided parent collector. If no keys are provided
	 * returns behaves as {@link #getOrCreateDataCollector(String)}
	 */
	public DataCollector getOrCreateDataCollector(String key, DataCollector parent) {

		if (Strings.isNullOrEmpty(key)) {
			throw new RuntimeException("DataCollector key cannot be null or empty");
		}

		return get(key, parent);
	}

	private DataCollector get(String key, DataCollector parent) {

		DataCollector collector = null;
		if (parent == null) {
			collector = collectors.get(key);
			if (collector == null) {
				collector = new TimedDataCollector();
				collector.setPaused(this.pauseAllCollectors.get());
				collectors.put(key, collector);
			}
		} else {
			collector = parent.getChild(key);
			if (collector == null) {
				collector = new TimedDataCollector();
				collector.setPaused(this.pauseAllCollectors.get());
				parent.addChild(key, collector);
			}
		}
		return collector;
	}

	// package
	/** Returns mapping between all top level collectors and their names. */
	Map<String, DataCollector> getRootCollectors() {
		return Collections.unmodifiableMap(collectors);
	}

	/** sets {@code paused} state for all data collectors. */
	void setPaused(boolean paused) {
		this.pauseAllCollectors.set(paused);
		collectors.values().forEach(collector -> collector.setPaused(paused));
	}

}

@@ -2238,16 +2238,583 @@
 );%0A%09%09%7D%0A%0A
+%09%09DataCollector parent = getParent(parentKeys);%0A%09%09return get(key, parent);%0A%09%7D%0A%0A%09/**%0A%09 * returns collector for a given key, that is child of the provided parent collector. If no keys are provided%0A%09 * returns behaves as %7B@link #getOrCreateDataCollector(String)%7D%0A%09 */%0A%09public DataCollector getOrCreateDataCollector(String key, DataCollector parent) %7B%0A%0A%09%09if (Strings.isNullOrEmpty(key)) %7B%0A%09%09%09throw new RuntimeException(%22DataCollector key cannot be null or empty%22);%0A%09%09%7D%0A%0A%09%09return get(key, parent);%0A%09%7D%0A%0A%09private synchronized DataCollector getParent(String... parentKeys) %7B%0A
 %09%09DataCo
@@ -3504,33 +3504,32 @@
 y;%0A%09%09%09%7D%0A%09%09%7D%0A
-%0A
 %09%09return
  get(key, pa
@@ -3520,457 +3520,42 @@
 turn
- get(key,
  parent
-)
 ;%0A%09%7D%0A%0A%09
-/**%0A%09 * returns collector for a given key, that is child of the provided parent collector. If no keys are provided%0A%09 * returns behaves as %7B@link #getOrCreateDataCollector(String)%7D%0A%09 */%0A%09public DataCollector getOrCreateDataCollector(String key, DataCollector parent) %7B%0A%0A%09%09if (Strings.isNullOrEmpty(key)) %7B%0A%09%09%09throw new RuntimeException(%22DataCollector key cannot be null or empty%22);%0A%09%09%7D%0A%0A%09%09return get(key, parent);%0A%09%7D%0A%0A%09private
+private synchronized
  Dat
@@ -3605,17 +3605,16 @@
 rent) %7B%0A
-%0A
 %09%09DataCo
@@ -3631,32 +3631,33 @@
 llector = null;%0A
+%0A
 %09%09if (parent == 
@@ -4079,32 +4079,33 @@
 ctor);%0A%09%09%09%7D%0A%09%09%7D%0A
+%0A
 %09%09return collect
@@ -4362,16 +4362,29 @@
 rs. */%0A%09
+synchronized 
 void set

add locking to data collectors (#756)