Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of a trie data structure for residents, towns, and nations #3712

Merged
merged 29 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3a1d80f
Functional trie structure implemented with /nation tab completes
griffinht Feb 21, 2020
5a488d8
Trie.java
griffinht Feb 21, 2020
987b0e0
Made applicable commands use trie structure
griffinht Feb 21, 2020
5d973ec
Implemented getStringsFromKey to replace getTrieNodesFromKey
griffinht Feb 21, 2020
2d46e6c
Unused imports, made addKey void
griffinht Feb 21, 2020
eac3835
Comments, cleanup, and rearranging
griffinht Feb 22, 2020
2be8226
Typo, remove MAX_RETURNS because nothing will get that high (see usages)
griffinht Feb 22, 2020
e0f331e
Removed test code, unused import
griffinht Feb 22, 2020
605ef9d
Unused import
griffinht Feb 22, 2020
79a344f
Removed TrieUtil.java and moved to BaseCommand
griffinht Feb 22, 2020
b082d32
Grammar/style javadoc change
griffinht Feb 22, 2020
8431389
Only check if arg isn't empty except for world
griffinht Feb 22, 2020
ff6a498
Grammar/style change
griffinht Feb 22, 2020
50f2861
Building trie structures logs with TownyMessaging.sendDebugMsg
griffinht Feb 22, 2020
74b462a
Added removeKey to Trie, includes test code
griffinht Feb 22, 2020
e1b8fdb
Added limit to Trie, removed test code, removed limit from BaseCommand
griffinht Feb 22, 2020
566fda3
Removed timing code
griffinht Feb 22, 2020
fbb5584
Update trie on renames and removes
griffinht Feb 22, 2020
b8721d5
Added missing support for town/nation deletion
griffinht Feb 22, 2020
51612d1
Removed/cleaned up some stuff,
suneettipirneni Feb 22, 2020
196075b
More cleanup
griffinht Feb 22, 2020
137bc2d
Spacing
griffinht Feb 22, 2020
c48c392
Removal of unnecessary debug lines, whitespace
griffinht Feb 22, 2020
28c59a7
Made the Trie generation thread scheduled in Bukkit
suneettipirneni Feb 22, 2020
c641d51
Only loop through keys, spacing
griffinht Feb 22, 2020
16b5bf3
add /r resident completion
suneettipirneni Feb 22, 2020
c8fd938
Fixed /r completions.
suneettipirneni Feb 22, 2020
a51a9e3
Resident tab complete uses methods from BaseCommand, and doesn't alwa…
griffinht Feb 22, 2020
5669fe5
Merge remote-tracking branch 'origin/feature/trie-data-structure' int…
griffinht Feb 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions src/com/palmergames/bukkit/towny/TownyUniverse.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.palmergames.bukkit.towny.war.eventwar.War;
import com.palmergames.bukkit.util.BukkitTools;
import com.palmergames.util.FileMgmt;
import com.palmergames.util.Trie;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
Expand All @@ -30,10 +31,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import java.util.HashMap;

/**
* Towny's class for internal API Methods
Expand All @@ -47,8 +49,11 @@ public class TownyUniverse {
private final Towny towny;

private final ConcurrentHashMap<String, Resident> residents = new ConcurrentHashMap<>();
private final Trie residentsTrie = new Trie();
private final ConcurrentHashMap<String, Town> towns = new ConcurrentHashMap<>();
private final Trie townsTrie = new Trie();
private final ConcurrentHashMap<String, Nation> nations = new ConcurrentHashMap<>();
private final Trie nationsTrie = new Trie();
private final ConcurrentHashMap<String, TownyWorld> worlds = new ConcurrentHashMap<>();
private final HashMap<String, CustomDataField> registeredMetadata = new HashMap<>();
private final List<Resident> jailedResidents = new ArrayList<>();
Expand Down Expand Up @@ -145,6 +150,27 @@ boolean loadSettings() {
}
towny.saveResource("outpostschecked.txt", false);
}
new Thread(() -> {
System.out.println("[Towny] Building trie structures...");
long start = System.nanoTime();

for (Map.Entry<String, Resident> entry:residents.entrySet()) {
residentsTrie.addKey(entry.getKey());
}
System.out.println("[Towny] Built trie for "+residents.size()+" residents in "+(System.nanoTime()-start)/1000000+"ms");
start = System.nanoTime();

for (Map.Entry<String, Town> entry:towns.entrySet()) {
townsTrie.addKey(entry.getKey());
}
System.out.println("[Towny] Built trie for "+towns.size()+" towns in "+(System.nanoTime()-start)/1000000+"ms");
start = System.nanoTime();

for (Map.Entry<String, Nation> entry:nations.entrySet()) {
nationsTrie.addKey(entry.getKey());
}
System.out.println("[Towny] Built trie for "+nations.size()+" nations in "+(System.nanoTime()-start)/1000000+"ms");
}).start();
return true;
}

Expand Down Expand Up @@ -255,10 +281,18 @@ public ConcurrentHashMap<String, Nation> getNationsMap() {
return nations;
}

public Trie getNationsTrie() {
return nationsTrie;
}

public ConcurrentHashMap<String, Resident> getResidentMap() {
return residents;
}


public Trie getResidentsTrie() {
return residentsTrie;
}

public List<Resident> getJailedResidentMap() {
return jailedResidents;
}
Expand All @@ -267,6 +301,10 @@ public ConcurrentHashMap<String, Town> getTownsMap() {
return towns;
}

public Trie getTownsTrie() {
return townsTrie;
}

public ConcurrentHashMap<String, TownyWorld> getWorldMap() {
return worlds;
}
Expand Down
84 changes: 53 additions & 31 deletions src/com/palmergames/bukkit/towny/command/BaseCommand.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,77 @@
package com.palmergames.bukkit.towny.command;

import com.palmergames.bukkit.towny.TownyUniverse;
import com.palmergames.bukkit.towny.object.Nation;
import com.palmergames.bukkit.towny.object.Resident;
import com.palmergames.bukkit.towny.object.Town;
import com.palmergames.bukkit.towny.utils.NameUtil;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;

import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;


public class BaseCommand implements TabCompleter{


@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
LinkedList<String> output = new LinkedList<>();
String lastArg = "";
return getTownyStartingWith(args[args.length - 1], "rtn");
}

/**
* Returns a List<String> containing strings of resident, town, and/or nation names that match with arg.
* Can check for multiple types, for example "rt" would check for residents and towns but not nations or worlds.
*
* @param arg the string to match with the chosen type
* @param type the type of Towny object to check for, can be r(esident), t(own), n(ation), w(orld), or any combination of those to check
* @return Matches for the arg with the chosen type
*/
static List<String> getTownyStartingWith(String arg, String type) {
//long start = System.nanoTime();
List<String> matches = new ArrayList<>();
TownyUniverse townyUniverse = TownyUniverse.getInstance();

// Get the last argument
if (args.length > 0) {
lastArg = args[args.length - 1].toLowerCase();
}

if (!lastArg.equalsIgnoreCase("")) {
// Match nations
for (Nation nation : townyUniverse.getDataSource().getNations()) {
if (nation.getName().toLowerCase().startsWith(lastArg)) {
output.add(nation.getName());
}

if (arg.length() > 0) { // An empty arg means checking all entries which can be very slow
if (type.contains("r")) {
matches.addAll(townyUniverse.getResidentsTrie().getStringsFromKey(arg));
}
// Match towns
for (Town town : townyUniverse.getDataSource().getTowns()) {
if (town.getName().toLowerCase().startsWith(lastArg)) {
output.add(town.getName());
}

if (type.contains("t")) {
matches.addAll(townyUniverse.getTownsTrie().getStringsFromKey(arg));
}
// Match residents
for (Resident resident : townyUniverse.getDataSource().getResidents()) {
if (resident.getName().toLowerCase().startsWith(lastArg)) {
output.add(resident.getName());
}


if (type.contains("n")) {
matches.addAll(townyUniverse.getNationsTrie().getStringsFromKey(arg));
}
}

if (type.contains("w")) { // There aren't many worlds so check even if arg is empty
matches.addAll(NameUtil.filterByStart(NameUtil.getNames(townyUniverse.getWorldMap().values()), arg));
}

//System.out.println("Found "+matches.size()+" for "+type+" in "+(float)(System.nanoTime()-start)/1000000+"ms");
griffinht marked this conversation as resolved.
Show resolved Hide resolved
return matches;
}

/**
* Checks if arg starts with filters, if not returns matches from {@link #getTownyStartingWith(String, String)}.
* Add a "+" to the type to return both cases
*
* @param filters the strings to filter arg with
* @param arg the string to check with filters and possibly match with Towny objects if no filters are found
* @param type the type of check to use, see {@link #getTownyStartingWith(String, String)} for possible types. Add "+" to check for both filters and {@link #getTownyStartingWith(String, String)}
* @return Matches for the arg filtered by filters or checked with type
*/
static List<String> filterByStartOrGetTownyStartingWith(List<String> filters, String arg, String type) {
List<String> filtered = NameUtil.filterByStart(filters, arg);
if (type.contains("+")) {
filtered.addAll(getTownyStartingWith(arg, type));
return filtered;
} else {
if (filtered.size() > 0) {
return filtered;
} else {
return getTownyStartingWith(arg, type);
}
}
return output;
}
}
17 changes: 6 additions & 11 deletions src/com/palmergames/bukkit/towny/command/NationCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,12 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
case "spawn":
case "merge":
if (args.length == 2) {
return NameUtil.getNationNamesStartingWith(args[1]);
return getTownyStartingWith(args[1], "n");
}
break;
case "add":
case "kick":
return NameUtil.getTownNamesStartingWith(args[args.length - 1]);
return getTownyStartingWith(args[args.length - 1], "t");
case "ally":
if (args.length == 2) {
return NameUtil.filterByStart(nationAllyTabCompletes, args[1]);
Expand All @@ -268,7 +268,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
} catch (TownyException ignored) {}
} else {
// Otherwise return possible nations to send invites to
return NameUtil.getNationNamesStartingWith(args[args.length - 1]);
return getTownyStartingWith(args[args.length - 1], "n");
}
case "remove":
// Return current allies to remove
Expand Down Expand Up @@ -309,7 +309,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
} else if (args.length == 3){
switch (args[1].toLowerCase()) {
case "add":
return NameUtil.getNationNamesStartingWith(args[2]);
return getTownyStartingWith(args[2], "n");
case "remove":
// Return enemies of nation
try {
Expand Down Expand Up @@ -338,20 +338,15 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
if (nationNames.size() > 0) {
return nationNames;
} else {
return NameUtil.getNationNamesStartingWith(args[0]);
return getTownyStartingWith(args[0], "n");
}
}
}
}
} else {
// Console
if (args.length == 1) {
List<String> returnValue = NameUtil.filterByStart(nationConsoleTabCompletes, args[0]);
if (returnValue.size() > 0) {
return returnValue;
} else {
return NameUtil.getNationNamesStartingWith(args[0]);
}
return filterByStartOrGetTownyStartingWith(nationConsoleTabCompletes, args[0], "n");
}
}

Expand Down
8 changes: 3 additions & 5 deletions src/com/palmergames/bukkit/towny/command/TownCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,13 @@ public TownCommand(Towny instance) {
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {

if (args.length == 1) {
List<String> townNames = NameUtil.getTownNames();
townNames.addAll(townTabCompletes);
return NameUtil.filterByStart(townNames, args[0]);
return filterByStartOrGetTownyStartingWith(townTabCompletes, args[0], "t");
}

if (args.length == 2) {
switch (args[0].toLowerCase()) {
case "spawn":
return NameUtil.getTownNamesStartingWith(args[1]);
return getTownyStartingWith(args[1], "t");
case "set":
return NameUtil.filterByStart(townSetTabCompletes, args[1]);
case "rank":
Expand Down Expand Up @@ -259,7 +257,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
switch (args[1].toLowerCase()) {
case "remove":
case "add":
return NameUtil.getNationNamesStartingWith(args[2]);
return getTownyStartingWith(args[2], "r"); // Not sure why this was set to nation previously, it should only be residents of player's town
case "perm":
return NameUtil.filterByStart(townPermTabCompletes, args[2]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,11 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
)), args[1]);
case "town":
case "givebonus":
return NameUtil.getTownNamesStartingWith(args[1]);
return getTownyStartingWith(args[1], "t");
case "resident":
return null;
case "nation":
return NameUtil.getNationNamesStartingWith(args[1]);
return getTownyStartingWith(args[1], "n");
case "toggle":
return NameUtil.filterByStart(adminToggleTabCompletes, args[1]);
case "set":
Expand Down Expand Up @@ -252,7 +252,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
case "plot":
case "surname":
case "title":
return NameUtil.getTownNamesStartingWith(args[2]);
return getTownyStartingWith(args[2], "t");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String commandLabel,
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
List<String> worldNames = NameUtil.getWorldNames();
worldNames.addAll(townyWorldTabCompletes);
return NameUtil.filterByStart(worldNames, args[0]);
return filterByStartOrGetTownyStartingWith(townyWorldTabCompletes, args[0], "+w");
}

if (args.length == 2) {
Expand Down
3 changes: 3 additions & 0 deletions src/com/palmergames/bukkit/towny/db/TownyDatabaseHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ public void newResident(String name) throws AlreadyRegisteredException, NotRegis
throw new AlreadyRegisteredException("A resident with the name " + filteredName + " is already in use.");

universe.getResidentMap().put(filteredName.toLowerCase(), new Resident(filteredName));
universe.getResidentsTrie().addKey(filteredName);
}

@Override
Expand All @@ -480,6 +481,7 @@ public void newTown(String name) throws AlreadyRegisteredException, NotRegistere
throw new AlreadyRegisteredException("The town " + filteredName + " is already in use.");

universe.getTownsMap().put(filteredName.toLowerCase(), new Town(filteredName));
universe.getTownsTrie().addKey(filteredName);

} finally {
lock.unlock();
Expand All @@ -504,6 +506,7 @@ public void newNation(String name) throws AlreadyRegisteredException, NotRegiste
throw new AlreadyRegisteredException("The nation " + filteredName + " is already in use.");

universe.getNationsMap().put(filteredName.toLowerCase(), new Nation(filteredName));
universe.getNationsTrie().addKey(filteredName);

} finally {
lock.unlock();
Expand Down
Loading