Created
January 30, 2026 22:27
-
-
Save mhewedy/5666cb93adf212673c375542189184a7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import com.github.mhewedy.expressions.Expression; | |
| import com.github.mhewedy.expressions.Expressions; | |
| import com.github.mhewedy.expressions.Operator; | |
| import org.springframework.data.domain.*; | |
| import java.util.*; | |
| import java.util.function.Function; | |
| import java.util.stream.Collectors; | |
| import static com.github.mhewedy.expressions.Expressions.Field; | |
| public class SearchUtil { | |
| public static Pageable sortById(Pageable pageable, boolean override) { | |
| return sortBy(pageable, "id", override); | |
| } | |
| public static Pageable sortBy(Pageable pageable, String field, boolean override) { | |
| if (override || pageable.getSort().isUnsorted()) { | |
| return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(field)); | |
| } else { | |
| return pageable; | |
| } | |
| } | |
| public static <T> Page<T> listToPage(List<T> list, Pageable pageable) { | |
| int pageSize = Math.min(pageable.getPageSize(), list.size()); | |
| int fromIndex = pageable.getPageNumber() * pageSize; | |
| var subList = list.subList(fromIndex, Math.min(fromIndex + pageSize, list.size())); | |
| return new PageImpl<>(subList, pageable, list.size()); | |
| } | |
| public static void checkAllowedFields(Expressions expressions, String... allowedFields) { | |
| Set<String> exprFields = expressions.extractFields().stream().map(Field::name).collect(Collectors.toSet()); | |
| Set<String> allowedFieldsSet = new HashSet<>(Arrays.asList(allowedFields)); | |
| if (!allowedFieldsSet.containsAll(exprFields)) { | |
| throw new IllegalArgumentException("expression.contains.disallowed.fields"); | |
| } | |
| } | |
| public static void checkAllowedOperators(Expressions expressions, Operator... allowedOperator) { | |
| Set<Operator> exprFields = expressions.extractFields().stream().map(Field::operator).collect(Collectors.toSet()); | |
| Set<Operator> allowedFieldsSet = new HashSet<>(Arrays.asList(allowedOperator)); | |
| if (!allowedFieldsSet.containsAll(exprFields)) { | |
| throw new IllegalArgumentException("expression.contains.disallowed.operator"); | |
| } | |
| } | |
| public static Optional<Field> findField(Expressions expressions, String field) { | |
| return expressions.extractFields().stream().filter(it -> it.name().equals(field)).findFirst(); | |
| } | |
| public static void replaceField(Expressions expressions, String fieldName, Function<Field, Expression> expressionFn) { | |
| SearchUtil.findField(expressions, fieldName).ifPresent(field -> { | |
| SearchUtil.removeField(expressions, field.name()); | |
| Expression expression = expressionFn.apply(field); | |
| if (expression != null) { | |
| expressions.and(expression); | |
| } | |
| }); | |
| } | |
| /** | |
| * Recursively removes all occurrences of a key from a nested Map structure | |
| * and removes empty parents all the way up. | |
| * | |
| * @param expressions The map to search through | |
| * @param field The key to remove | |
| * @return true if the map is empty after removals, false otherwise | |
| */ | |
| public static boolean removeField(Map<String, Object> expressions, String field) { | |
| Iterator<Map.Entry<String, Object>> iterator = expressions.entrySet().iterator(); | |
| while (iterator.hasNext()) { | |
| Map.Entry<String, Object> entry = iterator.next(); | |
| // Check if this is the key we want to remove | |
| if (entry.getKey().equals(field)) { | |
| iterator.remove(); | |
| continue; | |
| } | |
| Object value = entry.getValue(); | |
| // Recurse into nested map | |
| if (value instanceof Map<?, ?> map) { | |
| @SuppressWarnings("unchecked") | |
| Map<String, Object> nestedMap = (Map<String, Object>) map; | |
| boolean isEmpty = removeField(nestedMap, field); | |
| if (isEmpty) { | |
| iterator.remove(); | |
| } | |
| } | |
| // Recurse into collections | |
| else if (value instanceof Collection<?> collection) { | |
| removeFromCollection(collection, field); | |
| if (collection.isEmpty() || isCollectionEmpty(collection)) { | |
| iterator.remove(); | |
| } | |
| } | |
| } | |
| return expressions.isEmpty(); | |
| } | |
| private static void removeFromCollection(Collection<?> collection, String field) { | |
| Iterator<?> iterator = collection.iterator(); | |
| while (iterator.hasNext()) { | |
| Object item = iterator.next(); | |
| if (item instanceof Map<?, ?> map) { | |
| @SuppressWarnings("unchecked") | |
| Map<String, Object> nestedMap = (Map<String, Object>) map; | |
| boolean isEmpty = removeField(nestedMap, field); | |
| if (isEmpty) { | |
| iterator.remove(); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Checks if a collection is empty or contains only empty maps | |
| */ | |
| private static boolean isCollectionEmpty(Collection<?> collection) { | |
| for (Object item : collection) { | |
| if (item instanceof Map<?, ?> map) { | |
| if (!map.isEmpty()) { | |
| return false; | |
| } | |
| } else { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment