GuardRegistry.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer.
 *
 * 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:
 *     Carsten Hammer - initial API and implementation
 *******************************************************************************/
package org.sandbox.jdt.triggerpattern.internal;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.Bundle;
import org.sandbox.jdt.triggerpattern.api.GuardFunction;

/**
 * Registry for guard functions with built-in guards.
 * 
 * <p>The registry is a singleton that manages guard functions by name. It comes
 * pre-loaded with built-in guards for common checks such as type testing,
 * modifier inspection, source version comparison, and annotation checking.</p>
 * 
 * <h2>Built-in Guards</h2>
 * <table>
 *   <caption>Available built-in guard functions</caption>
 *   <tr><th>Name</th><th>Description</th></tr>
 *   <tr><td>{@code instanceof}</td><td>Checks if a binding's type matches a given type name</td></tr>
 *   <tr><td>{@code matchesAny}</td><td>Returns {@code true} if a placeholder's text matches any of the given literals, or if bound and no literals given</td></tr>
 *   <tr><td>{@code matchesNone}</td><td>Returns {@code true} if a placeholder's text matches none of the given literals, or if unbound and no literals given</td></tr>
 *   <tr><td>{@code referencedIn}</td><td>Checks if a variable is referenced within another expression</td></tr>
 *   <tr><td>{@code elementKindMatches}</td><td>Checks if a binding is of a specific element kind (FIELD, METHOD, LOCAL_VARIABLE, PARAMETER, TYPE)</td></tr>
 *   <tr><td>{@code hasNoSideEffect}</td><td>Checks if an expression has no side effects</td></tr>
 *   <tr><td>{@code sourceVersionGE}</td><td>Checks if the source version is greater than or equal to a given version</td></tr>
 *   <tr><td>{@code sourceVersionLE}</td><td>Checks if the source version is less than or equal to a given version</td></tr>
 *   <tr><td>{@code sourceVersionBetween}</td><td>Checks if the source version is within a given range</td></tr>
 *   <tr><td>{@code isStatic}</td><td>Checks if a binding has the static modifier</td></tr>
 *   <tr><td>{@code isFinal}</td><td>Checks if a binding has the final modifier</td></tr>
 *   <tr><td>{@code hasAnnotation}</td><td>Checks if a binding has a specific annotation</td></tr>
 *   <tr><td>{@code isDeprecated}</td><td>Checks if a binding is deprecated</td></tr>
 *   <tr><td>{@code contains}</td><td>Checks if a text pattern occurs in the enclosing method body</td></tr>
 *   <tr><td>{@code notContains}</td><td>Checks if a text pattern does NOT occur in the enclosing method body</td></tr>
 *   <tr><td>{@code isParameter}</td><td>Checks if a binding is a method parameter</td></tr>
 *   <tr><td>{@code isField}</td><td>Checks if a binding is a field (instance or static)</td></tr>
 *   <tr><td>{@code isInConstructor}</td><td>Checks if the matched node is inside a constructor</td></tr>
 *   <tr><td>{@code classOverrides}</td><td>Checks if the enclosing class declares a method with the given name</td></tr>
 * </table>
 * 
 * @since 1.3.2
 */
public final class GuardRegistry {
	
	private static final String GUARDS_EXTENSION_POINT_ID = "org.sandbox.jdt.triggerpattern.guards"; //$NON-NLS-1$
	
	private static final GuardRegistry INSTANCE = new GuardRegistry();
	
	private final Map<String, GuardFunction> guards = new ConcurrentHashMap<>();
	
	private GuardRegistry() {
		BuiltInGuards.registerAll(guards);
		// Register this registry as the resolver for GuardExpression evaluation
		org.sandbox.jdt.triggerpattern.api.GuardFunctionResolverHolder.setResolver(this::get);
	}
	
	/**
	 * Returns the singleton instance.
	 * 
	 * @return the guard registry instance
	 */
	public static GuardRegistry getInstance() {
		return INSTANCE;
	}
	
	/**
	 * Registers a guard function with the given name.
	 * 
	 * @param name the guard function name
	 * @param fn the guard function
	 */
	public void register(String name, GuardFunction fn) {
		guards.put(name, fn);
	}
	
	/**
	 * Removes a guard function by name.
	 *
	 * @param name the guard function name to remove
	 * @since 1.5.0
	 */
	public void unregister(String name) {
		guards.remove(name);
	}
	
	/**
	 * Returns the guard function registered under the given name.
	 * 
	 * @param name the guard function name
	 * @return the guard function, or {@code null} if not found
	 */
	public GuardFunction get(String name) {
		return guards.get(name);
	}
	
	/**
	 * Returns the names of all registered guard functions.
	 * 
	 * @return unmodifiable set of guard function names
	 * @since 1.3.6
	 */
	public Set<String> getRegisteredNames() {
		return java.util.Collections.unmodifiableSet(guards.keySet());
	}
	
	/**
	 * Loads custom guard functions from the Eclipse extension registry.
	 * 
	 * <p>This method queries the {@code org.sandbox.jdt.triggerpattern.guards}
	 * extension point for contributed guard function implementations. Each
	 * contribution specifies a name and a class implementing
	 * {@link GuardFunction}.</p>
	 * 
	 * <p>Guards loaded from extensions are registered in addition to the
	 * built-in guards. If a contributed guard has the same name as a built-in
	 * guard, it overrides the built-in.</p>
	 * 
	 * @return list of successfully loaded guard names
	 * @since 1.3.6
	 */
	public List<String> loadExtensions() {
		List<String> loaded = new ArrayList<>();
		
		IExtensionRegistry registry = Platform.getExtensionRegistry();
		if (registry == null) {
			return loaded;
		}
		
		IConfigurationElement[] elements = registry.getConfigurationElementsFor(GUARDS_EXTENSION_POINT_ID);
		
		for (IConfigurationElement element : elements) {
			if (!"guard".equals(element.getName())) { //$NON-NLS-1$
				continue;
			}
			String name = element.getAttribute("name"); //$NON-NLS-1$
			if (name == null || name.isEmpty()) {
				continue;
			}
			try {
				Bundle bundle = Platform.getBundle(element.getContributor().getName());
				if (bundle == null) {
					continue;
				}
				String className = element.getAttribute("class"); //$NON-NLS-1$
				if (className == null) {
					continue;
				}
				Class<?> guardClass = bundle.loadClass(className);
				Object instance = guardClass.getDeclaredConstructor().newInstance();
				if (instance instanceof GuardFunction guardFunction) {
					register(name, guardFunction);
					loaded.add(name);
				}
			} catch (Exception e) {
				ILog log = Platform.getLog(GuardRegistry.class);
				log.log(Status.warning(
						"Failed to load guard function from extension: " + name, e)); //$NON-NLS-1$
			}
		}
		
		return loaded;
	}
	
}