/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A ReferenceCounter is used to reference counting objects. Each object is
 * identified by a unique ID. Together they form an ID - value pair. An object
 * is added to the counter by calling #put(id, object). From this point on
 * additional refs can be made by calling #addRef(id) or #removeRef(id).
 */
public class ReferenceCounter {
	private Map mapIdToRec = new HashMap(11);

	/**
	 * Capture the information about an object.
	 */
	public static class RefRec {
		public RefRec(Object id, Object value) {
			this.id = id;
			this.value = value;
			addRef();
		}

		public Object getId() {
			return id;
		}

		public Object getValue() {
			return value;
		}

		public int addRef() {
			++refCount;
			return refCount;
		}

		public int removeRef() {
			--refCount;
			return refCount;
		}

		public int getRef() {
			return refCount;
		}

		public boolean isNotReferenced() {
			return (refCount <= 0);
		}

		public Object id;

		public Object value;

		private int refCount;
	}

	/**
	 * Creates a new counter.
	 */
	public ReferenceCounter() {
		super();
	}

	/**
	 * Adds one reference to an object in the counter.
	 *
	 * @param id is a unique ID for the object.
	 * @return the new ref count
	 */
	public int addRef(Object id) {
		RefRec rec = (RefRec) mapIdToRec.get(id);
		if (rec == null) {
			return 0;
		}
		return rec.addRef();
	}

	/**
	 * Returns the object defined by an ID. If the ID is not found <code>null</code>
	 * is returned.
	 *
	 * @return the object or <code>null</code>
	 */
	public Object get(Object id) {
		RefRec rec = (RefRec) mapIdToRec.get(id);
		if (rec == null) {
			return null;
		}
		return rec.getValue();
	}

	/**
	 * Returns a complete list of the keys in the counter.
	 *
	 * @return a Set containing the ID for each.
	 */
	public Set keySet() {
		return mapIdToRec.keySet();
	}

	/**
	 * Adds an object to the counter for counting and gives it an initial ref count
	 * of 1.
	 *
	 * @param id    is a unique ID for the object.
	 * @param value is the object itself.
	 */
	public void put(Object id, Object value) {
		RefRec rec = new RefRec(id, value);
		mapIdToRec.put(id, rec);
	}

	/**
	 * @param id is a unique ID for the object.
	 * @return the current ref count
	 */
	public int getRef(Object id) {
		RefRec rec = (RefRec) mapIdToRec.get(id);
		if (rec == null) {
			return 0;
		}
		return rec.refCount;
	}

	/**
	 * Removes one reference from an object in the counter. If the ref count drops
	 * to 0 the object is removed from the counter completely.
	 *
	 * @param id is a unique ID for the object.
	 * @return the new ref count
	 */
	public int removeRef(Object id) {
		RefRec rec = (RefRec) mapIdToRec.get(id);
		if (rec == null) {
			return 0;
		}
		int newCount = rec.removeRef();
		if (newCount <= 0) {
			mapIdToRec.remove(id);
		}
		return newCount;
	}

	/**
	 * Returns a complete list of the values in the counter.
	 *
	 * @return a Collection containing the values.
	 */
	public List values() {
		int size = mapIdToRec.size();
		ArrayList list = new ArrayList(size);
		Iterator iter = mapIdToRec.values().iterator();
		while (iter.hasNext()) {
			RefRec rec = (RefRec) iter.next();
			list.add(rec.getValue());
		}
		return list;
	}
}
