/** * */ package com.arangodb.springframework.repository.query.derived; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.springframework.data.domain.Range; 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 com.arangodb.springframework.repository.query.derived.geo.Ring; /** * @author Mark * */ public class BindParameterBinding { interface UniqueCheck { void check(Point point); } private final Map<String, Object> bindVars; public BindParameterBinding(final Map<String, Object> bindVars) { super(); this.bindVars = bindVars; } public int bind( final Object value, final boolean shouldIgnoreCase, final Boolean borderStatus, final UniqueCheck uniqueCheck, final int startIndex) { int index = startIndex; final Object caseAdjusted = ignoreArgumentCase(value, shouldIgnoreCase); final Class<? extends Object> clazz = caseAdjusted.getClass(); if (clazz == Distance.class) { final Distance distance = (Distance) caseAdjusted; bind(index++, convertDistanceToMeters(distance)); } else if (borderStatus != null && borderStatus) { bind(index++, escapeSpecialCharacters((String) caseAdjusted) + "%"); } else if (borderStatus != null) { bind(index++, "%" + escapeSpecialCharacters((String) caseAdjusted)); } else { bind(index++, caseAdjusted); } return index; } public int bindPolygon(final Object value, final boolean shouldIgnoreCase, final int startIndex) { int index = startIndex; final Polygon polygon = (Polygon) ignoreArgumentCase(value, shouldIgnoreCase); final List<List<Double>> points = new LinkedList<>(); polygon.forEach(p -> { final List<Double> point = new LinkedList<>(); point.add(p.getY()); point.add(p.getX()); points.add(point); }); bind(index++, points); return index; } public int bindRing( final Object value, final boolean shouldIgnoreCase, final UniqueCheck uniqueCheck, final int startIndex) { int index = startIndex; final Ring<?> ring = (Ring<?>) ignoreArgumentCase(value, shouldIgnoreCase); final Point point = ring.getPoint(); index = bindPoint(point, uniqueCheck, index); final Range<?> range = ring.getRange(); index = bindRange(range, index); return index; } public int bindRange(final Object value, final boolean shouldIgnoreCase, final int startIndex) { final int index = startIndex; final Range<?> range = (Range<?>) ignoreArgumentCase(value, shouldIgnoreCase); return bindRange(range, index); } public int bindBox(final Object value, final boolean shouldIgnoreCase, final int startIndex) { int index = startIndex; final Box box = (Box) ignoreArgumentCase(value, shouldIgnoreCase); final Point first = box.getFirst(); final Point second = box.getSecond(); final double minLatitude = Math.min(first.getY(), second.getY()); final double maxLatitude = Math.max(first.getY(), second.getY()); final double minLongitude = Math.min(first.getX(), second.getX()); final double maxLongitude = Math.max(first.getX(), second.getX()); bind(index++, minLatitude); bind(index++, maxLatitude); bind(index++, minLongitude); bind(index++, maxLongitude); return index; } public int bindPoint( final Object value, final boolean shouldIgnoreCase, final UniqueCheck uniqueCheck, final int startIndex) { return bindPoint((Point) ignoreArgumentCase(value, shouldIgnoreCase), uniqueCheck, startIndex); } private int bindPoint(final Point point, final UniqueCheck uniqueCheck, final int startIndex) { uniqueCheck.check(point); int index = startIndex; bind(index++, point.getY()); bind(index++, point.getX()); return index; } public int bindCircle( final Object value, final boolean shouldIgnoreCase, final UniqueCheck uniqueCheck, final int startIndex) { int index = startIndex; final Circle circle = (Circle) ignoreArgumentCase(value, shouldIgnoreCase); final Point center = circle.getCenter(); uniqueCheck.check(center); bind(index++, center.getY()); bind(index++, center.getX()); bind(index++, convertDistanceToMeters(circle.getRadius())); return index; } private int bindRange(final Range<?> range, int index) { Object lowerBound = range.getLowerBound().getValue().get(); Object upperBound = range.getUpperBound().getValue().get(); if (lowerBound.getClass() == Distance.class && upperBound.getClass() == lowerBound.getClass()) { lowerBound = convertDistanceToMeters((Distance) lowerBound); upperBound = convertDistanceToMeters((Distance) upperBound); } bind(index++, lowerBound); bind(index++, upperBound); return index; } private void bind(final int index, final Object value) { bindVars.put(Integer.toString(index), value); } private double convertDistanceToMeters(final Distance distance) { return distance.getNormalizedValue() * Metrics.KILOMETERS.getMultiplier() * 1000; } /** * Escapes special characters which could be used in an operand of LIKE operator * * @param string * @return */ private String escapeSpecialCharacters(final String string) { final StringBuilder escaped = new StringBuilder(); for (final char character : string.toCharArray()) { if (character == '%' || character == '_' || character == '\\') { escaped.append('\\'); } escaped.append(character); } return escaped.toString(); } /** * Lowers case of a given argument if its type is String, Iterable<String> or String[] if shouldIgnoreCase is true * * @param argument * @param shouldIgnoreCase * @return */ private Object ignoreArgumentCase(final Object argument, final boolean shouldIgnoreCase) { if (!shouldIgnoreCase) { return argument; } if (argument instanceof String) { return ((String) argument).toLowerCase(); } final List<String> lowered = new LinkedList<>(); if (argument.getClass().isArray()) { final String[] array = (String[]) argument; for (final String string : array) { lowered.add(string.toLowerCase()); } } else { @SuppressWarnings("unchecked") final Iterable<String> iterable = (Iterable<String>) argument; for (final Object object : iterable) { lowered.add(((String) object).toLowerCase()); } } return lowered; } }