ApplyNormalizeGraphInnerPathsTransform.java

/*******************************************************************************
 * Copyright (c) 2025 Eclipse RDF4J contributors.
 *
 * 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.queryrender.sparql.ir.util.transform;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.queryrender.sparql.TupleExprIRRenderer;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrBGP;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrGraph;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrMinus;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrNode;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrOptional;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrPathTriple;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrService;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrStatementPattern;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrUnion;

/**
 * Within GRAPH bodies, normalize local triple/path shapes by fusing adjacent PT/SP/PT patterns and performing
 * conservative tail joins. This helps later UNION/path fusers see a stable inner structure.
 */
public final class ApplyNormalizeGraphInnerPathsTransform extends BaseTransform {
	private ApplyNormalizeGraphInnerPathsTransform() {
	}

	public static IrBGP apply(IrBGP bgp, TupleExprIRRenderer r) {
		if (bgp == null) {
			return null;
		}
		List<IrNode> out = new ArrayList<>();
		for (IrNode n : bgp.getLines()) {
			if (n instanceof IrGraph) {
				IrGraph g = (IrGraph) n;
				IrBGP inner = g.getWhere();
				// Support both PT-then-SP and SP-then-PT fusions inside GRAPH bodies
				inner = fuseAdjacentPtThenSp(inner, r);
				inner = fuseAdjacentSpThenPt(inner, r);
				// Also collapse adjacent IrPathTriple ��� IrPathTriple chains
				inner = fuseAdjacentPtThenPt(inner);
				inner = joinPathWithLaterSp(inner, r);
				inner = fuseAltInverseTailBGP(inner, r);
				out.add(new IrGraph(g.getGraph(), inner, g.isNewScope()));
			} else if (n instanceof IrBGP || n instanceof IrOptional || n instanceof IrMinus || n instanceof IrUnion
					|| n instanceof IrService) {
				IrNode rec = BaseTransform.rewriteContainers(n, child -> apply(child, r));
				out.add(rec);
			} else {
				out.add(n);
			}
		}
		return BaseTransform.bgpWithLines(bgp, out);

	}

	public static IrBGP fuseAdjacentPtThenSp(IrBGP bgp, TupleExprIRRenderer r) {
		if (bgp == null) {
			return null;
		}
		List<IrNode> in = bgp.getLines();
		List<IrNode> out = new ArrayList<>();
		for (int i = 0; i < in.size(); i++) {
			IrNode n = in.get(i);
			if (i + 1 < in.size() && n instanceof IrPathTriple && in.get(i + 1) instanceof IrStatementPattern) {
				IrPathTriple pt = (IrPathTriple) n;
				IrStatementPattern sp = (IrStatementPattern) in.get(i + 1);
				Var pv = sp.getPredicate();
				if (isConstantIriPredicate(sp)) {
					Var bridge = pt.getObject();
					if (isAnonPathVar(bridge)) {
						if (sameVar(bridge, sp.getSubject())) {
							String fused = pt.getPathText() + "/" + iri(pv, r);
							IrPathTriple np = new IrPathTriple(pt.getSubject(), fused, sp.getObject(), false,
									pt.getPathVars());
							out.add(np);
							i += 1;
							continue;
						} else if (sameVar(bridge, sp.getObject())) {
							String fused = pt.getPathText() + "/^" + iri(pv, r);
							IrPathTriple np2 = new IrPathTriple(pt.getSubject(), fused, sp.getSubject(), false,
									pt.getPathVars());
							out.add(np2);
							i += 1;
							continue;
						}
					}
				}
			}
			// Recurse into containers
			if (n instanceof IrUnion) {
				IrUnion u = (IrUnion) n;
				IrUnion u2 = new IrUnion(u.isNewScope());
				for (IrBGP b : u.getBranches()) {
					IrBGP nb = fuseAdjacentPtThenSp(b, r);
					nb = fuseAdjacentSpThenPt(nb, r);
					nb = fuseAdjacentPtThenPt(nb);
					nb = joinPathWithLaterSp(nb, r);
					nb = fuseAltInverseTailBGP(nb, r);
					u2.addBranch(nb);
				}
				out.add(u2);
				continue;
			}
			IrNode rec = BaseTransform.rewriteContainers(n, child -> fuseAdjacentPtThenSp(child, r));
			out.add(rec);
		}
		return BaseTransform.bgpWithLines(bgp, out);
	}

}