001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.plugin;
019
020import net.tridentsdk.Trident;
021import net.tridentsdk.config.Config;
022import net.tridentsdk.event.Listener;
023import net.tridentsdk.plugin.annotation.PluginDesc;
024import net.tridentsdk.plugin.cmd.Command;
025import net.tridentsdk.registry.Registered;
026import net.tridentsdk.util.TridentLogger;
027import org.slf4j.Logger;
028
029import javax.annotation.Nonnull;
030import javax.annotation.Nullable;
031import javax.annotation.concurrent.NotThreadSafe;
032import java.io.File;
033import java.io.IOException;
034import java.io.InputStream;
035import java.nio.file.Files;
036import java.util.Objects;
037import java.util.stream.Collectors;
038
039/**
040 * Must be extended by a non-inner class to represent a plugin's <em>main class</em>
041 *
042 * @author The TridentSDK Team
043 * @since 0.3-alpha-DP
044 */
045@NotThreadSafe
046public class Plugin {
047    private File pluginFile;
048    private File configDirectory;
049    private PluginDesc description;
050    private Config defaultConfig;
051    public PluginLoader classLoader;
052
053    protected Plugin() {
054    }
055
056    public void init(File pluginFile, PluginDesc description, PluginLoader loader) {
057        this.pluginFile = pluginFile;
058        this.description = description;
059        this.configDirectory = new File("plugins" + File.separator + description.name() + File.separator);
060        this.defaultConfig = new Config(new File(this.configDirectory, "config.json"));
061        this.classLoader = loader;
062    }
063
064    /**
065     * Obtains the instance of the plugin which the caller class is in
066     *
067     * <p>Returns {@code null} if the plugin has not been loaded yet, or if the class is not a plugin loaded on the
068     * server.</p>
069     *
070     * @return the instance of the plugin
071     */
072    @Nullable
073    public static Plugin instance() {
074        Class<?> caller = Trident.findCaller(3);
075        ClassLoader loader = caller.getClassLoader();
076        for (Plugin plugin : Registered.plugins())
077            if (plugin.classLoader.equals(loader))
078                return plugin;
079        return null;
080    }
081
082    /**
083     * Obtains the instance of the plugin which has the specified class
084     *
085     * <p>Returns {@code null} if the plugin has not been loaded yet, or if the class is not a plugin loaded on the
086     * server.</p>
087     *
088     * @param c the main class of the plugin to obtain the instance of
089     * @return the instance of the plugin with the specified main class
090     */
091    @Nullable
092    public static <T extends Plugin> T instance(Class<T> c) {
093        for (Plugin plugin : Registered.plugins())
094            if (plugin.classLoader.loadedClasses().containsKey(c.getName())) {
095                return (T) plugin;
096            }
097        return null;
098    }
099
100    /**
101     * Called by the handler to indicate the plugin has been constructed
102     */
103    public void load() {
104        // Method intentionally left blank
105    }
106
107    /**
108     * Called by the handler to indicate the enabling of this plugin
109     */
110    public void enable() {
111        // Method intentionally left blank
112    }
113
114    /**
115     * Called by the handler to indicate the disabling of this plugin
116     */
117    public void disable() {
118        // Method intentionally left blank
119    }
120
121    /**
122     * Obtains the listener instance with the class specified
123     *
124     * @param c the class to find the listener instance by
125     * @return the listener instance registered to the server
126     */
127    public <T extends Listener> T listenerBy(Class<T> c) {
128        return (T) Registered.events()
129                .stream()
130                .filter(r -> r.listener().getClass().equals(c))
131                .collect(Collectors.toList())
132                .get(0);
133    }
134
135    /**
136     * Obtains the command instance with the class specified
137     *
138     * @param c the class to find the command instance by
139     * @return the command instance registered to the server
140     */
141    public <T extends Command> T commandBy(Class<T> c) {
142        return (T) Registered.commands().stream()
143                .filter(cmd -> cmd.getClass().equals(c))
144                .collect(Collectors.toList())
145                .get(0);
146    }
147
148    /**
149     * Gets the logger for this plugin
150     *
151     * @return the logger
152     */
153    public Logger logger() {
154        return TridentLogger.get(description.name()).internal();
155    }
156
157    /**
158     * Saves the configuration inside the jar to the plugin directory
159     *
160     * <p>If the configuration is already saved, this does not overwrite it.</p>
161     */
162    public void saveDefaultConfig() {
163        this.saveResource("config.json", false);
164    }
165
166    /**
167     * Saves a resource inside the jar to the plugin directory
168     *
169     * @param name    the filename of the directory
170     * @param replace whether or not replace the old resource, if it exists
171     */
172    public void saveResource(String name, boolean replace) {
173        try {
174            InputStream is = this.getClass().getResourceAsStream('/' + name);
175            File file = new File(this.configDirectory, name);
176
177            if (is == null) {
178                return;
179            }
180
181            if (replace && file.exists()) {
182                file.delete();
183            }
184
185            Files.copy(is, file.getAbsoluteFile().toPath());
186        } catch (IOException ex) {
187            TridentLogger.get().error(ex);
188        }
189    }
190
191    /**
192     * Obtains the file which this plugin was loaded from
193     *
194     * @return the file which the plugin is loaded from
195     */
196    public final File pluginFile() {
197        return this.pluginFile;
198    }
199
200    /**
201     * The default configuration for this plugin, which may or may not exist physically
202     *
203     * @return the default configuration given to this plugin
204     */
205    public Config defaultConfig() {
206        return this.defaultConfig;
207    }
208
209    /**
210     * The plugin directory
211     *
212     * <p>The returned file includes the trailing file separator</p>
213     *
214     * @return the plugin directory where resources like the default config are saved
215     */
216    public File configDirectory() {
217        return this.configDirectory;
218    }
219
220    /**
221     * Obtains the annotation given by this plugin
222     *
223     * @return the plugin descriptor for this plugin
224     */
225    @Nonnull
226    public final PluginDesc description() {
227        return this.description;
228    }
229
230    @Override
231    public boolean equals(Object other) {
232        if (other instanceof Plugin) {
233            Plugin otherPlugin = (Plugin) other;
234            if (otherPlugin.description().name().equals(this.description().name())) {
235                if (otherPlugin.description().author().equals(this.description().author())) {
236                    return true;
237                }
238            }
239        }
240        return false;
241    }
242
243    @Override
244    public int hashCode() {
245        // Find constants
246        String name = this.description().name();
247        String author = this.description().author();
248
249        return Objects.hash(name, author);
250    }
251}