/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.styling.css.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Base class to build a power set from a set of object, filtering it during construction to avoid * trying sub-trees that lead to no results * * @author Andrea Aime - GeoSolutions * * @param <T> The type of the domain elements * @param <R> The type of the power set elements (combinations might generate a new type of object) */ public abstract class FilteredPowerSetBuilder<T, R> { /** * The original list of values from which we'll build the power set */ private List<T> domain; /** * Signatures that have been rejected, that we already know won't generate an entry in the * result */ private Set<Signature> rejects = new HashSet<>(); /** * Initializes the power set builds with the initial domain values * * @param domain */ public FilteredPowerSetBuilder(List<T> domain) { this.domain = domain; } /** * See if a certain signature matches an already rejected signature * * @param s * @param k * @return */ private boolean rejected(Signature s, int k) { // see if rejected already for (Signature reject : rejects) { if (s.contains(reject, k)) { return true; } } return false; } /** * Builds the power set * * @return */ public List<R> buildPowerSet() { List<R> result = new ArrayList<>(); Signature s = Signature.newSignature(domain.size()); fill(s, 0, domain.size(), result); result = postFilterResult(result); return result; } /** * Allows subclasses to filter the results after they have been built * * @param result * @return */ protected List<R> postFilterResult(List<R> result) { return result; } /** * Recursively builds all possible signatures in the domain (will stop immediately if a * signature is not accepted, or builds on top of a already rejected signature) * * @param s * @param k * @param n * @param result */ void fill(Signature s, int k, int n, List<R> result) { List<T> objects = listFromSignature(s); if (!objects.isEmpty()) { if (!accept(objects)) { rejects.add((Signature) s.clone()); return; } } if (k == n) { List<R> combined = buildResult(objects); if (combined != null) { result.addAll(combined); } } else { s.set(k, true); if (!rejected(s, k)) { fill(s, k + 1, n, result); } s.set(k, false); if (!rejected(s, k)) { fill(s, k + 1, n, result); } } } /** * Builds a result from a combination of input objects. The method can return null to identify a * combination that does not generate anything useful, but whose set of object could still * generate a valid combination when grown with more objects (thus, not a candidate for * returning false in {@link #accept(List)}) * * @param objects * @return */ protected abstract List<R> buildResult(List<T> objects); /** * Checks if a certain list of objects should be accepted, or not. If rejected, a signature will * be built from this set, and any superset of these objects will also be rejected * * @param set * @return */ protected abstract boolean accept(List<T> set); /** * Returns the list of values associated to this signature * * @param signature * @return */ private List<T> listFromSignature(Signature signature) { List<T> test = new ArrayList<>(); for (int i = 0; i < domain.size(); i++) { if (signature.get(i)) { test.add(domain.get(i)); } } return test; } }