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}