Coverage Report - org.argouml.moduleloader.ModuleLoader2
 
Classes in this File Line Coverage Branch Coverage Complexity
ModuleLoader2
56%
153/272
50%
59/118
4.265
ModuleLoader2$JarFileFilter
100%
2/2
50%
3/6
4.265
ModuleStatus
46%
7/15
0%
0/2
4.265
 
 1  
 /* $Id: ModuleLoader2.java 17920 2010-01-24 15:03:19Z linus $
 2  
  *****************************************************************************
 3  
  * Copyright (c) 2009 Contributors - see below
 4  
  * All rights reserved. This program and the accompanying materials
 5  
  * are made available under the terms of the Eclipse Public License v1.0
 6  
  * which accompanies this distribution, and is available at
 7  
  * http://www.eclipse.org/legal/epl-v10.html
 8  
  *
 9  
  * Contributors:
 10  
  *    tfmorris
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 2004-2008 The Regents of the University of California. All
 17  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 18  
 // software and its documentation without fee, and without a written
 19  
 // agreement is hereby granted, provided that the above copyright notice
 20  
 // and this paragraph appear in all copies.  This software program and
 21  
 // documentation are copyrighted by The Regents of the University of
 22  
 // California. The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Regents. The Regents
 24  
 // does not warrant that the operation of the program will be
 25  
 // uninterrupted or error-free. The end-user understands that the program
 26  
 // was developed for research purposes and is advised not to rely
 27  
 // exclusively on the program for any reason.  IN NO EVENT SHALL THE
 28  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 29  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 30  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 31  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 33  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 34  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 35  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 36  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.moduleloader;
 40  
 
 41  
 import java.io.File;
 42  
 import java.io.FileFilter;
 43  
 import java.io.IOException;
 44  
 import java.io.UnsupportedEncodingException;
 45  
 import java.lang.reflect.Constructor;
 46  
 import java.lang.reflect.InvocationTargetException;
 47  
 import java.lang.reflect.Modifier;
 48  
 import java.net.URL;
 49  
 import java.net.URLClassLoader;
 50  
 import java.util.ArrayList;
 51  
 import java.util.Collection;
 52  
 import java.util.Collections;
 53  
 import java.util.Enumeration;
 54  
 import java.util.HashMap;
 55  
 import java.util.HashSet;
 56  
 import java.util.List;
 57  
 import java.util.Map;
 58  
 import java.util.StringTokenizer;
 59  
 import java.util.jar.Attributes;
 60  
 import java.util.jar.JarEntry;
 61  
 import java.util.jar.JarFile;
 62  
 import java.util.jar.Manifest;
 63  
 
 64  
 import org.apache.log4j.Logger;
 65  
 import org.argouml.application.api.AbstractArgoJPanel;
 66  
 import org.argouml.application.api.Argo;
 67  
 import org.argouml.i18n.Translator;
 68  
 
 69  
 
 70  
 /**
 71  
  * This is the module loader that loads modules implementing the
 72  
  * ModuleInterface.<p>
 73  
  *
 74  
  * This is a singleton. There are convenience functions that are static
 75  
  * to access the module.<p>
 76  
  *
 77  
  * @stereotype singleton
 78  
  * @author Linus Tolke
 79  
  * @since 0.15.4
 80  
  */
 81  
 public final class ModuleLoader2 {
 82  
     /**
 83  
      * Logger.
 84  
      */
 85  900
     private static final Logger LOG = Logger.getLogger(ModuleLoader2.class);
 86  
 
 87  
     /**
 88  
      * This map contains the module loader information about the module.<p>
 89  
      *
 90  
      * The keys is the list of available modules.
 91  
      */
 92  
     private Map<ModuleInterface, ModuleStatus> moduleStatus;
 93  
     
 94  
     /**
 95  
      * List of locations that we've searched and/or loaded modules
 96  
      * from.  This is for information purposes only to allow it to 
 97  
      * be displayed in the settings Environment tab.
 98  
      */
 99  900
     private List<String> extensionLocations = new ArrayList<String>();
 100  
 
 101  
     /**
 102  
      * The module loader object.
 103  
      */
 104  900
     private static final ModuleLoader2 INSTANCE = new ModuleLoader2();
 105  
 
 106  
     /**
 107  
      * The prefix in URL:s that are files.
 108  
      */
 109  
     private static final String FILE_PREFIX = "file:";
 110  
 
 111  
     /**
 112  
      * The prefix in URL:s that are jars.
 113  
      */
 114  
     private static final String JAR_PREFIX = "jar:";
 115  
 
 116  
     /**
 117  
      * Class file suffix.
 118  
      */
 119  
     public static final String CLASS_SUFFIX = ".class";
 120  
 
 121  
     /**
 122  
      * Constructor for this object.
 123  
      */
 124  900
     private ModuleLoader2() {
 125  900
         moduleStatus = new HashMap<ModuleInterface, ModuleStatus>();
 126  900
         computeExtensionLocations();
 127  900
     }
 128  
 
 129  
     /**
 130  
      * Get hold of the instance of this object.
 131  
      *
 132  
      * @return the instance
 133  
      */
 134  
     public static ModuleLoader2 getInstance() {
 135  8641
         return INSTANCE;
 136  
     }
 137  
 
 138  
     /**
 139  
      * @return a list of Details panel tabs
 140  
      */
 141  
     List<AbstractArgoJPanel> getDetailsTabs() {
 142  900
         List<AbstractArgoJPanel> result = new ArrayList<AbstractArgoJPanel>();
 143  900
         for (ModuleInterface module : getInstance().availableModules()) {
 144  4500
             ModuleStatus status = moduleStatus.get(module);
 145  4500
             if (status == null) {
 146  0
                 continue;
 147  
             }
 148  4500
             if (status.isEnabled()) {
 149  4500
                 if (module instanceof DetailsTabProvider) {
 150  0
                     result.addAll(
 151  
                             ((DetailsTabProvider) module).getDetailsTabs());
 152  
                 }
 153  
             }
 154  4500
         }
 155  900
         return result;
 156  
     }
 157  
     /**
 158  
      * Return a collection of all available modules.
 159  
      *
 160  
      * @return A Collection of all available modules.
 161  
      */
 162  
     private Collection<ModuleInterface> availableModules() {
 163  3687
         return Collections.unmodifiableCollection(moduleStatus.keySet());
 164  
     }
 165  
 
 166  
     // Access methods for program infrastructure.
 167  
     /**
 168  
      * Enables all selected modules and disabling all modules not selected.<p>
 169  
      *
 170  
      * In short this attempts to make the modules obey their selection.<p>
 171  
      *
 172  
      * @param failingAllowed is <code>true</code> if enabling or disabling of
 173  
      *                       some of the modules is allowed to fail.
 174  
      */
 175  
     public static void doLoad(boolean failingAllowed) {
 176  900
         getInstance().doInternal(failingAllowed);
 177  900
     }
 178  
 
 179  
     // Access methods for modules that need to query about the status of
 180  
     // other modules.
 181  
     /**
 182  
      * Gets the loaded status for some other module.
 183  
      *
 184  
      * @return true if the module exists and is enabled.
 185  
      * @param name is the module name of the queried module
 186  
      */
 187  
     public static boolean isEnabled(String name) {
 188  0
         return getInstance().isEnabledInternal(name);
 189  
     }
 190  
 
 191  
     // Access methods for the GUI that the user uses to enable and disable
 192  
     // modules.
 193  
 
 194  
     /**
 195  
      * Get a Collection with all the names.
 196  
      *
 197  
      * @return all the names.
 198  
      */
 199  
     public static Collection<String> allModules() {
 200  87
         Collection<String> coll = new HashSet<String>();
 201  
 
 202  87
         for (ModuleInterface mf : getInstance().availableModules()) {
 203  435
             coll.add(mf.getName());
 204  
         }
 205  
 
 206  87
         return coll;
 207  
     }
 208  
 
 209  
     /**
 210  
      * Get the selected.
 211  
      *
 212  
      * @param name The name of the module.
 213  
      * @return <code>true</code> if the module is selected.
 214  
      */
 215  
     public static boolean isSelected(String name) {
 216  190
         return getInstance().isSelectedInternal(name);
 217  
     }
 218  
 
 219  
     /**
 220  
      * Get the selected.
 221  
      *
 222  
      * @see #isSelected(String)
 223  
      * @param name The name of the module.
 224  
      * @return <code>true</code> if the module is selected.
 225  
      */
 226  
     private boolean isSelectedInternal(String name) {
 227  190
         Map.Entry<ModuleInterface, ModuleStatus> entry = findModule(name);
 228  
 
 229  190
         if (entry != null) {
 230  190
             ModuleStatus status = entry.getValue();
 231  
 
 232  190
             if (status == null) {
 233  0
                 return false;
 234  
             }
 235  
 
 236  190
             return status.isSelected();
 237  
         }
 238  0
         return false;
 239  
     }
 240  
 
 241  
     /**
 242  
      * Set the selected value.
 243  
      *
 244  
      * @param name The name of the module.
 245  
      * @param value Selected or not.
 246  
      */
 247  
     public static void setSelected(String name, boolean value) {
 248  0
         getInstance().setSelectedInternal(name, value);
 249  0
     }
 250  
 
 251  
     /**
 252  
      * Set the selected value.
 253  
      *
 254  
      * @see #setSelected(String, boolean)
 255  
      * @param name The name of the module.
 256  
      * @param value Selected or not.
 257  
      */
 258  
     private void setSelectedInternal(String name, boolean value) {
 259  0
         Map.Entry<ModuleInterface, ModuleStatus> entry = findModule(name);
 260  
 
 261  0
         if (entry != null) {
 262  0
             ModuleStatus status = entry.getValue();
 263  
 
 264  0
             status.setSelected(value);
 265  
         }
 266  0
     }
 267  
 
 268  
     /**
 269  
      * Create a description of the module based on the information provided
 270  
      * by the module itself.
 271  
      *
 272  
      * @param name The name of the module.
 273  
      * @return The description.
 274  
      */
 275  
     public static String getDescription(String name) {
 276  245
         return getInstance().getDescriptionInternal(name);
 277  
     }
 278  
 
 279  
     /**
 280  
      * Create a description of the module based on the information provided
 281  
      * by the module itself.
 282  
      *
 283  
      * @see #getDescription(String)
 284  
      * @param name The name of the module.
 285  
      * @return The description.
 286  
      */
 287  
     private String getDescriptionInternal(String name) {
 288  245
         Map.Entry<ModuleInterface, ModuleStatus> entry = findModule(name);
 289  
 
 290  245
         if (entry == null) {
 291  0
             throw new IllegalArgumentException("Module does not exist.");
 292  
         }
 293  
 
 294  245
         ModuleInterface module = entry.getKey();
 295  245
         StringBuffer sb = new StringBuffer();
 296  245
         String desc = module.getInfo(ModuleInterface.DESCRIPTION);
 297  245
         if (desc != null) {
 298  245
             sb.append(desc);
 299  245
             sb.append("\n\n");
 300  
         }
 301  245
         String author = module.getInfo(ModuleInterface.AUTHOR);
 302  245
         if (author != null) {
 303  245
             sb.append("Author: ").append(author);
 304  245
             sb.append("\n");
 305  
         }
 306  245
         String version = module.getInfo(ModuleInterface.VERSION);
 307  245
         if (version != null) {
 308  245
             sb.append("Version: ").append(version);
 309  245
             sb.append("\n");
 310  
         }
 311  245
         return sb.toString();
 312  
     }
 313  
 
 314  
 
 315  
     // Access methods for the program infrastructure
 316  
     /**
 317  
      * Enables all selected modules.
 318  
      *
 319  
      * @param failingAllowed is true if this is not the last attempt at
 320  
      * turning on.
 321  
      */
 322  
     private void doInternal(boolean failingAllowed) {
 323  900
         huntForModules();
 324  
 
 325  
         boolean someModuleSucceeded;
 326  
         do {
 327  1800
             someModuleSucceeded = false;
 328  
 
 329  1800
             for (ModuleInterface module : getInstance().availableModules()) {
 330  
 
 331  9000
                 ModuleStatus status = moduleStatus.get(module);
 332  
 
 333  9000
                 if (status == null) {
 334  0
                     continue;
 335  
                 }
 336  
 
 337  9000
                 if (!status.isEnabled() && status.isSelected()) {
 338  
                     try {
 339  4500
                         if (module.enable()) {
 340  4500
                             someModuleSucceeded = true;
 341  4500
                             status.setEnabled();
 342  
                         }
 343  
                     }
 344  
                     // Catch all exceptions and errors, however severe
 345  0
                     catch (Throwable e) {                       
 346  0
                         LOG.error("Exception or error while trying to "
 347  
                                 + "enable module " + module.getName(), e);
 348  4500
                     }
 349  4500
                 } else if (status.isEnabled() && !status.isSelected()) {
 350  
                     try { 
 351  0
                         if (module.disable()) {
 352  0
                             someModuleSucceeded = true;
 353  0
                             status.setDisabled();
 354  
                         }
 355  
                     }
 356  
                     // Catch all exceptions and errors, however severe
 357  0
                     catch (Throwable e) {
 358  0
                         LOG.error("Exception or error while trying to "
 359  
                                 + "disable module " + module.getName(), e);
 360  0
                     }
 361  
                 }
 362  9000
             }
 363  1800
         } while (someModuleSucceeded);
 364  
 
 365  900
         if (!failingAllowed) {
 366  
             // Notify the user that the modules in the list that are selected
 367  
             // but not enabled were not possible to enable and that are not
 368  
             // selected that we cannot disable.
 369  
             //
 370  
             // Currently we just log this.
 371  
             //
 372  
             // TODO: We could eventually pop up some warning window.
 373  
             //
 374  900
             for (ModuleInterface module : getInstance().availableModules()) {
 375  
 
 376  4500
                 ModuleStatus status = moduleStatus.get(module);
 377  
 
 378  4500
                 if (status == null) {
 379  0
                     continue;
 380  
                 }
 381  
 
 382  4500
                 if (status.isEnabled() && status.isSelected()) {
 383  4500
                     continue;
 384  
                 }
 385  
 
 386  0
                 if (!status.isEnabled() && !status.isSelected()) {
 387  0
                     continue;
 388  
                 }
 389  
 
 390  0
                 if (status.isSelected()) {
 391  0
                     LOG.warn("ModuleLoader was not able to enable module "
 392  
                              + module.getName());
 393  
                 } else {
 394  0
                     LOG.warn("ModuleLoader was not able to disable module "
 395  
                              + module.getName());
 396  
                 }
 397  0
             }
 398  
         }
 399  900
     }
 400  
 
 401  
     /**
 402  
      * Gets the loaded status for some other module.
 403  
      *
 404  
      * @return true if the module exists and is enabled.
 405  
      * @param name is the module name of the queried module
 406  
      */
 407  
     private boolean isEnabledInternal(String name) {
 408  0
         Map.Entry<ModuleInterface, ModuleStatus> entry = findModule(name);
 409  
 
 410  0
         if (entry != null) {
 411  0
             ModuleStatus status = entry.getValue();
 412  
 
 413  0
             if (status == null) {
 414  0
                 return false;
 415  
             }
 416  
 
 417  0
             return status.isEnabled();
 418  
         }
 419  0
         return false;
 420  
     }
 421  
 
 422  
 
 423  
     /**
 424  
      * Return the ModuleInterface, ModuleStatus pair for the module
 425  
      * with the given name or <code>null</code> if there isn't any.
 426  
      *
 427  
      * @param name The given name.
 428  
      * @return A pair (Map.Entry).
 429  
      */
 430  
     private Map.Entry<ModuleInterface, ModuleStatus> findModule(String name) {
 431  435
         for (Map.Entry<ModuleInterface, ModuleStatus> entry : moduleStatus
 432  
                 .entrySet()) {
 433  1305
             ModuleInterface module = entry.getKey();
 434  1305
             if (name.equalsIgnoreCase(module.getName())) {
 435  435
                 return entry;
 436  
             }
 437  870
         }
 438  0
         return null;
 439  
     }
 440  
 
 441  
     /**
 442  
      * Tries to find as many available modules as possible.<p>
 443  
      *
 444  
      * As the modules are found they are appended to {@link #moduleStatus}.<p>
 445  
      */
 446  
     private void huntForModules() {
 447  900
         huntForModulesFromExtensionDir();
 448  
         // TODO: huntForModulesFromJavaWebStart();
 449  
 
 450  
         // Load modules specified by a System property.
 451  
         // Modules specified by a system property is for
 452  
         // running modules from within Eclipse and running
 453  
         // from Java Web Start.
 454  900
         String listOfClasses = System.getProperty("argouml.modules");
 455  900
         if (listOfClasses != null) {
 456  0
             StringTokenizer si = new StringTokenizer(listOfClasses, ";");
 457  0
             while (si.hasMoreTokens()) {
 458  0
                 String className = si.nextToken();
 459  
                 try {
 460  0
                     addClass(className);
 461  0
                 } catch (ClassNotFoundException e) {
 462  0
                     LOG.error("Could not load module from class " + className);
 463  0
                 }
 464  0
             }
 465  
         }
 466  900
     }
 467  
     
 468  
     /**
 469  
      * Find and enable modules from our "ext" directory and from the
 470  
      * directory specified in "argo.ext.dir".<p>
 471  
      */
 472  
     private void huntForModulesFromExtensionDir() {
 473  900
         for (String location : extensionLocations) {
 474  900
             huntModulesFromNamedDirectory(location);            
 475  
         }
 476  900
     }
 477  
 
 478  
     /**
 479  
      * This does a calculation of where our "ext" directory is.
 480  
      * TODO: We should eventually make sure that this calculation is
 481  
      *       only present in one place in the code and not several.
 482  
      */
 483  
     private void computeExtensionLocations() {
 484  
         // Use a little trick to find out where Argo is being loaded from.
 485  
         // TODO: Use a different resource here. ARGOINI is unused and deprecated
 486  900
         String extForm = getClass().getResource(Argo.ARGOINI).toExternalForm();
 487  900
         String argoRoot =
 488  
             extForm.substring(0,
 489  
                               extForm.length() - Argo.ARGOINI.length());
 490  
 
 491  
         // If it's a jar, clean it up and make it look like a file url
 492  900
         if (argoRoot.startsWith(JAR_PREFIX)) {
 493  0
             argoRoot = argoRoot.substring(JAR_PREFIX.length());
 494  0
             if (argoRoot.endsWith("!")) {
 495  0
                 argoRoot = argoRoot.substring(0, argoRoot.length() - 1);
 496  
             }
 497  
         }
 498  
 
 499  900
         String argoHome = null;
 500  
 
 501  900
         if (argoRoot != null) {
 502  900
             LOG.info("argoRoot is " + argoRoot);
 503  900
             if (argoRoot.startsWith(FILE_PREFIX)) {
 504  900
                 argoHome =
 505  
                     new File(argoRoot.substring(FILE_PREFIX.length()))
 506  
                         .getAbsoluteFile().getParent();
 507  
             } else {
 508  0
                 argoHome = new File(argoRoot).getAbsoluteFile().getParent();
 509  
             }
 510  
 
 511  
             try {
 512  900
                 argoHome = java.net.URLDecoder.decode(argoHome, 
 513  
                         Argo.getEncoding());
 514  0
             } catch (UnsupportedEncodingException e) {
 515  0
                 LOG.warn("Encoding " 
 516  
                         + Argo.getEncoding() 
 517  
                         + " is unknown.");
 518  900
             }
 519  
 
 520  900
             LOG.info("argoHome is " + argoHome);
 521  
         }
 522  
 
 523  900
         if (argoHome != null) {
 524  
             String extdir;
 525  900
             if (argoHome.startsWith(FILE_PREFIX)) {
 526  0
                 extdir = argoHome.substring(FILE_PREFIX.length())
 527  
                         + File.separator + "ext";
 528  
             } else {
 529  900
                 extdir = argoHome + File.separator + "ext";
 530  
             }
 531  900
             extensionLocations.add(extdir);
 532  
         }
 533  
 
 534  900
         String extdir = System.getProperty("argo.ext.dir");
 535  900
         if (extdir != null) {
 536  0
             extensionLocations.add(extdir);
 537  
         }
 538  900
     }
 539  
     
 540  
     /**
 541  
      * Get the list of locations that we've loaded extension modules from.
 542  
      * @return A list of the paths we've loaded from.
 543  
      */
 544  
     public List<String> getExtensionLocations() {
 545  919
         return Collections.unmodifiableList(extensionLocations);
 546  
     }
 547  
 
 548  
     /**
 549  
      * Find and enable a module from a given directory.
 550  
      *
 551  
      * @param dirname The name of the directory.
 552  
      */
 553  
     private void huntModulesFromNamedDirectory(String dirname) {
 554  900
         File extensionDir = new File(dirname);
 555  900
         if (extensionDir.isDirectory()) {
 556  900
             File[] files = extensionDir.listFiles(new JarFileFilter());
 557  5400
             for (File file : files) {
 558  4500
                 JarFile jarfile = null;
 559  
                 // Try-catch only the JarFile instantiation so we
 560  
                 // don't accidentally mask anything in ArgoJarClassLoader
 561  
                 // or processJarFile.
 562  
                 try {
 563  4500
                     jarfile = new JarFile(file);
 564  4500
                     if (jarfile != null) {
 565  4500
                         ClassLoader classloader =
 566  
                             new URLClassLoader(new URL[] {
 567  
                                 file.toURI().toURL(),
 568  
                             }, getClass().getClassLoader());
 569  
                         try {
 570  4500
                             processJarFile(classloader, file);
 571  0
                         } catch (ClassNotFoundException e) {
 572  0
                             LOG.error("The class is not found.", e);
 573  0
                             return;
 574  4500
                         }
 575  
                     }
 576  0
                 } catch (IOException ioe) {
 577  0
                     LOG.error("Cannot open Jar file " + file, ioe);
 578  4500
                 }
 579  
             }
 580  
         }
 581  900
     }
 582  
 
 583  
     /**
 584  
      * Check a jar file for an ArgoUML extension/module.<p>
 585  
      *
 586  
      * If there isn't a manifest or it isn't readable, we fall back to using
 587  
      * the raw JAR entries.
 588  
      *
 589  
      * @param classloader The classloader to use.
 590  
      * @param file The file to process.
 591  
      * @throws ClassNotFoundException if the manifest file contains a class
 592  
      *         that doesn't exist.
 593  
      */
 594  
     private void processJarFile(ClassLoader classloader, File file)
 595  
         throws ClassNotFoundException {
 596  
 
 597  4500
         LOG.info("Opening jar file " + file);
 598  
         JarFile jarfile;
 599  
         try {
 600  4500
             jarfile = new JarFile(file);
 601  0
         } catch (IOException e) {
 602  0
             LOG.error("Unable to open " + file, e);
 603  0
             return;
 604  4500
         }
 605  
 
 606  
         Manifest manifest;
 607  
         try {
 608  4500
             manifest = jarfile.getManifest();
 609  4500
             if (manifest == null) {
 610  
                 // We expect all extensions to have a manifest even though we
 611  
                 // can operate without one if necessary.
 612  0
                 LOG.warn(file + " does not have a manifest");
 613  
             }
 614  0
         } catch (IOException e) {
 615  0
             LOG.error("Unable to read manifest of " + file, e);
 616  0
             return;
 617  4500
         }
 618  
         
 619  
         // TODO: It is a performance drain to load all classes at startup time.
 620  
         // They should be lazy loaded when needed.  Instead of scanning all
 621  
         // classes for ones which implement our loadable module interface, we 
 622  
         // should use a manifest entry or a special name/name pattern that we
 623  
         // look for to find the single main module class to load here.  - tfm
 624  
         
 625  4500
         boolean loadedClass = false;
 626  4500
         if (manifest == null) {
 627  0
             Enumeration<JarEntry> jarEntries = jarfile.entries();
 628  0
             while (jarEntries.hasMoreElements()) {
 629  0
                 JarEntry entry = jarEntries.nextElement();
 630  0
                 loadedClass =
 631  
                         loadedClass
 632  
                                 | processEntry(classloader, entry.getName());
 633  0
             }
 634  0
         } else {
 635  4500
             Map<String, Attributes> entries = manifest.getEntries();
 636  4500
             for (String key : entries.keySet()) {
 637  
                 // Look for our specification
 638  4500
                 loadedClass =
 639  
                     loadedClass
 640  
                             | processEntry(classloader, key);
 641  
             }
 642  
         }
 643  
 
 644  
         // Add this to search list for I18N properties
 645  
         // (Done for both modules & localized property file sets)
 646  4500
         Translator.addClassLoader(classloader);
 647  
         
 648  
         // If it didn't have a loadable module class and it doesn't look like
 649  
         // a localized property set, warn the user that something funny is in
 650  
         // their extension directory
 651  4500
         if (!loadedClass && !file.getName().contains("argouml-i18n-")) {
 652  0
             LOG.error("Failed to find any loadable ArgoUML modules in jar "
 653  
                     + file);
 654  
         }
 655  4500
     }
 656  
 
 657  
     /**
 658  
      * Process a JAR file entry, attempting to load anything that looks like a
 659  
      * Java class.
 660  
      * 
 661  
      * @param classloader
 662  
      *            the classloader to use when loading the class
 663  
      * @param cname
 664  
      *            the class name
 665  
      * @throws ClassNotFoundException
 666  
      * @return true if class was a module class and loaded successfully
 667  
      */
 668  
     private boolean processEntry(ClassLoader classloader, String cname)
 669  
         throws ClassNotFoundException {
 670  4500
         if (cname.endsWith(CLASS_SUFFIX)) {
 671  4500
             int classNamelen = cname.length() - CLASS_SUFFIX.length();
 672  4500
             String className = cname.substring(0, classNamelen);
 673  4500
             className = className.replace('/', '.');
 674  4500
             return addClass(classloader, className);
 675  
         }
 676  0
         return false;
 677  
     }
 678  
 
 679  
     /**
 680  
      * Add a class from the current class loader.
 681  
      *
 682  
      * @param classname The name of the class (including package).
 683  
      * @throws ClassNotFoundException if the class classname is not found.
 684  
      */
 685  
     public static void addClass(String classname)
 686  
         throws ClassNotFoundException {
 687  
 
 688  900
         getInstance().addClass(ModuleLoader2.class.getClassLoader(),
 689  
                                classname);
 690  0
     }
 691  
 
 692  
     /**
 693  
      * Try to load a module from the given ClassLoader.<p>
 694  
      *
 695  
      * Only add it as a module if it is a module (i.e. it implements the
 696  
      * {@link ModuleInterface} interface.
 697  
      *
 698  
      * @param classLoader The ClassLoader to load from.
 699  
      * @param classname The name.
 700  
      * @throws ClassNotFoundException if the class classname is not found.
 701  
      */
 702  
     private boolean addClass(ClassLoader classLoader, String classname)
 703  
         throws ClassNotFoundException {
 704  
 
 705  5400
         LOG.info("Loading module " + classname);
 706  
         Class moduleClass;
 707  
         try {
 708  5400
             moduleClass = classLoader.loadClass(classname);
 709  0
         } catch (UnsupportedClassVersionError e) {
 710  0
             LOG.error("Unsupported Java class version for " + classname);
 711  0
             return false;
 712  0
         } catch (NoClassDefFoundError e) {
 713  0
             LOG.error("Unable to find required class while loading "
 714  
                     + classname + " - may indicate an obsolete"
 715  
                     + " extension module or an unresolved dependency", e);
 716  0
             return false;
 717  900
         } catch (Throwable e) {
 718  900
             if (e instanceof ClassNotFoundException) {
 719  900
                 throw (ClassNotFoundException) e;
 720  
             }
 721  0
             LOG.error("Unexpected error while loading " + classname, e);
 722  0
             return false;
 723  4500
         }
 724  
         
 725  4500
         if (!ModuleInterface.class.isAssignableFrom(moduleClass)) {
 726  0
             LOG.debug("The class " + classname + " is not a module.");
 727  0
             return false;
 728  
         }
 729  
 
 730  
         Constructor defaultConstructor;
 731  
         try {
 732  4500
             defaultConstructor =
 733  
                     moduleClass.getDeclaredConstructor(new Class[] {});
 734  0
         } catch (SecurityException e) {
 735  0
             LOG.error("The default constructor for class " + classname
 736  
                       + " is not accessable.",
 737  
                       e);
 738  0
             return false;
 739  0
         } catch (NoSuchMethodException e) {
 740  0
             LOG.error("The default constructor for class " + classname
 741  
                       + " is not found.", e);
 742  0
             return false;
 743  0
         } catch (NoClassDefFoundError e) {
 744  0
             LOG.error("Unable to find required class while loading "
 745  
                     + classname + " - may indicate an obsolete"
 746  
                     + " extension module or an unresolved dependency", e);
 747  0
             return false;
 748  0
         } catch (Throwable e) {
 749  0
             LOG.error("Unexpected error while loading " + classname, e);
 750  0
             return false;
 751  4500
         }
 752  
 
 753  4500
         if (!Modifier.isPublic(defaultConstructor.getModifiers())) {
 754  0
             LOG.error("The default constructor for class " + classname
 755  
                     + " is not public.  Not loaded.");
 756  0
             return false;
 757  
         }
 758  
         Object moduleInstance;
 759  
         try {
 760  4500
             moduleInstance = defaultConstructor.newInstance(new Object[]{});
 761  0
         } catch (IllegalArgumentException e) {
 762  0
             LOG.error("The constructor for class " + classname
 763  
                     + " is called with incorrect argument.", e);
 764  0
             return false;
 765  0
         } catch (InstantiationException e) {
 766  0
             LOG.error("The constructor for class " + classname
 767  
                     + " threw an exception.", e);
 768  0
             return false;
 769  0
         } catch (IllegalAccessException e) {
 770  0
             LOG.error("The constructor for class " + classname
 771  
                     + " is not accessible.", e);
 772  0
             return false;
 773  0
         } catch (InvocationTargetException e) {
 774  0
             LOG.error("The constructor for class " + classname
 775  
                     + " cannot be called.", e);
 776  0
             return false;
 777  0
         } catch (NoClassDefFoundError e) {
 778  0
             LOG.error("Unable to find required class while instantiating "
 779  
                     + classname + " - may indicate an obsolete"
 780  
                     + " extension module or an unresolved dependency", e);
 781  0
             return false;
 782  0
         } catch (Throwable e) {
 783  0
             LOG.error("Unexpected error while instantiating " + classname, e);
 784  0
             return false;
 785  4500
         }
 786  
 
 787  
         // The following check should have been satisfied before we
 788  
         // instantiated the module, but double check again
 789  4500
         if (!(moduleInstance instanceof ModuleInterface)) {
 790  0
             LOG.error("The class " + classname + " is not a module.");
 791  0
             return false;
 792  
         }
 793  4500
         ModuleInterface mf = (ModuleInterface) moduleInstance;
 794  
 
 795  4500
         addModule(mf);
 796  4500
         LOG.info("Succesfully loaded module " + classname);
 797  4500
         return true;
 798  
     }
 799  
 
 800  
     /**
 801  
      * Add a newly found module to {@link #moduleStatus}. If we already
 802  
      * know about it, don't add it.
 803  
      *
 804  
      * @param mf The module to add.
 805  
      */
 806  
     private void addModule(ModuleInterface mf) {
 807  
         // Since there is no way to compare the objects as equal,
 808  
         // we have to search through the list at this point.
 809  4500
         for (ModuleInterface foundMf : moduleStatus.keySet()) {
 810  9000
             if (foundMf.getName().equals(mf.getName())) {
 811  0
                 return;
 812  
             }
 813  
         }
 814  
 
 815  
         // We havn't found it. Add it.
 816  4500
         ModuleStatus ms = new ModuleStatus();
 817  
 
 818  
         // Enable it.
 819  
         // TODO: This by default selects all modules that are found.
 820  
         //       Eventually we would rather obey a default either from the
 821  
         //       modules themselves, from how they are found, and also
 822  
         //       have information on what modules are selected from the
 823  
         //       configuration.
 824  4500
         ms.setSelected();
 825  
 
 826  4500
         moduleStatus.put(mf, ms);
 827  4500
     }
 828  
 
 829  
 
 830  
     /**
 831  
      * The file filter that selects Jar files.
 832  
      */
 833  900
     static class JarFileFilter implements FileFilter {
 834  
         /*
 835  
          * @see java.io.FileFilter#accept(java.io.File)
 836  
          */
 837  
         public boolean accept(File pathname) {
 838  4500
             return (pathname.canRead()
 839  
                     && pathname.isFile()
 840  
                     && pathname.getPath().toLowerCase().endsWith(".jar"));
 841  
         }
 842  
     }
 843  
 }
 844  
 
 845  
 
 846  
 /**
 847  
  * Status for each of the available modules. This is created in one copy per
 848  
  * available module.
 849  
  */
 850  4500
 class ModuleStatus {
 851  
     /**
 852  
      * If the module is enabled.
 853  
      */
 854  
     private boolean enabled;
 855  
 
 856  
     /**
 857  
      * If the module is selected.
 858  
      */
 859  
     private boolean selected;
 860  
 
 861  
     /**
 862  
      * Tells if the module is enabled or not.
 863  
      *
 864  
      * @return true if the module is enabled.
 865  
      */
 866  
     public boolean isEnabled() {
 867  22500
         return enabled;
 868  
     }
 869  
 
 870  
     /**
 871  
      * Setter for enabled.
 872  
      */
 873  
     public void setEnabled() {
 874  4500
         enabled = true;
 875  4500
     }
 876  
 
 877  
     /**
 878  
      * Setter for enabled.
 879  
      */
 880  
     public void setDisabled() {
 881  0
         enabled = false;
 882  0
     }
 883  
 
 884  
     /**
 885  
      * Tells if the module is selected by the user or not.
 886  
      *
 887  
      * @return true if it is selected.
 888  
      */
 889  
     public boolean isSelected() {
 890  13690
         return selected;
 891  
     }
 892  
 
 893  
 
 894  
     /**
 895  
      * Setter for selected.
 896  
      */
 897  
     public void setSelected() {
 898  4500
         selected = true;
 899  4500
     }
 900  
 
 901  
     /**
 902  
      * Setter for selected.
 903  
      */
 904  
     public void setUnselect() {
 905  0
         selected = false;
 906  0
     }
 907  
 
 908  
     /**
 909  
      * Setter for selected.
 910  
      *
 911  
      * @param value The value to set.
 912  
      */
 913  
     public void setSelected(boolean value) {
 914  0
         if (value) {
 915  0
             setSelected();
 916  
         } else {
 917  0
             setUnselect();
 918  
         }
 919  0
     }
 920  
 }