AotPlaceholders.java

/*
 * Copyright 2025-present the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.mongodb.repository.aot;

import java.util.List;

import org.jspecify.annotations.Nullable;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.geo.Shape;
import org.springframework.data.mongodb.core.geo.GeoJson;
import org.springframework.data.mongodb.core.geo.Sphere;

/**
 * Placeholders for AOT processing of MongoDB queries.
 *
 * @author Mark Paluch
 * @since 5.0
 */
class AotPlaceholders {

	/**
	 * Create a new placeholder using positional binding markers.
	 *
	 * @param position the index of the parameter to bind.
	 * @return new instance of {@link Placeholder}.
	 */
	static Placeholder indexed(int position) {
		return new PlaceholderImpl("?" + position);
	}

	/**
	 * Create a placeholder for a GeoJSON object.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @param type
	 * @return
	 */
	static Placeholder geoJson(int index, String type) {
		return new GeoJsonPlaceholder(index, type);
	}

	/**
	 * Create a placeholder for a {@link Point} object.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @return
	 */
	static Placeholder point(int index) {
		return new PointPlaceholder(index);
	}

	/**
	 * Create a placeholder for a {@link Circle} object.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @return
	 */
	static Placeholder circle(int index) {
		return new CirclePlaceholder(index);
	}

	/**
	 * Create a placeholder for a {@link Box} object.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @return
	 */
	static Placeholder box(int index) {
		return new BoxPlaceholder(index);
	}

	/**
	 * Create a placeholder for a {@link Sphere} object.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @return
	 */
	static Placeholder sphere(int index) {
		return new SpherePlaceholder(index);
	}

	/**
	 * Create a placeholder for a {@link Polygon} object.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @return
	 */
	static Placeholder polygon(int index) {
		return new PolygonPlaceholder(index);
	}

	static RegexPlaceholder regex(int index, @Nullable String options) {
		return new RegexPlaceholder(index, options);
	}

	/**
	 * Create a placeholder that indicates the value should be treated as list.
	 *
	 * @param index zero-based index referring to the bindable method parameter.
	 * @return new instance of {@link Placeholder}.
	 */
	static Placeholder asList(int index) {
		return asList(indexed(index));
	}

	/**
	 * Create a placeholder that indicates the wrapped placeholder should be treated as list.
	 *
	 * @param source the target placeholder
	 * @return new instance of {@link Placeholder}.
	 */
	static Placeholder asList(Placeholder source) {
		return new AsListPlaceholder(source);
	}

	/**
	 * A placeholder expression used when rending queries to JSON.
	 *
	 * @since 5.0
	 * @author Christoph Strobl
	 */
	interface Placeholder {

		String getValue();

		/**
		 * Unwrap the current {@link Placeholder} to the given target type if possible.
		 *
		 * @param targetType
		 * @return
		 * @param <T>
		 */
		default <T extends Placeholder> @Nullable T unwrap(Class<? extends T> targetType) {
			return targetType.isInstance(this) ? targetType.cast(this) : null;
		}
	}

	/**
	 * @author Christoph Strobl
	 * @since 5.0
	 */
	record PlaceholderImpl(String expression) implements AotPlaceholders.Placeholder {

		@Override
		public String getValue() {
			return expression;
		}

		public String toString() {
			return getValue();
		}

	}

	private static class PointPlaceholder extends Point implements Placeholder {

		private final int index;

		PointPlaceholder(int index) {
			super(Double.NaN, Double.NaN);
			this.index = index;
		}

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String toString() {
			return getValue();
		}

	}

	private record GeoJsonPlaceholder(int index, String type) implements Placeholder, GeoJson<List<Placeholder>>, Shape {

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String getType() {
			return type();
		}

		@Override
		public String toString() {
			return getValue();
		}

		@Override
		public List<Placeholder> getCoordinates() {
			return List.of();
		}

	}

	private static class CirclePlaceholder extends Circle implements Placeholder {

		private final int index;

		CirclePlaceholder(int index) {
			super(new PointPlaceholder(index), Distance.of(1, Metrics.NEUTRAL)); //
			this.index = index;
		}

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String toString() {
			return getValue();
		}

	}

	private static class BoxPlaceholder extends Box implements Placeholder {

		private final int index;

		BoxPlaceholder(int index) {
			super(new PointPlaceholder(index), new PointPlaceholder(index));
			this.index = index;
		}

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String toString() {
			return getValue();
		}

	}

	private static class SpherePlaceholder extends Sphere implements Placeholder {

		private final int index;

		SpherePlaceholder(int index) {
			super(new PointPlaceholder(index), Distance.of(1, Metrics.NEUTRAL)); //
			this.index = index;
		}

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String toString() {
			return getValue();
		}

	}

	private static class PolygonPlaceholder extends Polygon implements Placeholder {

		private final int index;

		PolygonPlaceholder(int index) {
			super(new PointPlaceholder(index), new PointPlaceholder(index), new PointPlaceholder(index),
					new PointPlaceholder(index));
			this.index = index;
		}

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String toString() {
			return getValue();
		}

	}

	static class RegexPlaceholder implements Placeholder {

		private final int index;
		private final @Nullable String options;

		RegexPlaceholder(int index, @Nullable String options) {
			this.index = index;
			this.options = options;
		}

		@Nullable
		String regexOptions() {
			return options;
		}

		@Override
		public String getValue() {
			return "?" + index;
		}

		@Override
		public String toString() {
			return getValue();
		}
	}

	record AsListPlaceholder(Placeholder placeholder) implements Placeholder {

		@Override
		public @Nullable <T extends Placeholder> T unwrap(Class<? extends T> targetType) {

			if (targetType.isInstance(placeholder)) {
				return targetType.cast(placeholder);
			}

			return Placeholder.super.unwrap(targetType);
		}

		@Override
		public String toString() {
			return getValue();
		}

		@Override
		public String getValue() {
			return "[" + placeholder.getValue() + "]";
		}
	}

}