001    // GraphLab Project: http://graphlab.sharif.edu
002    // Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology
003    // Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/
004    
005    /*
006     * Pluger.java
007     *
008     * Created on March 22, 2005, 3:17 PM
009     */
010    package graphlab.platform.plugin;
011    
012    import graphlab.platform.core.BlackBoard;
013    
014    import java.io.File;
015    import java.net.MalformedURLException;
016    import java.net.URL;
017    import java.net.URLClassLoader;
018    import java.util.ArrayList;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.Map.Entry;
022    import java.util.StringTokenizer;
023    import java.util.jar.JarFile;
024    
025    
026    /**
027     * GraphLab plugging functionality is provided here.
028     * <p/>
029     * Class Plugger, is the heart of plugin structure. In order to gather all the plugins, you need to gather all the possible libraries(.jar) files to the project. The method plug() does this using Java Reflection API and Java Class Loader.
030     * <p/>
031     * After this, you need to read the manifest files. This is done via Java JarFile class. other properties of the plugin like its version , the dependencies and the answer to the question that this plugin should be loaded to GraphLab should be answered here. Method init(File) gets a jar file and checks all the mentioned actions using helper methods verify(), dfs(), load() and remove().
032     * <p/>
033     * verify() reads a HashMap called depends. This Map is filled in the init() method using the property reader of JarFile class. It reads all the dependencies that are mentioned in the jar file. These are only related to version dependencies.
034     * <p/>
035     * dfs() tries a DFS algorithm to topologically sort the dependencies tree and then use the sorted trees to give priority to plugin loads.
036     *
037     * @author Reza Mohammadi
038     */
039    public class Plugger {
040    
041        public final String prefix = "graphlab.plugins.";
042        public final String postfix = ".Init";
043        public final String handler_postfix = ".HandlerInit";
044        public static final String PLUGGER_INSTANCE = "main.Plugger";
045        public HashMap<String, Long> versions = new HashMap<String, Long>();
046        public HashMap<String, File> files = new HashMap<String, File>();
047        public HashMap<String, Integer> mark = new HashMap<String, Integer>();
048        public HashMap<String, String> initializer = new HashMap<String, String>();
049        public HashMap<String, String> configxml = new HashMap<String, String>();
050        public HashMap<String, ArrayList<Object[]>> depends = new HashMap<String, ArrayList<Object[]>>();
051        public HashMap<String, ArrayList<String>> childs = new HashMap<String, ArrayList<String>>();
052        public URLClassLoader classLoader = null;
053        public int activePlugins = 0;
054    
055        private BlackBoard blackboard = null;
056        private String first = null;
057    
058        public Plugger(BlackBoard blackboard) {
059            this.blackboard = blackboard;
060            blackboard.setData(PLUGGER_INSTANCE, this);
061        }
062    
063        /**
064         * Search plugins directory and add jar files to Graphlab.
065         * And add all jar files in plgins directory and lib directory
066         * to <code>classLoader</code>
067         */
068        public void plug() {
069            File f = new File("plugins");
070            if (f.isDirectory() && f.canRead()) {
071                for (File ff : f.listFiles()) {
072                    if (ff.isFile() && "jar".equalsIgnoreCase(getExtension(ff))) {
073                        init(ff);
074                    }
075                }
076                System.out.println("------------------------------------------------------------");
077                verify();
078                System.out.println("------------------------------------------------------------");
079                if (first != null) {
080                    int libCount = 0;
081                    File libf = new File("lib");
082                    ArrayList<URL> libURLs = new ArrayList<URL>();
083                    if (libf.isDirectory() && libf.canRead()) {
084                        for (File ff : libf.listFiles()) {
085                            if (ff.isFile() && "jar".equalsIgnoreCase(getExtension(ff))) {
086                                try {
087                                    libURLs.add(ff.toURL());
088                                    System.out.println("Library file " + ff + " added.");
089                                } catch (MalformedURLException e) {
090                                    e.printStackTrace();
091                                }
092                                libCount++;
093                            }
094                        }
095                    }
096                    URL[] urls = new URL[activePlugins + libCount + 1];
097                    int i = 0;
098                    for (URL libURL : libURLs)
099                        urls[i++] = libURL;
100                    for (String name : files.keySet()) {
101                        if (mark.get(name) == 0)
102                            try {
103                                urls[i] = files.get(name).toURL();
104                            } catch (MalformedURLException e) {
105                                System.out.println(name + " [" + files.get(name).getPath() + "]");
106                                e.printStackTrace();
107                            }
108                        i++;
109                    }
110                    try {
111                        urls[i] = new File("extensions").toURL();
112                    } catch (MalformedURLException e) {
113                        e.printStackTrace();
114                    }
115                    classLoader = new URLClassLoader(urls);
116                    System.out.println("" + i + " jar file(s) loaded.");
117                    System.out.println("------------------------------------------------------------");
118                    dfs(first);
119                } else
120                    System.out.println("Can't Load Any Plugin!");
121                System.out.println("------------------------------------------------------------");
122            } else
123                System.out.println("There is no directory with name plugins.");
124        }
125    
126        /**
127         * Read manifest of a jar file and make that plugin candidate
128         * to be loaded.
129         *
130         * @param ff file object
131         */
132        public void init(File ff) {
133            try {
134                JarFile jf = new JarFile(ff);
135                System.out.println("------------------------------------------------------------");
136                String name = jf.getManifest().getMainAttributes().getValue("plugin-name");
137                String verStr = jf.getManifest().getMainAttributes().getValue("plugin-version");
138                String dependsStr = jf.getManifest().getMainAttributes().getValue("plugin-depends");
139                if (name == null || verStr == null) {
140                    System.out.println("Skipping " + name + "(" + verStr + ")");
141                    return;
142                }
143                Long ver = Long.parseLong(verStr);
144                System.out.println("Detected " + name + "(" + ver + ") ...");
145                for (Entry<Object, Object> s : jf.getManifest().getMainAttributes().entrySet()) {
146                    if (!"plugin-name".equals(s.getKey()) && !"plugin-version".equals(s.getKey()))
147                        System.out.println(s.getKey() + " : " + s.getValue());
148                }
149                ArrayList<Object[]> dependsArray = new ArrayList<Object[]>();
150                if (dependsStr != null) {
151                    StringTokenizer st = new StringTokenizer(dependsStr);
152                    while (st.hasMoreElements()) {
153                        String depName = st.nextToken();
154                        String depVerStr = st.nextToken();
155                        Long depVer = Long.parseLong(depVerStr);
156                        dependsArray.add(new Object[]{depName, depVer});
157                    }
158                }
159                versions.put(name, ver);
160                mark.put(name, -1);
161                files.put(name, ff);
162                childs.put(name, new ArrayList<String>());
163                depends.put(name, dependsArray);
164                initializer.put(name, jf.getManifest().getMainAttributes().getValue("plugin-initializer"));
165                configxml.put(name, jf.getManifest().getMainAttributes().getValue("plugin-configxml"));
166            } catch (Exception ex) {
167                ex.printStackTrace();
168            }
169    
170        }
171    
172        /**
173         * Remove a "plugin candidate" because of lack of dependencies.
174         *
175         * @param name Name of plugin
176         */
177        public void remove(String name) {
178            if (mark.get(name) != -2) {
179                System.out.println("Removing " + name + " because of lack of dependencies...");
180                mark.put(name, -2);
181                for (String ch : childs.get(name))
182                    remove(ch);
183                for (Object[] dep : depends.get(name))
184                    childs.get(dep[0]).remove(name);
185            }
186        }
187    
188        /**
189         * Check dependencies
190         */
191        public void verify() {
192            for (String name : versions.keySet()) {
193                for (Object[] dep : depends.get(name)) {
194                    if (mark.get(name) == -1) {
195                        String depName = (String) dep[0];
196                        Long depVer = (Long) dep[1];
197                        Long depPlugVer = (versions.get(depName) == null) ? null : (Long) versions.get(depName);
198                        if (depPlugVer == null || depPlugVer < depVer || mark.get(depName) == -2) {
199                            remove(name);
200                        } else {
201                            childs.get(depName).add(name);
202                        }
203                    }
204                }
205                if (mark.get(name) == -1)
206                    mark.put(name, 0);
207            }
208            activePlugins = 0;
209            for (String name : versions.keySet()) {
210                System.out.println("Plugin " + name + "(" + mark.get(name) + ") has " + childs.get(name).size() + " child(ren)!");
211                if (mark.get(name) == 0) {
212                    activePlugins++;
213                    if (first == null && childs.get(name).isEmpty())
214                        first = name;
215                }
216            }
217        }
218    
219        /**
220         * DFS to find a topological sort of plugins.
221         *
222         * @param name Name of plugin
223         */
224        public void dfs(String name) {
225            mark.put(name, 1);
226            System.out.println("DFS on " + name + " ...");
227            for (Object[] dep : depends.get(name)) {
228                if (mark.get(dep[0]) == 0)
229                    dfs((String) dep[0]);
230            }
231            mark.put(name, 2);
232            load(name);
233            mark.put(name, 3);
234            for (String chstr : childs.get(name)) {
235                if (mark.get(chstr) == 0)
236                    dfs((String) chstr);
237            }
238        }
239    
240        /**
241         * Load and initialize a plugin.
242         * if plugin has defined plugin-initializer or has
243         * graphlab.plugins.<i>pluginname</i>.Init, Then the
244         * init class of plugin will be loaded.
245         * Else if plugin has defined plugin-configxml
246         * (or using default =
247         * "/graphlab/gui/plugin/<i>pluginname</i>/config.xml"),
248         * then this function search parents of this plugin
249         * to find a <code>PluginHandlerInterface</code> and
250         * send path of config.xml to that handler.
251         *
252         * @param name Name of plugin
253         * @see PluginInterface#init(graphlab.platform.core.BlackBoard)
254         * @see PluginHandlerInterface#init(String,graphlab.platform.core.BlackBoard)
255         */
256        public void load(String name) {
257            try {
258                System.out.println("Loading " + name + " ...");
259                try {
260                    String cName = initializer.get(name);
261                    if (cName == null)
262                        cName = prefix + name + postfix;
263                    PluginInterface init = (PluginInterface) classLoader.loadClass(cName).newInstance();
264                    init.init(blackboard);
265                } catch (ClassNotFoundException cnfe) {
266                    String confixml = configxml.get(name);
267                    if (confixml == null)
268                        confixml = "/graphlab/plugins/" + name + "/config.xml";
269                    Iterator<Object[]> it = depends.get(name).iterator();
270                    PluginHandlerInterface init = null;
271                    while (init == null) {
272                        try {
273                            init = (PluginHandlerInterface) classLoader.loadClass(prefix + it.next()[0].toString() + handler_postfix).newInstance();
274                            init.init(confixml, blackboard);
275                        } catch (ClassNotFoundException cnfe2) {
276                        }
277                    }
278                }
279                System.out.println("Loaded : " + name + ".");
280            } catch (Exception ex) {
281                ex.printStackTrace();
282            }
283        }
284    
285        /**
286         * Return the extension portion of the file's name .
287         *
288         * @see #getExtension
289         * @see javax.swing.filechooser.FileFilter#accept
290         */
291        public static String getExtension(File f) {
292            if (f != null) {
293                String filename = f.getName();
294                int i = filename.lastIndexOf('.');
295                if (i > 0 && i < filename.length() - 1) {
296                    return filename.substring(i + 1).toLowerCase();
297                }
298                ;
299            }
300            return null;
301        }
302    }