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 for plots with a specific alias 224 * 225 * @param alias Plot alias 226 * @return The query instance 227 */ 228 public @NonNull PlotQuery withAlias(final @NonNull String alias) { 229 Preconditions.checkNotNull(alias, "Alias may not be null"); 230 return this.addFilter(new AliasFilter(alias)); 231 } 232 233 /** 234 * Query for plots with a specific member (added/trusted/owner) 235 * 236 * @param member Member UUID 237 * @return The query instance 238 */ 239 public @NonNull PlotQuery withMember(final @NonNull UUID member) { 240 Preconditions.checkNotNull(member, "Member may not be null"); 241 return this.addFilter(new MemberFilter(member)); 242 } 243 244 /** 245 * Query for plots that passes a given predicate 246 * 247 * @param predicate Predicate 248 * @return The query instance 249 */ 250 public @NonNull PlotQuery thatPasses(final @NonNull Predicate<Plot> predicate) { 251 Preconditions.checkNotNull(predicate, "Predicate may not be null"); 252 return this.addFilter(new PredicateFilter(predicate)); 253 } 254 255 /** 256 * Specify the sorting strategy that will decide how to 257 * sort the results. This only matters if you use {@link #asList()} 258 * 259 * @param strategy Strategy 260 * @return The query instance 261 */ 262 public @NonNull PlotQuery withSortingStrategy(final @NonNull SortingStrategy strategy) { 263 Preconditions.checkNotNull(strategy, "Strategy may not be null"); 264 this.sortingStrategy = strategy; 265 return this; 266 } 267 268 /** 269 * Use a custom comparator to sort the results 270 * 271 * @param comparator Comparator 272 * @return The query instance 273 */ 274 public @NonNull PlotQuery sorted(final @NonNull Comparator<Plot> comparator) { 275 Preconditions.checkNotNull(comparator, "Comparator may not be null"); 276 this.sortingStrategy = SortingStrategy.COMPARATOR; 277 this.plotComparator = comparator; 278 return this; 279 } 280 281 /** 282 * Defines the area around which plots may be sorted, depending on the 283 * sorting strategy 284 * 285 * @param plotArea Plot area 286 * @return The query instance 287 */ 288 public @NonNull PlotQuery relativeToArea(final @NonNull PlotArea plotArea) { 289 Preconditions.checkNotNull(plotArea, "Area may not be null"); 290 this.priorityArea = plotArea; 291 return this; 292 } 293 294 /** 295 * Get all plots that match the given criteria 296 * 297 * @return Matching plots 298 */ 299 public @NonNull Stream<Plot> asStream() { 300 return this.asList().stream(); 301 } 302 303 /** 304 * Get all plots that match the given criteria 305 * 306 * @return Matching plots as a mutable 307 */ 308 public @NonNull List<Plot> asList() { 309 final List<Plot> result; 310 if (this.filters.isEmpty()) { 311 result = new ArrayList<>(this.plotProvider.getPlots()); 312 } else { 313 final Collection<Plot> plots = this.plotProvider.getPlots(); 314 result = new ArrayList<>(plots.size()); 315 outer: 316 for (final Plot plot : plots) { 317 for (final PlotFilter filter : this.filters) { 318 if (!filter.accepts(plot)) { 319 continue outer; 320 } 321 } 322 result.add(plot); 323 } 324 } 325 if (this.sortingStrategy == SortingStrategy.NO_SORTING) { 326 return result; 327 } else if (this.sortingStrategy == SortingStrategy.SORT_BY_TEMP) { 328 return PlotSquared.get().sortPlotsByTemp(result); 329 } else if (this.sortingStrategy == SortingStrategy.SORT_BY_DONE) { 330 result.sort((a, b) -> { 331 String va = a.getFlag(DoneFlag.class); 332 String vb = b.getFlag(DoneFlag.class); 333 if (MathMan.isInteger(va)) { 334 if (MathMan.isInteger(vb)) { 335 return Integer.parseInt(vb) - Integer.parseInt(va); 336 } 337 return -1; 338 } 339 return 1; 340 }); 341 } else if (this.sortingStrategy == SortingStrategy.SORT_BY_RATING) { 342 result.sort((p1, p2) -> { 343 double v1 = 0; 344 int p1s = p1.getSettings().getRatings().size(); 345 int p2s = p2.getRatings().size(); 346 if (!p1.getSettings().getRatings().isEmpty()) { 347 v1 = p1.getRatings().values().stream().mapToDouble(Rating::getAverageRating) 348 .map(av -> av * av).sum(); 349 v1 /= p1s; 350 v1 += p1s; 351 } 352 double v2 = 0; 353 if (!p2.getSettings().getRatings().isEmpty()) { 354 for (Map.Entry<UUID, Rating> entry : p2.getRatings().entrySet()) { 355 double av = entry.getValue().getAverageRating(); 356 v2 += av * av; 357 } 358 v2 /= p2s; 359 v2 += p2s; 360 } 361 if (v2 == v1 && v2 != 0) { 362 return p2s - p1s; 363 } 364 return (int) Math.signum(v2 - v1); 365 }); 366 } else if (this.sortingStrategy == SortingStrategy.SORT_BY_CREATION) { 367 return PlotSquared.get().sortPlots(result, PlotSquared.SortType.CREATION_DATE, this.priorityArea); 368 } else if (this.sortingStrategy == SortingStrategy.COMPARATOR) { 369 result.sort(this.plotComparator); 370 } 371 return result; 372 } 373 374 /** 375 * Get all plots that match the given criteria 376 * 377 * @return Matching plots as a mutable set 378 */ 379 public @NonNull Set<Plot> asSet() { 380 return new HashSet<>(this.asList()); 381 } 382 383 /** 384 * Get all plots that match the given criteria 385 * in the form of a {@link PaginatedPlotResult} 386 * 387 * @param pageSize The size of the pages. Must be positive. 388 * @return Paginated plot result 389 */ 390 public @NonNull PaginatedPlotResult getPaginated(final int pageSize) { 391 Preconditions.checkState(pageSize > 0, "Page size must be greater than 0"); 392 return new PaginatedPlotResult(this.asList(), pageSize); 393 } 394 395 /** 396 * Get all plots that match the given criteria 397 * 398 * @return Matching plots as an immutable collection 399 */ 400 public @NonNull Collection<Plot> asCollection() { 401 return this.asList(); 402 } 403 404 /** 405 * Get the amount of plots contained in the query result 406 * 407 * @return Result count 408 */ 409 public int count() { 410 return this.asList().size(); 411 } 412 413 /** 414 * Get whether any provided plot matches the given filters. 415 * If no plot was provided, false will be returned. 416 * 417 * @return {@code true} if any provided plot matches the filters. 418 */ 419 public boolean anyMatch() { 420 if (this.filters.isEmpty()) { 421 return !this.plotProvider.getPlots().isEmpty(); 422 } else { 423 final Collection<Plot> plots = this.plotProvider.getPlots(); 424 outer: 425 for (final Plot plot : plots) { 426 // a plot must pass all filters to match the criteria 427 for (final PlotFilter filter : this.filters) { 428 if (!filter.accepts(plot)) { 429 continue outer; 430 } 431 } 432 return true; // a plot passed all filters, so we have a match 433 } 434 return false; 435 } 436 } 437 438 @NonNull 439 private PlotQuery addFilter(final @NonNull PlotFilter filter) { 440 this.filters.add(filter); 441 return this; 442 } 443 444 @NonNull 445 @Override 446 public Iterator<Plot> iterator() { 447 return this.asCollection().iterator(); 448 } 449 450}