Var.java

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.query.algebra;

import java.util.Objects;
import java.util.ServiceLoader;

import org.eclipse.rdf4j.model.Value;

/**
 * A variable that can contain a Value.
 *
 * <p>
 * <strong>Service Provider���based construction:</strong> Prefer the {@code Var.of(...)} static factory methods over
 * direct constructors. These factories delegate to a {@link Var.Provider} discovered via {@link ServiceLoader} or
 * selected via the {@link #PROVIDER_PROPERTY} system property. This allows third-party libraries to supply custom
 * {@code Var} subclasses without changing call sites. If no provider is found, construction falls back to
 * {@code new Var(...)}.
 * </p>
 *
 * <p>
 * To install a provider, add a file {@code META-INF/services/org.eclipse.rdf4j.query.algebra.Var$Provider} containing
 * the implementing class name, or set system property {@link #PROVIDER_PROPERTY} to a specific provider FQCN.
 * </p>
 *
 * @implNote In the future this class may stop extending AbstractQueryModelNode in favor of directly implementing
 *           ValueExpr and QueryModelNode.
 */
public class Var extends AbstractQueryModelNode implements ValueExpr {

	/**
	 * System property that, when set to a fully qualified class name implementing {@link Var.Provider}, selects that
	 * provider. If absent, the first provider discovered by {@link ServiceLoader} is used; if none are found, a default
	 * provider that constructs {@code Var} directly is used.
	 */
	public static final String PROVIDER_PROPERTY = "org.eclipse.rdf4j.query.algebra.Var.provider";

	private final String name;

	private final Value value;

	private final boolean anonymous;

	private boolean constant = false;

	private int cachedHashCode = 0;

	/*
	 * ========================= Static factory entry points =========================
	 */

	/**
	 * Factory mirroring {@link #Var(String)}.
	 */
	public static Var of(String name) {
		return Holder.PROVIDER.newVar(name, null, false, false);
	}

	/**
	 * Factory mirroring {@link #Var(String, boolean)}.
	 */
	public static Var of(String name, boolean anonymous) {
		return Holder.PROVIDER.newVar(name, null, anonymous, false);
	}

	/**
	 * Factory mirroring {@link #Var(String, Value)}.
	 */
	public static Var of(String name, Value value) {
		return Holder.PROVIDER.newVar(name, value, false, false);
	}

	/**
	 * Factory mirroring {@link #Var(String, Value, boolean)}.
	 */
	public static Var of(String name, Value value, boolean anonymous) {
		return Holder.PROVIDER.newVar(name, value, anonymous, false);
	}

	/**
	 * Factory mirroring {@link #Var(String, Value, boolean, boolean)}.
	 */
	public static Var of(String name, Value value, boolean anonymous, boolean constant) {
		return Holder.PROVIDER.newVar(name, value, anonymous, constant);
	}

	/*
	 * ========================= Constructors (existing API) =========================
	 */

	/**
	 * @deprecated since 5.1.5, use {@link #of(String, Value, boolean, boolean)} instead. Constructor will be made
	 *             protected, subclasses may still use this method to instantiate themselves.
	 * @param name
	 * @param value
	 * @param anonymous
	 * @param constant
	 */
	@Deprecated(since = "5.1.5", forRemoval = true)
	public Var(String name, Value value, boolean anonymous, boolean constant) {
		this.name = name;
		this.value = value;
		this.anonymous = anonymous;
		this.constant = constant;
	}

	/**
	 * @deprecated since 5.1.5, use {@link #of(String)} instead.
	 * @param name
	 */
	@Deprecated(since = "5.1.5", forRemoval = true)
	public Var(String name) {
		this(name, null, false, false);
	}

	/**
	 * @deprecated since 5.1.5, use {@link #of(String, boolean)} instead.
	 * @param name
	 * @param anonymous
	 */
	@Deprecated(since = "5.1.5", forRemoval = true)
	public Var(String name, boolean anonymous) {
		this(name, null, anonymous, false);
	}

	/**
	 * @deprecated since 5.1.5, use {@link #of(String, Value)} instead.
	 * @param name
	 * @param value
	 */
	@Deprecated(since = "5.1.5", forRemoval = true)
	public Var(String name, Value value) {
		this(name, value, false, false);
	}

	/**
	 * @deprecated since 5.1.5, use {@link #of(String, Value, boolean)} instead.
	 * @param name
	 * @param value
	 * @param anonymous
	 */
	@Deprecated(since = "5.1.5", forRemoval = true)
	public Var(String name, Value value, boolean anonymous) {
		this(name, value, anonymous, false);
	}

	/*
	 * ========================= Service Provider Interface (SPI) =========================
	 */

	/**
	 * Service Provider Interface for globally controlling {@link Var} instantiation.
	 *
	 * <p>
	 * Implementations may return custom subclasses of {@code Var}. Implementations should be registered via
	 * {@code META-INF/services/org.eclipse.rdf4j.query.algebra.Var$Provider} or selected with
	 * {@link #PROVIDER_PROPERTY}.
	 * </p>
	 *
	 * <p>
	 * <strong>Important:</strong> Implementations must not call {@code Var.of(...)} from within
	 * {@link #newVar(String, Value, boolean, boolean)} or {@link #cloneVar(Var)} to avoid infinite recursion. Call a
	 * constructor directly (e.g., {@code return new CustomVar(...); }). Returned instances from both methods must
	 * remain consistent with {@link Var#equals(Object)} and {@link Var#hashCode()}.
	 * </p>
	 */
	@FunctionalInterface
	public interface Provider {
		/**
		 * Mirror of the primary 4-argument {@link Var} constructor.
		 */
		Var newVar(String name, Value value, boolean anonymous, boolean constant);

		/**
		 * Creates a copy of the supplied {@link Var}. Implementations should ensure the clone is consistent with
		 * {@link #equals(Object)} and {@link #hashCode()} for the concrete {@code Var} subtype they produce.
		 * <p>
		 * <strong>Important:</strong> Implementations must not call {@code Var.of(...)} from within this method to
		 * avoid infinite recursion. Call a constructor or factory that does not delegate back to
		 * {@link Var#of(String)}.
		 * </p>
		 */
		default Var cloneVar(Var original) {
			return newVar(original.getName(), original.getValue(), original.isAnonymous(), original.isConstant());
		}
	}

	public boolean isAnonymous() {
		return anonymous;
	}

	public String getName() {
		return name;
	}

	public boolean hasValue() {
		return value != null;
	}

	public Value getValue() {
		return value;
	}

	@Override
	public <X extends Exception> void visit(QueryModelVisitor<X> visitor) throws X {
		visitor.meet(this);
	}

	@Override
	public <X extends Exception> void visitChildren(QueryModelVisitor<X> visitor) throws X {
		// no-op
	}

	@Override
	public void setParentNode(QueryModelNode parent) {
		assert getParentNode() == null;
		super.setParentNode(parent);
	}

	@Override
	public void replaceChildNode(QueryModelNode current, QueryModelNode replacement) {

	}

	@Override
	public String getSignature() {
		StringBuilder sb = new StringBuilder(64);

		sb.append(this.getClass().getSimpleName());

		sb.append(" (name=").append(name);

		if (value != null) {
			sb.append(", value=").append(value);
		}

		if (anonymous) {
			sb.append(", anonymous");
		}

		sb.append(")");

		return sb.toString();
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (!(o instanceof Var)) {
			return false;
		}
		Var var = (Var) o;

		if (cachedHashCode != 0 && var.cachedHashCode != 0 && cachedHashCode != var.cachedHashCode) {
			return false;
		}

		return spiEquals(var) && var.spiEquals(this);
	}

	@Override
	public int hashCode() {
		if (cachedHashCode == 0) {
			cachedHashCode = spiHashCode();
		}
		return cachedHashCode;
	}

	@Override
	public Var clone() {
		Var var = Holder.PROVIDER.cloneVar(this);
		var.setVariableScopeChange(this.isVariableScopeChange());
		return var;
	}

	/**
	 * Extension hook for subclasses to participate in {@link #equals(Object)} while preserving symmetry with other
	 * {@link Var} instances.
	 */
	protected boolean spiEquals(Var other) {
		return anonymous == other.anonymous
				&& !(name == null && other.name != null || value == null && other.value != null)
				&& Objects.equals(name, other.name) && Objects.equals(value, other.value);
	}

	/**
	 * Extension hook for subclasses to contribute additional state to {@link #hashCode()} while reusing the cached hash
	 * storage in {@link Var}.
	 */
	protected int spiHashCode() {
		int result = 1;
		result = 31 * result + (name == null ? 0 : name.hashCode());
		result = 31 * result + (value == null ? 0 : value.hashCode());
		result = 31 * result + Boolean.hashCode(anonymous);
		return result;
	}

	/**
	 * @return Returns the constant.
	 */
	public boolean isConstant() {
		return constant;
	}

	private static final class Holder {
		private static final Provider DEFAULT = Var::new;

		static final Provider PROVIDER = initProvider();

		private static Provider initProvider() {
			// 1) Explicit override via system property (FQCN of Var.Provider)
			String fqcn = null;
			try {
				fqcn = System.getProperty(PROVIDER_PROPERTY);
			} catch (SecurityException se) {
				// Restricted environments may deny property access; ignore and fall back to discovery/default.
			}
			if (fqcn != null && !fqcn.isEmpty()) {
				try {
					Class<?> cls = Class.forName(fqcn, true, Var.class.getClassLoader());
					if (Provider.class.isAssignableFrom(cls)) {
						@SuppressWarnings("unchecked")
						Class<? extends Provider> pcls = (Class<? extends Provider>) cls;
						return pcls.getDeclaredConstructor().newInstance();
					}
					// Fall through to discovery if class does not implement Provider
				} catch (Throwable t) {
					// Swallow and fall back to discovery; avoid linking to any logging framework here.
				}
			}

			// 2) ServiceLoader discovery: pick the first provider found
			try {
				ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);
				for (Provider p : loader) {
					return p; // first one wins
				}
			} catch (Throwable t) {
				// ignore and fall back
			}

			// 3) Fallback: direct construction
			return DEFAULT;
		}
	}

}