/*******************************************************************************
 * Copyright (c) 2007 Wind River Systems, Inc. and others.
 * 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: 
 * Martin Oberhuber (Wind River) - initial API and implementation for [105554] 
 *******************************************************************************/

package org.eclipse.core.internal.localstore;

import java.util.Arrays;

/**
 * A pool of Strings for doing prefix checks against multiple
 * candidates.
 * <p>
 * Allows to enter a list of Strings, and then perform the
 * following checks:
 * <ul>
 *   <li>{@link #containsAsPrefix(String)} - check whether a given
 *       String s is a prefix of any String in the pool.</li>
 *   <li>{@link #hasPrefixOf(String)} - check whether any String
 *       in the pool is a prefix of the given String s. 
 * </ul>
 * The prefix pool is always kept normalized, i.e. no element of
 * the pool is a prefix of any other element in the pool. In order
 * to maintain this constraint, there are two methods for adding
 * Strings to the pool:
 * <ul>
 *   <li>{@link #insertLonger(String)} - add a String s to the pool,
 *       and remove any existing prefix of s from the pool.</li>
 *   <li>{@link #insertShorter(String)} - add a String s to the pool,
 *       and remove any existing Strings sx from the pool which 
 *       contain s as prefix.</li>
 * </ul>
 * The PrefixPool grows as needed when adding Strings. Typically,
 * it is used for prefix checks on absolute paths of a tree.
 * </p><p> 
 * This class is not thread-safe: no two threads may add or
 * check items at the same time.
 * 
 * @since 3.3
 */
public class PrefixPool {
	private String[] pool;
	private int size;

	/**
	 * Constructor.
	 * @param initialCapacity the initial size of the 
	 *     internal array holding the String pool. Must
	 *     be greater than 0.
	 */
	public PrefixPool(int initialCapacity) {
		if (initialCapacity <= 0)
			throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); //$NON-NLS-1$
		pool = new String[initialCapacity];
		size = 0;
	}

	/**
	 * Clears the prefix pool, allowing all items to be
	 * garbage collected. Does not change the capacity
	 * of the pool.
	 */
	public/*synchronized*/void clear() {
		Arrays.fill(pool, 0, size, null);
		size = 0;
	}

	/**
	 * Return the current size of prefix pool.
	 * @return the number of elements in the pool. 
	 */
	public/*synchronized*/int size() {
		return size;
	}

	/**
	 * Ensure that there is room for at least one more element.
	 */
	private void checkCapacity() {
		if (size + 1 >= pool.length) {
			String[] newprefixList = new String[2 * pool.length];
			System.arraycopy(pool, 0, newprefixList, 0, pool.length);
			Arrays.fill(pool, null); //help the garbage collector
			pool = newprefixList;
		}
	}

	/**
	 * Insert a String s into the pool of known prefixes, removing
	 * any existing prefix of it.
	 * <p>
	 * If any existing prefix of this String is found in the pool,
	 * it is replaced by the new longer one in order to maintain
	 * the constraint of keeping the pool normalized.
	 * </p><p>
	 * If it turns out that s is actually a prefix or equal to
	 * an existing element in the pool (so it is essentially
	 * shorter), this method returns with no operation in order
	 * to maintain the constraint that the pool remains normalized.
	 * </p>
	 * @param s the String to insert.
	 */
	public/*synchronized*/void insertLonger(String s) {
		//check in reverse order since we expect some locality
		for (int i = size - 1; i >= 0; i--) {
			if (pool[i].startsWith(s)) {
				//prefix of an existing String --> no-op
				return;
			} else if (s.startsWith(pool[i])) {
				//replace, since a longer s has more prefixes than a short one
				pool[i] = s;
				return;
			}
		}
		checkCapacity();
		pool[size] = s;
		size++;
	}

	/**
	 * Insert a String s into the pool of known prefixes, removing
	 * any Strings that have s as prefix.
	 * <p>
	 * If this String is a prefix of any existing String in the pool,
	 * all elements that contain the new String as prefix are removed
	 * and return value <code>true</code> is returned.
	 * </p><p>
	 * Otherwise, the new String is added to the pool unless an 
	 * equal String or e prefix of it exists there already (so
	 * it is essentially equal or longer than an existing prefix).
	 * In all these cases, <code>false</code> is returned since
	 * no prefixes were replaced.
	 * </p>
	 * @param s the String to insert.
	 * @return <code>true</code>if any longer elements have been
	 *     removed.
	 */
	public/*synchronized*/boolean insertShorter(String s) {
		boolean replaced = false;
		//check in reverse order since we expect some locality
		for (int i = size - 1; i >= 0; i--) {
			if (s.startsWith(pool[i])) {
				//longer or equal to an existing prefix - nothing to do
				return false;
			} else if (pool[i].startsWith(s)) {
				if (replaced) {
					//replaced before, so shrink the array.
					//Safe since we are iterating in reverse order.
					System.arraycopy(pool, i + 1, pool, i, size - i - 1);
					size--;
					pool[size] = null;
				} else {
					//replace, since this is a shorter s
					pool[i] = s;
					replaced = true;
				}
			}
		}
		if (!replaced) {
			//append at the end
			checkCapacity();
			pool[size] = s;
			size++;
		}
		return replaced;
	}

	/**
	 * Check if the given String s is a prefix of any of Strings
	 * in the pool.
	 * @param s a s to check for being a prefix
	 * @return <code>true</code> if the passed s is a prefix
	 *     of any of the Strings contained in the pool.
	 */
	public/*synchronized*/boolean containsAsPrefix(String s) {
		//check in reverse order since we expect some locality
		for (int i = size - 1; i >= 0; i--) {
			if (pool[i].startsWith(s)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Test if the String pool contains any one that is a prefix
	 * of the given String s. 
	 * @param s the String to test
	 * @return <code>true</code> if the String pool contains a
	 *     prefix of the given String.
	 */
	public/*synchronized*/boolean hasPrefixOf(String s) {
		for (int i = size - 1; i >= 0; i--) {
			if (s.startsWith(pool[i])) {
				return true;
			}
		}
		return false;
	}

}
