Skip to content

Commit cebe28e

Browse files
MiniDiggeraikar
authored andcommitted
[WIP] Add dependency injection functionality, Closes #85 (#86)
* misc improvements (as suggested by intellij) * first draft of the DI functionality (#85) * address review * reenabled disabled inspections * overload registerDependency method * inject fields of superclasses two and remove invalid sponge default dependency
1 parent 23e8858 commit cebe28e

File tree

16 files changed

+232
-23
lines changed

16 files changed

+232
-23
lines changed

bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public BukkitCommandCompletions(BukkitCommandManager manager) {
7474

7575
Player senderPlayer = sender instanceof Player ? (Player) sender : null;
7676

77-
ArrayList<String> matchedPlayers = new ArrayList<String>();
77+
ArrayList<String> matchedPlayers = new ArrayList<>();
7878
for (Player player : Bukkit.getOnlinePlayers()) {
7979
String name = player.getName();
8080
if ((senderPlayer == null || senderPlayer.canSee(player)) && StringUtil.startsWithIgnoreCase(name, c.getInput())) {

bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@
3535
import org.bukkit.command.CommandSender;
3636
import org.bukkit.command.SimpleCommandMap;
3737
import org.bukkit.entity.Player;
38+
import org.bukkit.inventory.ItemFactory;
3839
import org.bukkit.plugin.Plugin;
40+
import org.bukkit.plugin.PluginManager;
41+
import org.bukkit.plugin.java.JavaPlugin;
42+
import org.bukkit.scheduler.BukkitScheduler;
3943
import org.bukkit.scheduler.BukkitTask;
44+
import org.bukkit.scoreboard.ScoreboardManager;
4045
import org.jetbrains.annotations.NotNull;
4146

4247
import java.lang.reflect.Field;
@@ -95,6 +100,15 @@ public BukkitCommandManager(Plugin plugin) {
95100
}
96101
Bukkit.getOnlinePlayers().forEach(this::readPlayerLocale);
97102
}, 5, 5);
103+
104+
registerDependency(plugin.getClass(), plugin);
105+
registerDependency(Plugin.class, plugin);
106+
registerDependency(JavaPlugin.class, plugin);
107+
registerDependency(PluginManager.class, Bukkit.getPluginManager());
108+
registerDependency(Server.class, Bukkit.getServer());
109+
registerDependency(BukkitScheduler.class, Bukkit.getScheduler());
110+
registerDependency(ScoreboardManager.class, Bukkit.getScoreboardManager());
111+
registerDependency(ItemFactory.class, Bukkit.getItemFactory());
98112
}
99113

100114
@NotNull private CommandMap hookCommandMap() {
@@ -261,7 +275,7 @@ void readPlayerLocale(Player player) {
261275
if (nmsPlayer != null) {
262276
Field localeField = nmsPlayer.getClass().getField("locale");
263277
Object localeString = localeField.get(nmsPlayer);
264-
if (localeString != null && localeString instanceof String) {
278+
if (localeString instanceof String) {
265279
String[] split = ACFPatterns.UNDERSCORE.split((String) localeString);
266280
Locale locale = split.length > 1 ? new Locale(split[0], split[1]) : new Locale(split[0]);
267281
Locale prev = issuersLocale.put(player.getUniqueId(), locale);

bukkit/src/main/java/co/aikar/commands/ProxyCommandMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public boolean dispatch(CommandSender sender, String cmdLine) throws CommandExce
8787

8888
@Override
8989
public void clearCommands() {
90-
super.clearCommands();;
90+
super.clearCommands();
9191
proxied.clearCommands();
9292
}
9393

bungee/src/main/java/co/aikar/commands/ACFBungeeUtil.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public static String replaceChatString(String message, Pattern replace, String w
7979
public static final char COLOR_CHAR = '\u00A7';
8080

8181
public static String getLastColors(String input) {
82-
String result = "";
82+
StringBuilder result = new StringBuilder();
8383
int length = input.length();
8484

8585
// Search backwards from the end as it is faster
@@ -90,7 +90,7 @@ public static String getLastColors(String input) {
9090
ChatColor color = ChatColor.getByChar(c);
9191

9292
if (color != null) {
93-
result = color.toString() + result;
93+
result.insert(0, color.toString());
9494

9595
// Once we find a color or reset we can stop searching
9696
if (isChatColorAColor(color) || color.equals(ChatColor.RESET)) {
@@ -99,7 +99,7 @@ public static String getLastColors(String input) {
9999
}
100100
}
101101
}
102-
return result;
102+
return result.toString();
103103
}
104104

105105
public static boolean isChatColorAColor(ChatColor chatColor) {

bungee/src/main/java/co/aikar/commands/BungeeCommandManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public BungeeCommandManager(Plugin plugin) {
5858
this.formatters.put(MessageType.INFO, new BungeeMessageFormatter(ChatColor.BLUE, ChatColor.DARK_GREEN, ChatColor.GREEN));
5959
this.formatters.put(MessageType.HELP, new BungeeMessageFormatter(ChatColor.AQUA, ChatColor.GREEN, ChatColor.YELLOW));
6060
getLocales(); // auto load locales
61+
62+
// TODO more default dependencies for bungee
63+
registerDependency(plugin.getClass(), plugin);
64+
registerDependency(Plugin.class, plugin);
6165
}
6266

6367
public Plugin getPlugin() {

core/src/main/java/co/aikar/commands/BaseCommand.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ void onRegister(CommandManager manager) {
123123
onRegister(manager, this.commandName);
124124
}
125125
void onRegister(CommandManager manager, String cmd) {
126+
manager.injectDependencies(this);
126127
this.manager = manager;
127128
final Class<? extends BaseCommand> self = this.getClass();
128129
CommandAlias rootCmdAliasAnno = self.getAnnotation(CommandAlias.class);

core/src/main/java/co/aikar/commands/CommandManager.java

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,19 @@
2323

2424
package co.aikar.commands;
2525

26-
import co.aikar.commands.annotation.Conditions;
26+
import co.aikar.commands.annotation.Dependency;
2727
import co.aikar.locales.MessageKeyProvider;
28+
import com.google.common.collect.HashBasedTable;
2829
import com.google.common.collect.Lists;
2930
import com.google.common.collect.Sets;
31+
import com.google.common.collect.Table;
3032
import org.jetbrains.annotations.NotNull;
3133

34+
import java.lang.reflect.Field;
3235
import java.lang.reflect.InvocationTargetException;
3336
import java.lang.reflect.Method;
3437
import java.lang.reflect.Parameter;
38+
import java.util.Arrays;
3539
import java.util.HashMap;
3640
import java.util.IdentityHashMap;
3741
import java.util.List;
@@ -53,18 +57,17 @@ public abstract class CommandManager <
5357
/**
5458
* This is a stack incase a command calls a command
5559
*/
56-
static ThreadLocal<Stack<CommandOperationContext>> commandOperationContext = ThreadLocal.withInitial(() -> {
57-
return new Stack<CommandOperationContext>() {
58-
@Override
59-
public synchronized CommandOperationContext peek() {
60-
return super.size() == 0 ? null : super.peek();
61-
}
62-
};
60+
static ThreadLocal<Stack<CommandOperationContext>> commandOperationContext = ThreadLocal.withInitial(() -> new Stack<CommandOperationContext>() {
61+
@Override
62+
public synchronized CommandOperationContext peek() {
63+
return super.size() == 0 ? null : super.peek();
64+
}
6365
});
6466
protected Map<String, RootCommand> rootCommands = new HashMap<>();
6567
protected final CommandReplacements replacements = new CommandReplacements(this);
6668
protected final CommandConditions<I, CEC, CC> conditions = new CommandConditions<>(this);
6769
protected ExceptionHandler defaultExceptionHandler = null;
70+
protected Table<Class<?>, String, Object> dependencies = HashBasedTable.create();
6871

6972
protected boolean usePerIssuerLocale = false;
7073
protected List<IssuerLocaleChangedCallback<I>> localeChangedCallbacks = Lists.newArrayList();
@@ -377,6 +380,73 @@ public void addSupportedLanguage(Locale locale) {
377380
getLocales().loadMissingBundles();
378381
}
379382

383+
/**
384+
* Registers an instance of a class to be registered as an injectable dependency.<br>
385+
* The command manager will attempt to inject all fields in a command class that are annotated with
386+
* {@link co.aikar.commands.annotation.Dependency} with the provided instance.
387+
*
388+
* @param clazz the class the injector should look for when injecting
389+
* @param instance the instance of the class that should be injected
390+
* @throws IllegalStateException when there is already an instance for the provided class registered
391+
*/
392+
public <T> void registerDependency(Class<? extends T> clazz, T instance){
393+
registerDependency(clazz, clazz.getName(), instance);
394+
}
395+
396+
/**
397+
* Registers an instance of a class to be registered as an injectable dependency.<br>
398+
* The command manager will attempt to inject all fields in a command class that are annotated with
399+
* {@link co.aikar.commands.annotation.Dependency} with the provided instance.
400+
*
401+
* @param clazz the class the injector should look for when injecting
402+
* @param key the key which needs to be present if that
403+
* @param instance the instance of the class that should be injected
404+
* @throws IllegalStateException when there is already an instance for the provided class registered
405+
*/
406+
public <T> void registerDependency(Class<? extends T> clazz, String key, T instance){
407+
if(dependencies.containsRow(clazz) && dependencies.containsColumn(key)){
408+
throw new IllegalStateException("There is already an instance of " + clazz.getName() + " with the key " + key + " registered!");
409+
}
410+
411+
dependencies.put(clazz, key, instance);
412+
}
413+
414+
/**
415+
* Attempts to inject instances of classes registered with {@link CommandManager#registerDependency(Class, Object)}
416+
* into all fields of the class and its superclasses that are marked with {@link Dependency}.
417+
*
418+
* @param baseCommand the instance which fields should be filled
419+
*/
420+
void injectDependencies(BaseCommand baseCommand) {
421+
Class clazz = baseCommand.getClass();
422+
do {
423+
for (Field field : clazz.getDeclaredFields()) {
424+
if (field.isAnnotationPresent(Dependency.class)) {
425+
Dependency dependency = field.getAnnotation(Dependency.class);
426+
String key = (key = dependency.value()).equals("") ? field.getType().getName() : key;
427+
Object object = dependencies.row(field.getType()).get(key);
428+
if (object == null) {
429+
throw new UnresolvedDependencyException("Could not find a registered instance of " +
430+
field.getType().getName() + " with key " + key + " for field " + field.getName() +
431+
" in class " + baseCommand.getClass().getName());
432+
}
433+
434+
try {
435+
boolean accessible = field.isAccessible();
436+
if (!accessible) {
437+
field.setAccessible(true);
438+
}
439+
field.set(baseCommand, object);
440+
field.setAccessible(accessible);
441+
} catch (IllegalAccessException e) {
442+
e.printStackTrace(); //TODO should we print our own exception here to make a more descriptive error?
443+
}
444+
}
445+
}
446+
clazz = clazz.getSuperclass();
447+
} while (!clazz.equals(BaseCommand.class));
448+
}
449+
380450
/**
381451
* @deprecated Use this with caution! If you enable and use Unstable API's, your next compile using ACF
382452
* may require you to update your implementation to those unstable API's

core/src/main/java/co/aikar/commands/InvalidCommandArgument.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class InvalidCommandArgument extends Exception {
3232
final String[] replacements;
3333

3434
public InvalidCommandArgument() {
35-
this((String) null, true);
35+
this(null, true);
3636
}
3737
public InvalidCommandArgument(boolean showSyntax) {
3838
this(null, showSyntax);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2016-2018 Daniel Ennis (Aikar) - MIT License
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the
6+
* "Software"), to deal in the Software without restriction, including
7+
* without limitation the rights to use, copy, modify, merge, publish,
8+
* distribute, sublicense, and/or sell copies of the Software, and to
9+
* permit persons to whom the Software is furnished to do so, subject to
10+
* the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be
13+
* included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*/
23+
24+
package co.aikar.commands;
25+
26+
/**
27+
* Thrown when a command mananger couldn't find a registered instance for a field that is marked with
28+
* {@link co.aikar.commands.annotation.Dependency}
29+
*/
30+
public class UnresolvedDependencyException extends RuntimeException {
31+
UnresolvedDependencyException(String message) {
32+
super(message);
33+
}
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2016-2018 Daniel Ennis (Aikar) - MIT License
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the
6+
* "Software"), to deal in the Software without restriction, including
7+
* without limitation the rights to use, copy, modify, merge, publish,
8+
* distribute, sublicense, and/or sell copies of the Software, and to
9+
* permit persons to whom the Software is furnished to do so, subject to
10+
* the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be
13+
* included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*/
23+
24+
package co.aikar.commands.annotation;
25+
26+
import java.lang.annotation.Retention;
27+
import java.lang.annotation.RetentionPolicy;
28+
29+
@Retention(RetentionPolicy.RUNTIME)
30+
public @interface Dependency {
31+
/**
32+
* The key that should be used to lookup the instances, defaults to \"\"
33+
* @return the key
34+
*/
35+
String value() default "";
36+
}

0 commit comments

Comments
 (0)