package com.supaham.commons.bukkit;
import com.google.common.base.Preconditions;
import com.supaham.commons.bukkit.commands.CommonCommandsManager;
import com.supaham.commons.bukkit.modules.Module;
import com.supaham.commons.bukkit.modules.ModuleContainer;
import com.supaham.commons.bukkit.utils.SerializationUtils;
import com.supaham.commons.state.State;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import pluginbase.config.datasource.yaml.YamlDataSource;
import pluginbase.logging.PluginLogger;
import pluginbase.messages.messaging.SendablePluginBaseException;
/**
* Represents a simple implementation of {@link CommonPlugin}. This class throws an
* {@link AssertionError} when {@link #SimpleCommonPlugin()} is called, so the implementation must
* not call said constructor and instead call {@link #SimpleCommonPlugin()}
* <p />
* This class does the following tasks:
* <br />
* <ol>
* <li>Offers the following protected fields:
* <ul>
* <li>moduleContainer; {@link ModuleContainer}; used to assign {@link Module}s to this class</li>
* </ul>
* </li>
* <li>Within the only available constructor, this plugin is hooked into {@link CBukkitMain}.</li>
* <li>During onLoad, onEnable, onDisable, they call the {@code pluginAgent} in their respective
* order. So, when overriding, ensure to call said super methods.</li>
* <li>Implements other methods provided </li>
* </ol>
*/
public abstract class SimpleCommonPlugin<T extends SimpleCommonPlugin> extends JavaPlugin
implements CommonPlugin {
private State state = State.STOPPED;
private final PluginLogger pluginLogger = PluginLogger.getLogger(this);
protected final ModuleContainer moduleContainer = new ModuleContainer(this);
private final SettingsContainer settingsContainer = new SettingsContainer();
private final FirstRunContainer firstRunContainer = new FirstRunContainer();
@Nonnull private CommonCommandsManager commandsManager = new CommonCommandsManager(this);
private Metrics metrics;
public SimpleCommonPlugin() {
CBukkitMain.hook(this);
}
@Override
public void onLoad() {
if (!this.settingsContainer.init()) {
getLog().finer("Settings disabled.");
}
if (reloadSettings()) {
SimpleCommonPlugin.this.firstRunContainer.init(); // FirstRun runs after reloadSettings
}
}
@Override
public void onEnable() {
this.state = State.ACTIVE;
}
@Override
public void onDisable() {
this.state = State.STOPPED;
disableMetrics(); // Automatically disable metrics if it is enabled, convenience method.
}
@Nonnull
@Override
public ModuleContainer getModuleContainer() {
return this.moduleContainer;
}
@Nonnull
@Override
public PluginLogger getLog() {
return this.pluginLogger;
}
/*
* onCommand is overridden in order for bukkit to relay all command calls to our own executor.
*/
@Override
public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) {
return getCommandsManager().getDefaultExecutor().onCommand(sender, command, alias, args);
}
@Nonnull
@Override
public CommonCommandsManager getCommandsManager() {
return this.commandsManager;
}
protected void setCommandsManager(@Nonnull CommonCommandsManager commandsManager) {
Preconditions.checkState(this.state.isIdle(), "cannot change CommonCommandsManager when plugin is enabled.");
Preconditions.checkNotNull(commandsManager, "commandsManager cannot be null.");
this.commandsManager = commandsManager;
}
@Override
public CommonSettings getSettings() {
return this.settingsContainer.settings;
}
protected void setSettings(Supplier<CommonSettings> settingsSupplier) {
this.settingsContainer.settingsSupplier = settingsSupplier;
}
@Nonnull
@Override
public File getSettingsFile() {
return this.settingsContainer.settingsFile;
}
protected void setSettingsFile(@Nonnull File file) {
Preconditions.checkNotNull(file, "file cannot be null.");
this.settingsContainer.settingsFile = file;
}
@Override
public boolean reloadSettings() {
if (this.settingsContainer.isEnabled()) {
return this.settingsContainer.load();
}
return false;
}
@Override
public boolean saveSettings() {
if (this.settingsContainer.isEnabled()) {
return this.settingsContainer.save();
}
return false;
}
@Override
public boolean isFirstRun() {
return this.firstRunContainer.firstRun;
}
public boolean hasFirstRunRunnable(@Nonnull Runnable runnable) {
Preconditions.checkNotNull(runnable, "runnable cannot be null.");
return this.firstRunContainer.firstRunRunnables.contains(runnable);
}
public boolean addFirstRunRunnable(@Nonnull Runnable runnable) {
Preconditions.checkNotNull(runnable, "runnable cannot be null.");
return this.firstRunContainer.firstRunRunnables.add(runnable);
}
public boolean removeFirstRunRunnable(@Nonnull Runnable runnable) {
Preconditions.checkNotNull(runnable, "runnable cannot be null.");
return this.firstRunContainer.firstRunRunnables.remove(runnable);
}
protected boolean enableMetrics() {
if (this.metrics != null) {
return false;
}
try {
this.metrics = new Metrics(this);
this.metrics.enable();
return true;
} catch (IOException e) {
getLog().severe("Failed to reach Metrics server: " + e.getMessage());
if (getLog().getDebugLevel() > 0) {
e.printStackTrace();
}
return false;
}
}
protected boolean disableMetrics() {
if (this.metrics == null) {
return false;
}
try {
this.metrics.disable();
this.metrics = null;
return true;
} catch (IOException e) {
getLog().severe("Failed to reach Metrics server: " + e.getMessage());
if (getLog().getDebugLevel() > 0) {
e.printStackTrace();
}
return false;
}
}
private final class SettingsContainer {
private Supplier<CommonSettings> settingsSupplier = () -> new CommonSettings(SimpleCommonPlugin.this);
private File settingsFile = new File(getDataFolder(), "config.yml");
private CommonSettings settings;
private YamlDataSource yaml;
private boolean isEnabled() {
return settingsSupplier != null; // not null = enabled
}
private boolean init() {
if (!isEnabled()) {
return false;
}
this.settings = settingsSupplier.get();
return true;
}
private boolean load() {
if (!isEnabled()) {
throw new IllegalStateException("Settings not enabled.");
}
try {
yaml = SerializationUtils.yaml(this.settingsFile).build();
SerializationUtils.loadOrCreateProperties(getLog(), this.yaml, this.settings);
save();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public boolean save() {
if (!isEnabled()) {
throw new IllegalStateException("Settings not enabled.");
}
try {
this.yaml.save(this.settings);
return true;
} catch (SendablePluginBaseException e) {
e.printStackTrace();
return false;
}
}
}
private final class FirstRunContainer {
private List<Runnable> firstRunRunnables = new ArrayList<>();
private boolean firstRun = false;
private void init() {
if (getSettings().isFirstRun()) {
getLog().fine(getName() + " first run");
this.firstRun = true;
this.firstRunRunnables.forEach(Runnable::run);
getSettings().setFirstRun(false);
saveSettings();
}
this.firstRunRunnables = null;
}
}
}