/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <https://www.compadre.org/osp/>
 */

package org.opensourcephysics.tools;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.display.OSPRuntime;

/**
 * This modal dialog lets the user choose launchable classes from jar files.
 */
public class LaunchClassChooser extends JDialog {
  // static fields
  private static Pattern pattern;
  private static Matcher matcher;
  private static Map<String, LaunchableClassMap> classMaps = new TreeMap<String, LaunchableClassMap>(); // maps path to classMap
  protected static boolean jarsOnly = true;
  protected static String baseDirectoryPath;
  // instance fields
  private JTextField searchField;
  private String defaultSearch = "";   //$NON-NLS-1$
  private String currentSearch = defaultSearch;
  private JScrollPane scroller;
  private JList choices;               // list of search results (class names)
  private LaunchableClassMap classMap; // map of  available launchable classes
  private boolean applyChanges = false;
  private JButton okButton;

  /**
   * Constructs an empty LaunchClassChooser dialog.
   *
   * @param owner the component that owns the dialog (may be null)
   */
  public LaunchClassChooser(Component owner) {
    super(JOptionPane.getFrameForComponent(owner), true);
    setTitle(LaunchRes.getString("ClassChooser.Frame.Title")); //$NON-NLS-1$
    JLabel textLabel = new JLabel(LaunchRes.getString("ClassChooser.Search.Label")+" "); //$NON-NLS-1$ //$NON-NLS-2$
    // create the buttons
    okButton = new JButton(LaunchRes.getString("ClassChooser.Button.Accept")); //$NON-NLS-1$
    okButton.setEnabled(false);
    okButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        applyChanges = true;
        setVisible(false);
      }

    });
    JButton cancelButton = new JButton(LaunchRes.getString("ClassChooser.Button.Cancel")); //$NON-NLS-1$
    cancelButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        setVisible(false);
      }

    });
    // create the search field
    searchField = new JTextField(defaultSearch);
    searchField.addKeyListener(new KeyAdapter() {
      public void keyReleased(KeyEvent e) {
        Object obj = choices.getSelectedValue();
        if("model".equals(searchField.getName())) { //$NON-NLS-1$
          searchForModel();
        } else {
          search();
        }
        choices.setSelectedValue(obj, true);
      }

    });
    getRootPane().setDefaultButton(okButton);
    // lay out the header pane
    JPanel headerPane = new JPanel();
    headerPane.setLayout(new BoxLayout(headerPane, BoxLayout.X_AXIS));
    headerPane.add(textLabel);
    headerPane.add(Box.createHorizontalGlue());
    headerPane.add(searchField);
    headerPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10));
    // lay out the scroll pane
    JPanel scrollPane = new JPanel(new BorderLayout());
    scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    // lay out the button pane
    JPanel buttonPane = new JPanel();
    buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
    buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
    buttonPane.add(Box.createHorizontalGlue());
    buttonPane.add(okButton);
    buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
    buttonPane.add(cancelButton);
    // add everything to the content pane
    Container contentPane = getContentPane();
    contentPane.add(headerPane, BorderLayout.NORTH);
    contentPane.add(scrollPane, BorderLayout.CENTER);
    contentPane.add(buttonPane, BorderLayout.SOUTH);
    // create the scroll pane
    scroller = new JScrollPane();
    scroller.setPreferredSize(new Dimension(400, 300));
    scrollPane.add(scroller, BorderLayout.CENTER);
    pack();
    // center dialog on the screen
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (dim.width-this.getBounds().width)/2;
    int y = (dim.height-this.getBounds().height)/2;
    setLocation(x, y);
  }

  /**
   * Sets the path to be searched. The path must be a set of jar file names
   * separated by semicolons unless jarsOnly is set to false.
   *
   * @param path the search path
   * @return true if at least one jar file was successfully loaded
   */
  public boolean setPath(String path) {
    String[] jarNames = parsePath(path);
    // reset class map to null
    classMap = null;
    if((jarNames==null)||(jarNames.length==0)) {
      return false;
    }
    // create classMaps key
    String key = ""; //$NON-NLS-1$
    for(int i = 0; i<jarNames.length; i++) {
      if(!key.equals("")) { //$NON-NLS-1$
        // separate jars with semicolon
        key += ";";         //$NON-NLS-1$
      }
      key += jarNames[i];
    }
    // set the current classMap
    classMap = classMaps.get(key);
    if(classMap==null) {
      classMap = new LaunchableClassMap(jarNames);
      classMaps.put(key, classMap);
    }
    return true;
  }

  /**
   * Determines if the specified path is loaded. This will return true
   * only if the path is one or more jar files all of which are loaded.
   *
   * @param path the path
   * @return true if all jars in the path are loaded
   */
  public boolean isLoaded(String path) {
    if(classMap==null) {
      return false;
    }
    String[] jarNames = parsePath(path);
    for(int i = 0; i<jarNames.length; i++) {
      if(!classMap.includesJar(jarNames[i])) {
        return false;
      }
    }
    return true;
  }

  /**
   * Chooses a launchable class and assigns it to the specified launch node.
   *
   * @param node the node
   * @return true if the class assignment is approved
   */
  public boolean chooseClassFor(LaunchNode node) {
    if("model".equals(searchField.getName())) { //$NON-NLS-1$
      searchField.setText(null);
    }
    setTitle(LaunchRes.getString("ClassChooser.Frame.Title")); //$NON-NLS-1$
    searchField.setName("launch");                             //$NON-NLS-1$
    search();
    // select node's current launch class, if any
    choices.setSelectedValue(node.launchClassName, true);
    applyChanges = false;
    setVisible(true);
    if(!applyChanges) {
      return false;
    }
    Object obj = choices.getSelectedValue();
    if(obj==null) {
      return false;
    }
    String className = obj.toString();
    // get the class name and the launchable class
    node.launchClass = classMap.get(className);
    node.launchClassName = className;
    node.launchModelScroller = null;
    return true;
  }

  /**
   * Chooses and returns a model class.
   *
   * @param previousClassName the previous class name. May be null.
   * @return the newly selected class. May return null.
   */
  public Class<?> chooseModel(String previousClassName) {
    if("launch".equals(searchField.getName())) { //$NON-NLS-1$
      searchField.setText(null);
    }
    setTitle(LaunchRes.getString("ModelClassChooser.Frame.Title")); //$NON-NLS-1$
    searchField.setName("model");                                   //$NON-NLS-1$
    searchForModel();
    // select tab's previous model class, if any
    choices.setSelectedValue(previousClassName, true);
    applyChanges = false;
    setVisible(true);
    if(!applyChanges) {
      return null;
    }
    Object obj = choices.getSelectedValue();
    if(obj==null) {
      return null;
    }
    String className = obj.toString();
    return classMap.models.get(className);
  }

  /**
   * Gets the class with the given name in the current class map.
   *
   * @param className the class name
   * @return the Class object, or null if not found
   */
  public Class<?> getClass(String className) {
    if(classMap==null) {
      return null;
    }
    return classMap.getClass(className);
  }

  /**
   * Sets the base directory for classpaths.
   *
   * @param path the base directory path
   */
  public static void setBasePath(String path) {
  	baseDirectoryPath = path;
  }

  
  /**
   * Gets the class with the given name in the specified path.
   *
   * @param classPath the path
   * @param className the class name
   * @return the Class object, or null if not found
   */
  public static Class<?> getClass(String classPath, String className) {
    if((classPath==null)||(className==null)) {
      return null;
    }
    // get the classMap for the specified path
    String[] jarNames = parsePath(classPath);
    LaunchableClassMap classMap = getClassMap(jarNames);
    // get the class from the classMap
    return classMap.getClass(className);
  }

  /**
   * Gets the modelPane class with the given name in the specified path.
   *
   * @param classPath the path
   * @param className the class name
   * @return the Class object, or null if not found
   */
  public static Class<?> getModelClass(String classPath, String className) {
    if((classPath==null)||(className==null)) {
      return null;
    }
    // get the classMap for the specified path
    String[] jarNames = parsePath(classPath);
    LaunchableClassMap classMap = getClassMap(jarNames);
    // get the class from the classMap
    return classMap.getModelClass(className);
  }

  /**
   * Gets the class with the given name in the specified path.
   *
   * @param classPath the path
   * @param className the class name
   * @param type a subclass of the desired class
   * @return the Class object, or null if not found
   */
  public static Class<?> getClassOfType(String classPath, String className, Class<?> type) {
    if((classPath==null)||(className==null)) {
      return null;
    }
    // get the classMap for the specified path
    String[] jarNames = parsePath(classPath);
    LaunchableClassMap classMap = getClassMap(jarNames);
    // get the class from the classMap
    return classMap.getClassOfType(className, type);
  }

  /**
   * Gets the ClassLoader for the specified path.
   *
   * @param classPath the path
   * @return the ClassLoader object, or null if not found
   */
  public static ClassLoader getClassLoader(String classPath) {
    if((classPath==null)||classPath.equals("")) { //$NON-NLS-1$
      return null;
    }
    // get the classMap for the specified path
    String[] jarNames = parsePath(classPath);
    LaunchableClassMap classMap = getClassMap(jarNames);
    // get the class loader from the classMap
    return classMap.classLoader;
  }

  /**
   * Gets the launchable class map for the specified jar name array.
   *
   * @param jarNames the string array of jar names
   * @return the class map
   */
  private static LaunchableClassMap getClassMap(String[] jarNames) {
    // create a key string from the jar names
    String key = ""; //$NON-NLS-1$
    for(int i = 0; i<jarNames.length; i++) {
      if(!key.equals("")) { //$NON-NLS-1$
        key += ";";         //$NON-NLS-1$
      }
      key += jarNames[i];
    }
    // get the classMap for the key
    LaunchableClassMap classMap = classMaps.get(key);
    if(classMap==null) {
      classMap = new LaunchableClassMap(jarNames);
      classMaps.put(key, classMap);
    }
    return classMap;
  }

  /**
   * Searches using the current search field text.
   */
  private void search() {
    if(classMap==null) {
      return;
    }
    classMap.loadAllClasses();
    if(search(searchField.getText())) {
      currentSearch = searchField.getText();
      searchField.setBackground(Color.white);
    } else {
      JOptionPane.showMessageDialog(this, LaunchRes.getString("Dialog.InvalidRegex.Message")+" \""+searchField.getText()+"\"", LaunchRes.getString("Dialog.InvalidRegex.Title"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
      searchField.setText(currentSearch);
    }
  }

  /**
   * Searches for a modelPane using the current search field text.
   */
  private void searchForModel() {
    if(classMap==null) {
      return;
    }
    classMap.loadAllClasses();
    if(searchForModel(searchField.getText())) {
      currentSearch = searchField.getText();
      searchField.setBackground(Color.white);
    } else {
      JOptionPane.showMessageDialog(this, LaunchRes.getString("Dialog.InvalidRegex.Message")+" \""+searchField.getText()+"\"", LaunchRes.getString("Dialog.InvalidRegex.Title"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
      searchField.setText(currentSearch);
    }
  }

  /**
   * Searches for class names using a regular expression string
   * and puts matches into the class chooser list of choices.
   *
   * @param regex the regular expression
   * @return true if the search succeeded (even if no matches found)
   */
  private boolean search(String regex) {
    regex = regex.toLowerCase();
    okButton.setEnabled(false);
    try {
      pattern = Pattern.compile(regex);
    } catch(Exception ex) {
      return false;
    }
    ArrayList<String> matches = new ArrayList<String>();
    for(Iterator<?> it = classMap.keySet().iterator(); it.hasNext(); ) {
      String name = (String) it.next();
      matcher = pattern.matcher(name.toLowerCase());
      if(matcher.find()) {
        matches.add(name);
      }
    }
    Object[] results = matches.toArray();
    choices = new JList(results);
    choices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    choices.setFont(searchField.getFont());
    choices.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        JList theList = (JList) e.getSource();
        okButton.setEnabled(!theList.isSelectionEmpty());
      }

    });
    choices.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        JList theList = (JList) e.getSource();
        if((e.getClickCount()==2)&&!theList.isSelectionEmpty()) {
          okButton.doClick();
        }
      }

    });
    scroller.getViewport().setView(choices);
    return true;
  }

  /**
   * Searches for class names using a regular expression string
   * and puts matches into the class chooser list of choices.
   *
   * @param regex the regular expression
   * @return true if the search succeeded (even if no matches found)
   */
  private boolean searchForModel(String regex) {
    regex = regex.toLowerCase();
    okButton.setEnabled(false);
    try {
      pattern = Pattern.compile(regex);
    } catch(Exception ex) {
      return false;
    }
    ArrayList<String> matches = new ArrayList<String>();
    for(Iterator<?> it = classMap.models.keySet().iterator(); it.hasNext(); ) {
      String name = (String) it.next();
      matcher = pattern.matcher(name.toLowerCase());
      if(matcher.find()) {
        matches.add(name);
      }
    }
    Object[] results = matches.toArray();
    choices = new JList(results);
    choices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    choices.setFont(searchField.getFont());
    choices.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        JList theList = (JList) e.getSource();
        okButton.setEnabled(!theList.isSelectionEmpty());
      }

    });
    choices.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        JList theList = (JList) e.getSource();
        if((e.getClickCount()==2)&&!theList.isSelectionEmpty()) {
          okButton.doClick();
        }
      }

    });
    scroller.getViewport().setView(choices);
    return true;
  }

  /**
   * Parses the specified path into path tokens (at semicolons).
   *
   * @param path the path
   * @param jarsOnly true if only ".jar" names are returned
   * @return an array of path names
   */
  static String[] parsePath(String path) {
    return parsePath(path, jarsOnly);
  }

  /**
   * Parses the specified path into path tokens (at semicolons).
   *
   * @param path the path
   * @param jarsOnly true if only ".jar" names are returned
   * @return an array of path names
   */
  static String[] parsePath(String path, boolean jarsOnly) {
    Collection<String> tokens = new ArrayList<String>();
    // get the first path token
    String next = path;
    int i = path.indexOf(";"); //$NON-NLS-1$
    if(i!=-1) {
      next = path.substring(0, i);
      path = path.substring(i+1);
    } else {
      path = ""; //$NON-NLS-1$
    }
    // iterate thru the path tokens, trim and add to token list
    while(next.length()>0) {
      if(!jarsOnly||next.endsWith(".jar")) { //$NON-NLS-1$
        tokens.add(next);
      }
      i = path.indexOf(";");                 //$NON-NLS-1$
      if(i==-1) {
        next = path.trim();
        path = "";                           //$NON-NLS-1$
      } else {
        next = path.substring(0, i).trim();
        path = path.substring(i+1).trim();
      }
    }
    return tokens.toArray(new String[0]);
  }

}

/**
 * A map of class name to launchable Class object.
 * The name of the jar or directory is prepended to the class name.
 */
class LaunchableClassMap extends TreeMap<String, Class<?>> {
  // instance fields
  ClassLoader classLoader; // loads classes
  String[] jarOrDirectoryNames; // paths of jars or directories relative to jar base
  boolean allLoaded = false;
  TreeMap<String, Class<?>> models = new TreeMap<String, Class<?>>();

  // constructor creates class loader
  LaunchableClassMap(String[] names) {
    jarOrDirectoryNames = names;
    // create a URL for each jar or directory
    Collection<URL> urls = new ArrayList<URL>();
    // changed by D Brown 2007-11-06 for Linux
    // changed by F Esquembre and D Brown 2010-03-02 to allow arbitrary base path
    String basePath = LaunchClassChooser.baseDirectoryPath;
    if (basePath==null) basePath = OSPRuntime.getLaunchJarDirectory();
    for(int i = 0; i<names.length; i++) {
      String path = XML.getResolvedPath(names[i], basePath);
      if (!path.endsWith(".jar") && !path.endsWith("/")) { //$NON-NLS-1$ //$NON-NLS-2$
      	path += "/";  // directories passed to URLClassLoader must end with slash //$NON-NLS-1$
      }
      try {
        urls.add(new URL("file:"+path)); //$NON-NLS-1$
      } catch(MalformedURLException ex) {
        OSPLog.info(ex+" "+path);        //$NON-NLS-1$
      }
    }
    // create the class loader
    classLoader = URLClassLoader.newInstance(urls.toArray(new URL[0]));
  }

  /**
   * Loads a class from the URLClassLoader or, if this fails, from the
   * current class loader.
   *
   * @param name the class name
   * @return the Class
   * @throws ClassNotFoundException
   */
  Class<?> smartLoadClass(String name) throws ClassNotFoundException {
    try {
      return classLoader.loadClass(name);
    } catch(ClassNotFoundException e) { // added by Kip Barros
      return this.getClass().getClassLoader().loadClass(name);
    }
  }
  
  // returns a list of class files at all depths in a specified directory
  ArrayList<File> getClassFiles(File directory) {
  	ArrayList<File> files = new ArrayList<File>();
  	for (File next: directory.listFiles()) {
  		if (next.isDirectory()) {
  			files.addAll(getClassFiles(next)); 
  		}
  		else if (next.getName().endsWith(".class")) { //$NON-NLS-1$
  			files.add(next);
  		}
  	}
  	return files;
  }

  // loads all launchable and model classes
  void loadAllClasses() {
    if(allLoaded) {
      return;
    }
    JApplet applet = org.opensourcephysics.display.OSPRuntime.applet;
    // for each jar or directory name, find launchable classes
    for(String next: jarOrDirectoryNames) {
    	if (next.indexOf(".jar")>-1) { // next is a relative jar path  //$NON-NLS-1$
	      // create a JarFile
	      JarFile jar = null;
	      try {
	        if(applet==null) { // application mode
	          String basePath = LaunchClassChooser.baseDirectoryPath;
	          if (basePath==null) basePath = OSPRuntime.getLaunchJarDirectory();
	          String path = XML.getResolvedPath(next, basePath);
	          jar = new JarFile(path);
	        } 
	        else { // applet mode
	          String path = XML.getResolvedPath(next, applet.getCodeBase().toExternalForm());
	          // create a URL that refers to a jar file on the web
	          URL url = new URL("jar:"+path+"!/");   //$NON-NLS-1$ //$NON-NLS-2$
	          // get the jar
	          JarURLConnection conn = (JarURLConnection) url.openConnection();
	          jar = conn.getJarFile();
	        }
	      } catch(Exception ex) {
	        OSPLog.info(ex.getClass().getName()+": "+ex.getMessage()); //$NON-NLS-1$
	      }
	      if(jar==null) {
	        continue;
	      }
	      // iterate thru JarFile entries and load classes
	      for(Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
	        JarEntry entry = e.nextElement();
	        String name = entry.getName();
	        if(name.endsWith(".class")) {     //$NON-NLS-1$
	          loadClass(name);
	        }
	      }
    	}
    	else { // next is a relative directory path
        String basePath = LaunchClassChooser.baseDirectoryPath;
        if (basePath==null) basePath = OSPRuntime.getLaunchJarDirectory();
        String directoryPath = XML.getResolvedPath(next, basePath);
        for (File nextFile: getClassFiles(new File(directoryPath))) {
          String name = XML.getPathRelativeTo(nextFile.getPath(), directoryPath);
          loadClass(name);
        }
    	}
    }
    allLoaded = true;
  }
  
  // load a class and, if launchable or a model, store it with class name key
  void loadClass(String name) {
    // ignore manifest and inner classes
    if(name.indexOf("$")==-1) {     //$NON-NLS-1$
      // remove class extension and replace slashes
      name = name.substring(0, name.indexOf(".class"));        //$NON-NLS-1$
      int j = name.indexOf("/");                               //$NON-NLS-1$
      while(j!=-1) {
        name = name.substring(0, j)+"."+name.substring(j+1);   //$NON-NLS-1$
        j = name.indexOf("/");                                 //$NON-NLS-1$
      }
      // return if class is already loaded
      if((get(name)!=null)||(models.get(name)!=null)) {
        return;
      }
      try {
        // load the class
        Class<?> nextClass = smartLoadClass(name);  // changed by Kip Barros
        // if launchable, store it
        if(Launcher.isLaunchable(nextClass)) {
          put(name, nextClass);
        }
        // if a model, store it
        if(Launcher.isModel(nextClass)) {
          models.put(name, nextClass);
        }
      } catch(ClassNotFoundException ex) {
        /** empty block */
      } catch(NoClassDefFoundError err) {
        OSPLog.info(err.toString());
      }
    }  	
  }

  // returns true of this classMap includes specified jar
  boolean includesJar(String jarName) {
    for(int i = 0; i<jarOrDirectoryNames.length; i++) {
      if(jarOrDirectoryNames[i].equals(jarName)) {
        return true;
      }
    }
    return false;
  }

  // gets the specified class, or null if not loadable or launchable
  Class<?> getClass(String className) {
    Class<?> type = get(className);
    if((type!=null)||allLoaded) {
      return type;
    }
    try {
      // load the class and, if launchable, return it
      type = smartLoadClass(className); // changed by Kip Barros
      if(Launcher.isLaunchable(type)) {
        return type;
      }
    } catch(ClassNotFoundException ex) {
      /** empty block */
    } catch(NoClassDefFoundError err) {
      OSPLog.info(err.toString());
    }
    return null;
  }

  // gets the specified model class, or null if not loadable or not a model
  Class<?> getModelClass(String className) {
    Class<?> type = models.get(className);
    if((type!=null)) {
      return type;
    }
    try {
      // load the class and, if a model, return it
      type = smartLoadClass(className); // changed by Kip Barros
      if(Launcher.isModel(type)) {
        return type;
      }
      if(JComponent.class.isAssignableFrom(type)) {
        try {
          type.getConstructor((Class[]) null);
          return type;
        } catch(Exception ex) {}
      }
    } catch(ClassNotFoundException ex) {}
    catch(NoClassDefFoundError err) {
      OSPLog.info(err.toString());
    }
    return null;
  }

  // gets the specified class, or null if not found or wrong type
  Class<?> getClassOfType(String className, Class<?> type) {
    try {
      // load the class and, if right type, return it
    	Class<?> theClass = smartLoadClass(className);
      if(type.isAssignableFrom(theClass)) {
        return theClass;
      }
    } catch(ClassNotFoundException ex) {
      /** empty block */
    } catch(NoClassDefFoundError err) {
      OSPLog.info(err.toString());
    }
    return null;
  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2019  The Open Source Physics project
 *                     https://www.compadre.org/osp
 */