001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.util.query;
020
021import com.google.common.base.Preconditions;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.player.PlotPlayer;
024import com.plotsquared.core.plot.Plot;
025import com.plotsquared.core.plot.PlotArea;
026import com.plotsquared.core.plot.Rating;
027import com.plotsquared.core.plot.flag.implementations.DoneFlag;
028import com.plotsquared.core.plot.world.PlotAreaManager;
029import com.plotsquared.core.util.MathMan;
030import org.checkerframework.checker.nullness.qual.NonNull;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Comparator;
036import java.util.HashSet;
037import java.util.Iterator;
038import java.util.LinkedList;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042import java.util.UUID;
043import java.util.function.Predicate;
044import java.util.stream.Stream;
045
046/**
047 * This represents a plot query, and can be used to
048 * search for plots matching certain criteria.
049 * <p>
050 * The queries can be reused as no results are stored
051 * in the query itself
052 */
053public final class PlotQuery implements Iterable<Plot> {
054
055    private final Collection<PlotFilter> filters = new LinkedList<>();
056    private final PlotAreaManager plotAreaManager;
057    private PlotProvider plotProvider;
058    private SortingStrategy sortingStrategy = SortingStrategy.NO_SORTING;
059    private PlotArea priorityArea;
060    private Comparator<Plot> plotComparator;
061
062    private PlotQuery(final @NonNull PlotAreaManager plotAreaManager) {
063        this.plotAreaManager = plotAreaManager;
064        this.plotProvider = new GlobalPlotProvider(plotAreaManager);
065    }
066
067    /**
068     * Create a new plot query instance
069     *
070     * @return New query
071     */
072    public static PlotQuery newQuery() {
073        return new PlotQuery(PlotSquared.get().getPlotAreaManager());
074    }
075
076    /**
077     * Query for plots in a single area
078     *
079     * @param area Area
080     * @return The query instance
081     */
082    public @NonNull PlotQuery inArea(final @NonNull PlotArea area) {
083        Preconditions.checkNotNull(area, "Area may not be null");
084        this.plotProvider = new AreaLimitedPlotProvider(Collections.singletonList(area));
085        return this;
086    }
087
088    /**
089     * Query for plots in all areas in a world
090     *
091     * @param world World name
092     * @return The query instance
093     */
094    public @NonNull PlotQuery inWorld(final @NonNull String world) {
095        Preconditions.checkNotNull(world, "World may not be null");
096        this.plotProvider = new AreaLimitedPlotProvider(this.plotAreaManager.getPlotAreasSet(world));
097        return this;
098    }
099
100    /**
101     * Query for plots in specific areas
102     *
103     * @param areas Plot areas
104     * @return The query instance
105     */
106    public @NonNull PlotQuery inAreas(final @NonNull Collection<PlotArea> areas) {
107        Preconditions.checkNotNull(areas, "Areas may not be null");
108        Preconditions.checkState(!areas.isEmpty(), "At least one area must be provided");
109        this.plotProvider = new AreaLimitedPlotProvider(Collections.unmodifiableCollection(areas));
110        return this;
111    }
112
113    /**
114     * Query for expired plots
115     *
116     * @return The query instance
117     */
118    public @NonNull PlotQuery expiredPlots() {
119        this.plotProvider = new ExpiredPlotProvider();
120        return this;
121    }
122
123    /**
124     * Query for all plots
125     *
126     * @return The query instance
127     */
128    public @NonNull PlotQuery allPlots() {
129        this.plotProvider = new GlobalPlotProvider(this.plotAreaManager);
130        return this;
131    }
132
133    /**
134     * Don't query at all
135     *
136     * @return The query instance
137     */
138    public @NonNull PlotQuery noPlots() {
139        this.plotProvider = new NullProvider();
140        return this;
141    }
142
143    /**
144     * Query for plots based on a search term
145     *
146     * @param searchTerm search term to use (uuid, plotID, username)
147     * @return The query instance
148     */
149    public @NonNull PlotQuery plotsBySearch(final @NonNull String searchTerm) {
150        Preconditions.checkNotNull(searchTerm, "Search term may not be null");
151        this.plotProvider = new SearchPlotProvider(searchTerm);
152        return this;
153    }
154
155    /**
156     * Query with a pre-defined result
157     *
158     * @param plot to return when Query is searched
159     * @return The query instance
160     */
161    public @NonNull PlotQuery withPlot(final @NonNull Plot plot) {
162        Preconditions.checkNotNull(plot, "Plot may not be null");
163        this.plotProvider = new FixedPlotProvider(plot);
164        return this;
165    }
166
167    /**
168     * Query for base plots only
169     *
170     * @return The query instance
171     */
172    public @NonNull PlotQuery whereBasePlot() {
173        return this.addFilter(new PredicateFilter(Plot::isBasePlot));
174    }
175
176    /**
177     * Query for plots owned by a specific player
178     *
179     * @param owner Owner UUID
180     * @return The query instance
181     */
182    public @NonNull PlotQuery ownedBy(final @NonNull UUID owner) {
183        Preconditions.checkNotNull(owner, "Owner may not be null");
184        return this.addFilter(new OwnerFilter(owner));
185    }
186
187    /**
188     * Query for plots owned by a specific player
189     *
190     * @param owner Owner
191     * @return The query instance
192     */
193    public @NonNull PlotQuery ownedBy(final @NonNull PlotPlayer<?> owner) {
194        Preconditions.checkNotNull(owner, "Owner may not be null");
195        return this.addFilter(new OwnerFilter(owner.getUUID()));
196    }
197
198    /**
199     * Query for base plots where one of the merged plots is owned by a specific player
200     *
201     * @param owner Owner UUID
202     * @return The query instance
203     * @since 6.1.0
204     */
205    public @NonNull PlotQuery ownersInclude(final @NonNull UUID owner) {
206        Preconditions.checkNotNull(owner, "Owner may not be null");
207        return this.addFilter(new OwnersIncludeFilter(owner));
208    }
209
210    /**
211     * Query for base plots where one of the merged plots is owned by a specific player
212     *
213     * @param owner Owner
214     * @return The query instance
215     * @since 6.1.0
216     */
217    public @NonNull PlotQuery ownersInclude(final @NonNull PlotPlayer<?> owner) {
218        Preconditions.checkNotNull(owner, "Owner may not be null");
219        return this.addFilter(new OwnersIncludeFilter(owner.getUUID()));
220    }
221
222    /**
223     * Query only for plots that have an owner
224     *
225     * @return The query instance
226     * @since 7.2.1
227     */
228
229    public @NonNull PlotQuery hasOwner() {
230        return this.addFilter(new HasOwnerFilter());
231    }
232    /**
233     * Query for plots with a specific alias
234     *
235     * @param alias Plot alias
236     * @return The query instance
237     */
238    public @NonNull PlotQuery withAlias(final @NonNull String alias) {
239        Preconditions.checkNotNull(alias, "Alias may not be null");
240        return this.addFilter(new AliasFilter(alias));
241    }
242
243    /**
244     * Query for plots with a specific member (added/trusted/owner)
245     *
246     * @param member Member UUID
247     * @return The query instance
248     */
249    public @NonNull PlotQuery withMember(final @NonNull UUID member) {
250        Preconditions.checkNotNull(member, "Member may not be null");
251        return this.addFilter(new MemberFilter(member));
252    }
253
254    /**
255     * Query for plots that passes a given predicate
256     *
257     * @param predicate Predicate
258     * @return The query instance
259     */
260    public @NonNull PlotQuery thatPasses(final @NonNull Predicate<Plot> predicate) {
261        Preconditions.checkNotNull(predicate, "Predicate may not be null");
262        return this.addFilter(new PredicateFilter(predicate));
263    }
264
265    /**
266     * Specify the sorting strategy that will decide how to
267     * sort the results. This only matters if you use {@link #asList()}
268     *
269     * @param strategy Strategy
270     * @return The query instance
271     */
272    public @NonNull PlotQuery withSortingStrategy(final @NonNull SortingStrategy strategy) {
273        Preconditions.checkNotNull(strategy, "Strategy may not be null");
274        this.sortingStrategy = strategy;
275        return this;
276    }
277
278    /**
279     * Use a custom comparator to sort the results
280     *
281     * @param comparator Comparator
282     * @return The query instance
283     */
284    public @NonNull PlotQuery sorted(final @NonNull Comparator<Plot> comparator) {
285        Preconditions.checkNotNull(comparator, "Comparator may not be null");
286        this.sortingStrategy = SortingStrategy.COMPARATOR;
287        this.plotComparator = comparator;
288        return this;
289    }
290
291    /**
292     * Defines the area around which plots may be sorted, depending on the
293     * sorting strategy
294     *
295     * @param plotArea Plot area
296     * @return The query instance
297     */
298    public @NonNull PlotQuery relativeToArea(final @NonNull PlotArea plotArea) {
299        Preconditions.checkNotNull(plotArea, "Area may not be null");
300        this.priorityArea = plotArea;
301        return this;
302    }
303
304    /**
305     * Get all plots that match the given criteria
306     *
307     * @return Matching plots
308     */
309    public @NonNull Stream<Plot> asStream() {
310        return this.asList().stream();
311    }
312
313    /**
314     * Get all plots that match the given criteria
315     *
316     * @return Matching plots as a mutable
317     */
318    public @NonNull List<Plot> asList() {
319        final List<Plot> result;
320        if (this.filters.isEmpty()) {
321            result = new ArrayList<>(this.plotProvider.getPlots());
322        } else {
323            final Collection<Plot> plots = this.plotProvider.getPlots();
324            result = new ArrayList<>(plots.size());
325            outer:
326            for (final Plot plot : plots) {
327                for (final PlotFilter filter : this.filters) {
328                    if (!filter.accepts(plot)) {
329                        continue outer;
330                    }
331                }
332                result.add(plot);
333            }
334        }
335        if (this.sortingStrategy == SortingStrategy.NO_SORTING) {
336            return result;
337        } else if (this.sortingStrategy == SortingStrategy.SORT_BY_TEMP) {
338            return PlotSquared.get().sortPlotsByTemp(result);
339        } else if (this.sortingStrategy == SortingStrategy.SORT_BY_DONE) {
340            result.sort((a, b) -> {
341                String va = a.getFlag(DoneFlag.class);
342                String vb = b.getFlag(DoneFlag.class);
343                if (MathMan.isInteger(va)) {
344                    if (MathMan.isInteger(vb)) {
345                        return Integer.parseInt(vb) - Integer.parseInt(va);
346                    }
347                    return -1;
348                }
349                return 1;
350            });
351        } else if (this.sortingStrategy == SortingStrategy.SORT_BY_RATING) {
352            result.sort((p1, p2) -> {
353                double v1 = 0;
354                int p1s = p1.getSettings().getRatings().size();
355                int p2s = p2.getRatings().size();
356                if (!p1.getSettings().getRatings().isEmpty()) {
357                    v1 = p1.getRatings().values().stream().mapToDouble(Rating::getAverageRating)
358                            .map(av -> av * av).sum();
359                    v1 /= p1s;
360                    v1 += p1s;
361                }
362                double v2 = 0;
363                if (!p2.getSettings().getRatings().isEmpty()) {
364                    for (Map.Entry<UUID, Rating> entry : p2.getRatings().entrySet()) {
365                        double av = entry.getValue().getAverageRating();
366                        v2 += av * av;
367                    }
368                    v2 /= p2s;
369                    v2 += p2s;
370                }
371                if (v2 == v1 && v2 != 0) {
372                    return p2s - p1s;
373                }
374                return (int) Math.signum(v2 - v1);
375            });
376        } else if (this.sortingStrategy == SortingStrategy.SORT_BY_CREATION) {
377            return PlotSquared.get().sortPlots(result, PlotSquared.SortType.CREATION_DATE, this.priorityArea);
378        } else if (this.sortingStrategy == SortingStrategy.COMPARATOR) {
379            result.sort(this.plotComparator);
380        }
381        return result;
382    }
383
384    /**
385     * Get all plots that match the given criteria
386     *
387     * @return Matching plots as a mutable set
388     */
389    public @NonNull Set<Plot> asSet() {
390        return new HashSet<>(this.asList());
391    }
392
393    /**
394     * Get all plots that match the given criteria
395     * in the form of a {@link PaginatedPlotResult}
396     *
397     * @param pageSize The size of the pages. Must be positive.
398     * @return Paginated plot result
399     */
400    public @NonNull PaginatedPlotResult getPaginated(final int pageSize) {
401        Preconditions.checkState(pageSize > 0, "Page size must be greater than 0");
402        return new PaginatedPlotResult(this.asList(), pageSize);
403    }
404
405    /**
406     * Get all plots that match the given criteria
407     *
408     * @return Matching plots as an immutable collection
409     */
410    public @NonNull Collection<Plot> asCollection() {
411        return this.asList();
412    }
413
414    /**
415     * Get the amount of plots contained in the query result
416     *
417     * @return Result count
418     */
419    public int count() {
420        return this.asList().size();
421    }
422
423    /**
424     * Get whether any provided plot matches the given filters.
425     * If no plot was provided, false will be returned.
426     *
427     * @return {@code true} if any provided plot matches the filters.
428     */
429    public boolean anyMatch() {
430        if (this.filters.isEmpty()) {
431            return !this.plotProvider.getPlots().isEmpty();
432        } else {
433            final Collection<Plot> plots = this.plotProvider.getPlots();
434            outer:
435            for (final Plot plot : plots) {
436                // a plot must pass all filters to match the criteria
437                for (final PlotFilter filter : this.filters) {
438                    if (!filter.accepts(plot)) {
439                        continue outer;
440                    }
441                }
442                return true; // a plot passed all filters, so we have a match
443            }
444            return false;
445        }
446    }
447
448    @NonNull
449    private PlotQuery addFilter(final @NonNull PlotFilter filter) {
450        this.filters.add(filter);
451        return this;
452    }
453
454    @NonNull
455    @Override
456    public Iterator<Plot> iterator() {
457        return this.asCollection().iterator();
458    }
459
460}