/* * PlotSquared, a land and world management plugin for Minecraft. * Copyright (C) IntellectualSites * Copyright (C) IntellectualSites team and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.plotsquared.core.util.query; import com.google.common.base.Preconditions; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.Rating; import com.plotsquared.core.plot.flag.implementations.DoneFlag; import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.util.MathMan; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Stream; /** * This represents a plot query, and can be used to * search for plots matching certain criteria. *

* The queries can be reused as no results are stored * in the query itself */ public final class PlotQuery implements Iterable { private final Collection filters = new LinkedList<>(); private final PlotAreaManager plotAreaManager; private PlotProvider plotProvider; private SortingStrategy sortingStrategy = SortingStrategy.NO_SORTING; private PlotArea priorityArea; private Comparator plotComparator; private PlotQuery(final @NonNull PlotAreaManager plotAreaManager) { this.plotAreaManager = plotAreaManager; this.plotProvider = new GlobalPlotProvider(plotAreaManager); } /** * Create a new plot query instance * * @return New query */ public static PlotQuery newQuery() { return new PlotQuery(PlotSquared.get().getPlotAreaManager()); } /** * Query for plots in a single area * * @param area Area * @return The query instance */ public @NonNull PlotQuery inArea(final @NonNull PlotArea area) { Preconditions.checkNotNull(area, "Area may not be null"); this.plotProvider = new AreaLimitedPlotProvider(Collections.singletonList(area)); return this; } /** * Query for plots in all areas in a world * * @param world World name * @return The query instance */ public @NonNull PlotQuery inWorld(final @NonNull String world) { Preconditions.checkNotNull(world, "World may not be null"); this.plotProvider = new AreaLimitedPlotProvider(this.plotAreaManager.getPlotAreasSet(world)); return this; } /** * Query for plots in specific areas * * @param areas Plot areas * @return The query instance */ public @NonNull PlotQuery inAreas(final @NonNull Collection areas) { Preconditions.checkNotNull(areas, "Areas may not be null"); Preconditions.checkState(!areas.isEmpty(), "At least one area must be provided"); this.plotProvider = new AreaLimitedPlotProvider(Collections.unmodifiableCollection(areas)); return this; } /** * Query for expired plots * * @return The query instance */ public @NonNull PlotQuery expiredPlots() { this.plotProvider = new ExpiredPlotProvider(); return this; } /** * Query for all plots * * @return The query instance */ public @NonNull PlotQuery allPlots() { this.plotProvider = new GlobalPlotProvider(this.plotAreaManager); return this; } /** * Don't query at all * * @return The query instance */ public @NonNull PlotQuery noPlots() { this.plotProvider = new NullProvider(); return this; } /** * Query for plots based on a search term * * @param searchTerm search term to use (uuid, plotID, username) * @return The query instance */ public @NonNull PlotQuery plotsBySearch(final @NonNull String searchTerm) { Preconditions.checkNotNull(searchTerm, "Search term may not be null"); this.plotProvider = new SearchPlotProvider(searchTerm); return this; } /** * Query with a pre-defined result * * @param plot to return when Query is searched * @return The query instance */ public @NonNull PlotQuery withPlot(final @NonNull Plot plot) { Preconditions.checkNotNull(plot, "Plot may not be null"); this.plotProvider = new FixedPlotProvider(plot); return this; } /** * Query for base plots only * * @return The query instance */ public @NonNull PlotQuery whereBasePlot() { return this.addFilter(new PredicateFilter(Plot::isBasePlot)); } /** * Query for plots owned by a specific player * * @param owner Owner UUID * @return The query instance */ public @NonNull PlotQuery ownedBy(final @NonNull UUID owner) { Preconditions.checkNotNull(owner, "Owner may not be null"); return this.addFilter(new OwnerFilter(owner)); } /** * Query for plots owned by a specific player * * @param owner Owner * @return The query instance */ public @NonNull PlotQuery ownedBy(final @NonNull PlotPlayer owner) { Preconditions.checkNotNull(owner, "Owner may not be null"); return this.addFilter(new OwnerFilter(owner.getUUID())); } /** * Query for base plots where one of the merged plots is owned by a specific player * * @param owner Owner UUID * @return The query instance * @since 6.1.0 */ public @NonNull PlotQuery ownersInclude(final @NonNull UUID owner) { Preconditions.checkNotNull(owner, "Owner may not be null"); return this.addFilter(new OwnersIncludeFilter(owner)); } /** * Query for base plots where one of the merged plots is owned by a specific player * * @param owner Owner * @return The query instance * @since 6.1.0 */ public @NonNull PlotQuery ownersInclude(final @NonNull PlotPlayer owner) { Preconditions.checkNotNull(owner, "Owner may not be null"); return this.addFilter(new OwnersIncludeFilter(owner.getUUID())); } /** * Query for plots with a specific alias * * @param alias Plot alias * @return The query instance */ public @NonNull PlotQuery withAlias(final @NonNull String alias) { Preconditions.checkNotNull(alias, "Alias may not be null"); return this.addFilter(new AliasFilter(alias)); } /** * Query for plots with a specific member (added/trusted/owner) * * @param member Member UUID * @return The query instance */ public @NonNull PlotQuery withMember(final @NonNull UUID member) { Preconditions.checkNotNull(member, "Member may not be null"); return this.addFilter(new MemberFilter(member)); } /** * Query for plots that passes a given predicate * * @param predicate Predicate * @return The query instance */ public @NonNull PlotQuery thatPasses(final @NonNull Predicate predicate) { Preconditions.checkNotNull(predicate, "Predicate may not be null"); return this.addFilter(new PredicateFilter(predicate)); } /** * Specify the sorting strategy that will decide how to * sort the results. This only matters if you use {@link #asList()} * * @param strategy Strategy * @return The query instance */ public @NonNull PlotQuery withSortingStrategy(final @NonNull SortingStrategy strategy) { Preconditions.checkNotNull(strategy, "Strategy may not be null"); this.sortingStrategy = strategy; return this; } /** * Use a custom comparator to sort the results * * @param comparator Comparator * @return The query instance */ public @NonNull PlotQuery sorted(final @NonNull Comparator comparator) { Preconditions.checkNotNull(comparator, "Comparator may not be null"); this.sortingStrategy = SortingStrategy.COMPARATOR; this.plotComparator = comparator; return this; } /** * Defines the area around which plots may be sorted, depending on the * sorting strategy * * @param plotArea Plot area * @return The query instance */ public @NonNull PlotQuery relativeToArea(final @NonNull PlotArea plotArea) { Preconditions.checkNotNull(plotArea, "Area may not be null"); this.priorityArea = plotArea; return this; } /** * Get all plots that match the given criteria * * @return Matching plots */ public @NonNull Stream asStream() { return this.asList().stream(); } /** * Get all plots that match the given criteria * * @return Matching plots as a mutable */ public @NonNull List asList() { final List result; if (this.filters.isEmpty()) { result = new ArrayList<>(this.plotProvider.getPlots()); } else { final Collection plots = this.plotProvider.getPlots(); result = new ArrayList<>(plots.size()); outer: for (final Plot plot : plots) { for (final PlotFilter filter : this.filters) { if (!filter.accepts(plot)) { continue outer; } } result.add(plot); } } if (this.sortingStrategy == SortingStrategy.NO_SORTING) { return result; } else if (this.sortingStrategy == SortingStrategy.SORT_BY_TEMP) { return PlotSquared.get().sortPlotsByTemp(result); } else if (this.sortingStrategy == SortingStrategy.SORT_BY_DONE) { result.sort((a, b) -> { String va = a.getFlag(DoneFlag.class); String vb = b.getFlag(DoneFlag.class); if (MathMan.isInteger(va)) { if (MathMan.isInteger(vb)) { return Integer.parseInt(vb) - Integer.parseInt(va); } return -1; } return 1; }); } else if (this.sortingStrategy == SortingStrategy.SORT_BY_RATING) { result.sort((p1, p2) -> { double v1 = 0; int p1s = p1.getSettings().getRatings().size(); int p2s = p2.getRatings().size(); if (!p1.getSettings().getRatings().isEmpty()) { v1 = p1.getRatings().values().stream().mapToDouble(Rating::getAverageRating) .map(av -> av * av).sum(); v1 /= p1s; v1 += p1s; } double v2 = 0; if (!p2.getSettings().getRatings().isEmpty()) { for (Map.Entry entry : p2.getRatings().entrySet()) { double av = entry.getValue().getAverageRating(); v2 += av * av; } v2 /= p2s; v2 += p2s; } if (v2 == v1 && v2 != 0) { return p2s - p1s; } return (int) Math.signum(v2 - v1); }); } else if (this.sortingStrategy == SortingStrategy.SORT_BY_CREATION) { return PlotSquared.get().sortPlots(result, PlotSquared.SortType.CREATION_DATE, this.priorityArea); } else if (this.sortingStrategy == SortingStrategy.COMPARATOR) { result.sort(this.plotComparator); } return result; } /** * Get all plots that match the given criteria * * @return Matching plots as a mutable set */ public @NonNull Set asSet() { return new HashSet<>(this.asList()); } /** * Get all plots that match the given criteria * in the form of a {@link PaginatedPlotResult} * * @param pageSize The size of the pages. Must be positive. * @return Paginated plot result */ public @NonNull PaginatedPlotResult getPaginated(final int pageSize) { Preconditions.checkState(pageSize > 0, "Page size must be greater than 0"); return new PaginatedPlotResult(this.asList(), pageSize); } /** * Get all plots that match the given criteria * * @return Matching plots as an immutable collection */ public @NonNull Collection asCollection() { return this.asList(); } /** * Get the amount of plots contained in the query result * * @return Result count */ public int count() { return this.asList().size(); } /** * Get whether any provided plot matches the given filters. * If no plot was provided, false will be returned. * * @return {@code true} if any provided plot matches the filters. */ public boolean anyMatch() { if (this.filters.isEmpty()) { return !this.plotProvider.getPlots().isEmpty(); } else { final Collection plots = this.plotProvider.getPlots(); outer: for (final Plot plot : plots) { // a plot must pass all filters to match the criteria for (final PlotFilter filter : this.filters) { if (!filter.accepts(plot)) { continue outer; } } return true; // a plot passed all filters, so we have a match } return false; } } @NonNull private PlotQuery addFilter(final @NonNull PlotFilter filter) { this.filters.add(filter); return this; } @NonNull @Override public Iterator iterator() { return this.asCollection().iterator(); } }