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; 020 021import com.google.common.cache.Cache; 022import com.google.common.cache.CacheBuilder; 023import com.plotsquared.core.PlotSquared; 024import com.plotsquared.core.command.Command; 025import com.plotsquared.core.command.CommandCategory; 026import com.plotsquared.core.command.RequiredType; 027import com.plotsquared.core.configuration.Settings; 028import com.plotsquared.core.player.PlotPlayer; 029import com.plotsquared.core.plot.Plot; 030import com.plotsquared.core.plot.PlotArea; 031import com.plotsquared.core.uuid.UUIDMapping; 032import org.checkerframework.checker.nullness.qual.NonNull; 033 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.List; 039import java.util.Locale; 040import java.util.UUID; 041import java.util.concurrent.TimeUnit; 042import java.util.function.Predicate; 043import java.util.stream.Collectors; 044 045/** 046 * Tab completion utilities 047 */ 048public final class TabCompletions { 049 050 private static final Cache<String, List<String>> cachedCompletionValues = 051 CacheBuilder.newBuilder() 052 .expireAfterWrite(Settings.Tab_Completions.CACHE_EXPIRATION, TimeUnit.SECONDS) 053 .build(); 054 055 private static final Command booleanTrueCompletion = new Command(null, false, "true", "", 056 RequiredType.NONE, null 057 ) { 058 }; 059 private static final Command booleanFalseCompletion = new Command(null, false, "false", "", 060 RequiredType.NONE, null 061 ) { 062 }; 063 064 private TabCompletions() { 065 throw new UnsupportedOperationException( 066 "This is a utility class and cannot be instantiated"); 067 } 068 069 /** 070 * Get a list of tab completions corresponding to player names. This uses the UUID pipeline 071 * cache, so it will complete will all names known to PlotSquared 072 * 073 * @param input Command input 074 * @param issuer The player who issued the tab completion 075 * @param existing Players that should not be included in completions 076 * @return List of completions 077 * @since 6.1.3 078 */ 079 public static @NonNull List<Command> completePlayers( 080 final @NonNull PlotPlayer<?> issuer, 081 final @NonNull String input, 082 final @NonNull List<String> existing 083 ) { 084 return completePlayers("players", issuer, input, existing, uuid -> true); 085 } 086 087 /** 088 * Get a list of tab completions corresponding to player names added to the given plot. 089 * 090 * @param issuer The player who issued the tab completion 091 * @param plot Plot to complete added players for 092 * @param input Command input 093 * @param existing Players that should not be included in completions 094 * @return List of completions 095 * @since 6.1.3 096 */ 097 public static @NonNull List<Command> completeAddedPlayers( 098 final @NonNull PlotPlayer<?> issuer, 099 final @NonNull Plot plot, 100 final @NonNull String input, final @NonNull List<String> existing 101 ) { 102 return completePlayers("added" + plot, issuer, input, existing, 103 uuid -> plot.getMembers().contains(uuid) 104 || plot.getTrusted().contains(uuid) 105 || plot.getDenied().contains(uuid) 106 ); 107 } 108 109 public static @NonNull List<Command> completePlayersInPlot( 110 final @NonNull Plot plot, 111 final @NonNull String input, final @NonNull List<String> existing 112 ) { 113 List<String> players = cachedCompletionValues.getIfPresent("inPlot" + plot); 114 if (players == null) { 115 final List<PlotPlayer<?>> inPlot = plot.getPlayersInPlot(); 116 players = new ArrayList<>(inPlot.size()); 117 for (PlotPlayer<?> player : inPlot) { 118 players.add(player.getName()); 119 } 120 cachedCompletionValues.put("inPlot" + plot, players); 121 } 122 return filterCached(players, input, existing); 123 } 124 125 /** 126 * Get a list of completions corresponding to WorldEdit(/FastAsyncWorldEdit) patterns. This uses 127 * WorldEdit's pattern completer internally. 128 * 129 * @param input Command input 130 * @return List of completions 131 */ 132 public static @NonNull List<Command> completePatterns(final @NonNull String input) { 133 return PatternUtil.getSuggestions(input.trim()).stream() 134 .map(value -> value.toLowerCase(Locale.ENGLISH).replace("minecraft:", "")) 135 .filter(value -> value.startsWith(input.toLowerCase(Locale.ENGLISH))) 136 .map(value -> new Command(null, false, value, "", RequiredType.NONE, null) { 137 }).collect(Collectors.toList()); 138 } 139 140 public static @NonNull List<Command> completeBoolean(final @NonNull String input) { 141 if (input.isEmpty()) { 142 return Arrays.asList(booleanTrueCompletion, booleanFalseCompletion); 143 } 144 if ("true".startsWith(input)) { 145 return Collections.singletonList(booleanTrueCompletion); 146 } 147 if ("false".startsWith(input)) { 148 return Collections.singletonList(booleanFalseCompletion); 149 } 150 return Collections.emptyList(); 151 } 152 153 /** 154 * Get a list of integer numbers matching the given input. If the input string 155 * is empty, nothing will be returned. The list is unmodifiable. 156 * 157 * @param input Input to filter with 158 * @param amountLimit Maximum amount of suggestions 159 * @param highestLimit Highest number to include 160 * @return Unmodifiable list of number completions 161 */ 162 public static @NonNull List<Command> completeNumbers( 163 final @NonNull String input, 164 final int amountLimit, final int highestLimit 165 ) { 166 if (input.isEmpty() || input.length() > highestLimit || !MathMan.isInteger(input)) { 167 return Collections.emptyList(); 168 } 169 int offset; 170 try { 171 offset = Integer.parseInt(input) * 10; 172 } catch (NumberFormatException ignored) { 173 return Collections.emptyList(); 174 } 175 final List<String> commands = new ArrayList<>(); 176 for (int i = offset; i < highestLimit && (offset - i + amountLimit) > 0; i++) { 177 commands.add(String.valueOf(i)); 178 } 179 return asCompletions(commands.toArray(new String[0])); 180 } 181 182 /** 183 * Get a list of plot areas matching the given input. 184 * The list is unmodifiable. 185 * 186 * @param input Input to filter with 187 * @return Unmodifiable list of area completions 188 */ 189 public static @NonNull List<Command> completeAreas(final @NonNull String input) { 190 final List<Command> completions = new ArrayList<>(); 191 for (final PlotArea area : PlotSquared.get().getPlotAreaManager().getAllPlotAreas()) { 192 String areaName = area.getWorldName(); 193 if (area.getId() != null) { 194 areaName += ";" + area.getId(); 195 } 196 if (!areaName.toLowerCase().startsWith(input.toLowerCase())) { 197 continue; 198 } 199 completions.add(new Command(null, false, areaName, "", 200 RequiredType.NONE, null 201 ) { 202 }); 203 } 204 return Collections.unmodifiableList(completions); 205 } 206 207 public static @NonNull List<Command> asCompletions(String... toFilter) { 208 final List<Command> completions = new ArrayList<>(); 209 for (String completion : toFilter) { 210 completions.add(new Command(null, false, completion, "", 211 RequiredType.NONE, null 212 ) { 213 }); 214 } 215 return Collections.unmodifiableList(completions); 216 } 217 218 /** 219 * @param cacheIdentifier Cache key 220 * @param issuer The player who issued the tab completion 221 * @param input Command input 222 * @param existing Players that should not be included in completions 223 * @param uuidFilter Filter applied before caching values 224 * @return List of completions 225 * @since 6.1.3 226 */ 227 private static List<Command> completePlayers( 228 final @NonNull String cacheIdentifier, 229 final @NonNull PlotPlayer<?> issuer, 230 final @NonNull String input, final @NonNull List<String> existing, 231 final @NonNull Predicate<UUID> uuidFilter 232 ) { 233 List<String> players; 234 if (Settings.Enabled_Components.EXTENDED_USERNAME_COMPLETION) { 235 players = cachedCompletionValues.getIfPresent(cacheIdentifier); 236 if (players == null) { 237 final Collection<UUIDMapping> mappings = 238 PlotSquared.get().getImpromptuUUIDPipeline().getAllImmediately(); 239 players = new ArrayList<>(mappings.size()); 240 for (final UUIDMapping mapping : mappings) { 241 if (uuidFilter.test(mapping.uuid())) { 242 players.add(mapping.username()); 243 } 244 } 245 cachedCompletionValues.put(cacheIdentifier, players); 246 } 247 } else { 248 final Collection<? extends PlotPlayer<?>> onlinePlayers = PlotSquared.platform().playerManager().getPlayers(); 249 players = new ArrayList<>(onlinePlayers.size()); 250 for (final PlotPlayer<?> player : onlinePlayers) { 251 if (!uuidFilter.test(player.getUUID())) { 252 continue; 253 } 254 if (issuer != null && !issuer.canSee(player)) { 255 continue; 256 } 257 players.add(player.getName()); 258 } 259 } 260 return filterCached(players, input, existing); 261 } 262 263 private static List<Command> filterCached( 264 Collection<String> playerNames, String input, 265 List<String> existing 266 ) { 267 final String processedInput = input.toLowerCase(Locale.ENGLISH); 268 return playerNames.stream().filter(player -> player.toLowerCase(Locale.ENGLISH).startsWith(processedInput)) 269 .filter(player -> !existing.contains(player)).map( 270 player -> new Command(null, false, player, "", RequiredType.NONE, 271 CommandCategory.INFO 272 ) { 273 }) 274 /* If there are more than 200 suggestions, just send the first 200 */ 275 .limit(200) 276 .collect(Collectors.toList()); 277 } 278 279}