package com.psddev.cms.db; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import com.psddev.dari.db.Modification; import com.psddev.dari.db.ObjectFieldComparator; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.Predicate; import com.psddev.dari.db.PredicateParser; import com.psddev.dari.db.Query; import com.psddev.dari.db.Recordable; import com.psddev.dari.util.ObjectUtils; public interface Taxon extends Recordable { public boolean isRoot(); public Collection<? extends Taxon> getChildren(); @FieldInternalNamePrefix("cms.taxon.") public static final class Data extends Modification<Taxon> { @Indexed @ToolUi.Hidden private Boolean root; @ToolUi.Hidden private Boolean childrenEmpty; private transient boolean selectable = true; @ToolUi.Hidden private String altLabel; public Boolean isRoot() { return Boolean.TRUE.equals(root); } public void setRoot(Boolean root) { this.root = root ? Boolean.TRUE : null; } public boolean isChildrenEmpty() { return Boolean.TRUE.equals(childrenEmpty); } public void setChildrenEmpty(boolean childrenEmpty) { this.childrenEmpty = childrenEmpty ? Boolean.TRUE : null; } public boolean isSelectable() { return selectable; } public void setSelectable(boolean selectable) { this.selectable = selectable; } public String getAltLabel() { return altLabel; } public void setAltLabel(String altLabel) { this.altLabel = altLabel; } public void beforeSave() { Taxon taxon = getOriginalObject(); setRoot(taxon.isRoot()); setChildrenEmpty(taxon.getChildren().isEmpty()); } } /** * {@link Taxon} utility methods. */ public static final class Static { public static <T extends Taxon> List<T> getRoots(Class<T> taxonClass) { return getRoots(taxonClass, null); } public static <T extends Taxon> List<T> getRoots(Class<T> taxonClass, Site site) { return getRoots(taxonClass, site, null); } public static <T extends Taxon> List<T> getRoots(Class<T> taxonClass, Site site, Predicate predicate) { Query<T> query = Query.from(taxonClass).where("cms.taxon.root = true"); if (site != null) { query.and(site.itemsPredicate()); } List<T> roots = query.selectAll(); return filter(roots, predicate); } public static <T extends Taxon> boolean hasChildren(T taxon, Predicate predicate) { return taxon.getChildren().stream() .anyMatch(childTaxon -> PredicateParser.Static.evaluate(childTaxon, predicate) || hasChildren(childTaxon, predicate)); } public static <T extends Taxon> List<? extends Taxon> getChildren(T taxon, Predicate predicate) { if (taxon == null) { return Collections.emptyList(); } List<Taxon> children = new ArrayList<>(); children.addAll(taxon.getChildren()); return filter(children, predicate); } public static <T extends Taxon> Comparator<T> getSorter(List<T> taxons) { if (ObjectUtils.isBlank(taxons)) { return (o1, o2) -> 0; } ObjectType taxonType = taxons.get(0).getState().getType(); ToolUi ui = taxonType.as(ToolUi.class); if (ObjectUtils.isBlank(ui.getDefaultSortField())) { return (o1, o2) -> 0; } ObjectFieldComparator comparator = new ObjectFieldComparator(ui.getDefaultSortField(), true); return (o1, o2) -> comparator.compare(o1, o2); } public static <T extends Taxon> List<T> filter(List<T> taxons, Predicate predicate) { // If there's no items, we can just return the empty list if (taxons.isEmpty()) { return taxons; } // If there's nothing to filter on, just return the list, sorted if (predicate == null) { return taxons.stream() .sorted(getSorter(taxons)) .collect(Collectors.toList()); } // mark the roots that don't match the predicate as not selectable taxons.stream() .filter(taxon -> !PredicateParser.Static.evaluate(taxon, predicate)) .forEach(child -> child.as(Taxon.Data.class).setSelectable(false)); // Filter out any roots that are not selectable AND have no children that are selectable return taxons.stream() .filter(taxon -> taxon.as(Taxon.Data.class).isSelectable() || hasChildren(taxon, predicate)) .sorted(getSorter(taxons)) .collect(Collectors.toList()); } } }