content: /* ========================================================================
   * JCommon : a free general purpose class library for the Java(tm) platform
   * ========================================================================
   *
   * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jcommon/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library 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 Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
   * in the United States and other countries.]
   *
   * -------------------
   * AbstractModule.java
   * -------------------
   * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
   *
   * Original Author:  Thomas Morgner;
   * Contributor(s):   David Gilbert (for Object Refinery Limited);
   *
   * $Id: AbstractModule.java,v 1.7 2008/09/10 09:16:54 mungady Exp $
   *
   * Changes
   * -------
   * 05-Jul-2003 : Initial version
   * 07-Jun-2004 : Added JCommon header (DG);
   *
   */
  
  package org.jfree.base.modules;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.util.ArrayList;
  
  import org.jfree.util.ObjectUtilities;
  
  
  /**
   * The abstract module provides a default implementation of the module interface.
   * <p>
   * The module can be specified in an external property file. The file name of this
   * specification defaults to "module.properties". This file is no real property file,
   * it follows a more complex rule set.
   * <p>
   * Lines starting with '#' are considered comments.
   * Section headers start at the beginning of the line, section properties
   * are indented with at least one whitespace.
   * <p>
   * The first section is always the module info and contains the basic module
   * properties like name, version and a short description.
   * <p>
   * <pre>
   * module-info:
   *   name: xls-export-gui
   *   producer: The JFreeReport project - www.jfree.org/jfreereport
   *   description: A dialog component for the Excel table export.
   *   version.major: 0
   *   version.minor: 84
   *   version.patchlevel: 0
   * </pre>
   * The properties name, producer and description are simple strings. They may
   * span multiple lines, but may not contain a colon (':').
   * The version properties are integer values.
   * <p>
   * This section may be followed by one or more "depends" sections. These
   * sections describe the base modules that are required to be active to make this
   * module work. The package manager will enforce this policy and will deactivate this
   * module if one of the base modules is missing.
   * <p>
   * <pre>
   * depends:
   *   module: org.jfree.report.modules.output.table.xls.XLSTableModule
   *   version.major: 0
   *   version.minor: 84
   * </pre>
   * <p>
   * The property module references to the module implementation of the module package.
   *
   * @author Thomas Morgner
   */
  public abstract class AbstractModule extends DefaultModuleInfo implements Module
  {
    /**
     * The reader helper provides a pushback interface for the reader to read and
     * buffer  complete lines.
     * @author Thomas Morgner
     */
    private static class ReaderHelper
    {
      /** The line buffer containing the last line read. */
      private String buffer;
      /** The reader from which to read the text. */
      private final BufferedReader reader;
  
      /**
       * Creates a new reader helper for the given buffered reader.
       *
       * @param reader the buffered reader that is the source of the text.
       */
      protected ReaderHelper(final BufferedReader reader)
      {
        this.reader = reader;
      }
  
      /**
       * Checks, whether the reader contains a next line. Returns false if the end
       * of the stream has been reached.
       *
       * @return true, if there is a next line to read, false otherwise.
       * @throws IOException if an error occures.
       */
      public boolean hasNext() throws IOException
      {
        if (this.buffer == null)
        {
          this.buffer = readLine();
        }
        return this.buffer != null;
      }
  
      /**
       * Returns the next line.
       *
       * @return the next line.
       */
      public String next()
      {
        final String line = this.buffer;
        this.buffer = null;
        return line;
      }
  
      /**
       * Pushes the given line back into the buffer. Only one line can be contained in
       * the buffer at one time.
       *
       * @param line the line that should be pushed back into the buffer.
       */
      public void pushBack(final String line)
      {
        this.buffer = line;
      }
  
      /**
       * Reads the next line skipping all comment lines.
       *
       * @return the next line, or null if no line can be read.
       * @throws IOException if an IO error occures.
       */
      protected String readLine() throws IOException
      {
        String line = this.reader.readLine();
        while (line != null && (line.length() == 0 || line.startsWith("#")))
        {
          // empty line or comment is ignored
          line = this.reader.readLine();
        }
        return line;
      }
  
      /**
       * Closes the reader.
       *
       * @throws IOException if an IOError occurs.
       */
      public void close() throws IOException
      {
        this.reader.close();
      }
    }
  
    /** The list of required modules. */
    private ModuleInfo[] requiredModules;
    /** The list of optional modules. */
    private ModuleInfo[] optionalModules;
  
    /** The name of the module. */
    private String name;
    /** A short description of the module. */
    private String description;
    /** The name of the module producer. */
    private String producer;
    /** The modules subsystem. */
    private String subsystem;
  
    /**
     * Default Constructor.
     */
    public AbstractModule()
    {
      setModuleClass(this.getClass().getName());
    }
  
    /**
     * Loads the default module description from the file "module.properties". This file
     * must be in the same package as the implementing class.
     *
     * @throws ModuleInitializeException if an error occurs.
     */
    protected void loadModuleInfo() throws ModuleInitializeException
    {
      final InputStream in = ObjectUtilities.getResourceRelativeAsStream
              ("module.properties", getClass());
      if (in == null)
      {
        throw new ModuleInitializeException
            ("File 'module.properties' not found in module package.");
      }
  
      loadModuleInfo(in);
    }
  
    /**
     * Loads the module descriptiong from the given input stream. The module description
     * must conform to the rules define in the class description. The file must be encoded
     * with "ISO-8859-1" (like property files).
     *
     * @param in the input stream from where to read the file
     * @throws ModuleInitializeException if an error occurs.
     */
    protected void loadModuleInfo(final InputStream in) throws ModuleInitializeException
    {
      if (in == null)
      {
        throw new NullPointerException
            ("Given InputStream is null.");
      }
  
      try
      {
        final ArrayList optionalModules = new ArrayList();
        final ArrayList dependendModules = new ArrayList();
        final ReaderHelper rh = new ReaderHelper(new BufferedReader
            (new InputStreamReader(in, "ISO-8859-1")));
        try
        {
          while (rh.hasNext())
          {
            final String lastLineRead = rh.next();
            if (lastLineRead.startsWith("module-info:"))
            {
              readModuleInfo(rh);
            }
            else if (lastLineRead.startsWith("depends:"))
            {
              dependendModules.add(readExternalModule(rh));
            }
            else if (lastLineRead.startsWith("optional:"))
            {
              optionalModules.add(readExternalModule(rh));
            }
            else
            {
              // we dont understand the current line, so we skip it ...
              // should we throw a parse exception instead?
            }
          }
        }
        finally
        {
          rh.close();
        }
  
        this.optionalModules = (ModuleInfo[])
            optionalModules.toArray(new ModuleInfo[optionalModules.size()]);
  
        this.requiredModules = (ModuleInfo[])
            dependendModules.toArray(new ModuleInfo[dependendModules.size()]);
      }
      catch (IOException ioe)
      {
        throw new ModuleInitializeException("Failed to load properties", ioe);
      }
    }
  
    /**
     * Reads a multiline value the stream. This will read the stream until
     * a new key is found or the end of the file is reached.
     *
     * @param reader the reader from where to read.
     * @param firstLine the first line (which was read elsewhere).
     * @return the complete value, never null
     * @throws IOException if an IO error occurs.
     */
    private String readValue(final ReaderHelper reader, String firstLine) throws IOException
    {
      final StringBuffer b = new StringBuffer(firstLine.trim());
      boolean newLine = true;
      while (isNextLineValueLine(reader))
      {
        firstLine = reader.next();
        final String trimedLine = firstLine.trim();
        if (trimedLine.length() == 0 && (newLine == false))
        {
          b.append ("\n");
          newLine = true;
        }
        else
        {
          if (newLine == false)
          {
            b.append(" ");
          }
          b.append(parseValue(trimedLine));
          newLine = false;
        }
      }
      return b.toString();
    }
  
    /**
     * Checks, whether the next line in the reader is a value line.
     *
     * @param reader from where to read the lines.
     * @return true, if the next line is a value line, false otherwise.
     * @throws IOException if an IO error occurs.
     */
    private boolean isNextLineValueLine (final ReaderHelper reader) throws IOException
    {
      if (reader.hasNext() == false)
      {
        return false;
      }
      final String firstLine = reader.next();
      if (firstLine == null)
      {
        return false;
      }
      if (parseKey(firstLine) != null)
      {
        reader.pushBack(firstLine);
        return false;
      }
      reader.pushBack(firstLine);
      return true;
    }
  
    /**
     * Reads the module definition header. This header contains information about
     * the module itself.
     *
     * @param reader the reader from where to read the content.
     * @throws IOException if an error occures
     */
    private void readModuleInfo(final ReaderHelper reader) throws IOException
    {
      while (reader.hasNext())
      {
        final String lastLineRead = reader.next();
  
        if (Character.isWhitespace(lastLineRead.charAt(0)) == false)
        {
          // break if the current character is no whitespace ...
          reader.pushBack(lastLineRead);
          return;
        }
  
        final String line = lastLineRead.trim();
        final String key = parseKey(line);
        if (key != null)
        {
          // parse error: Non data line does not contain a colon
          final String b = readValue(reader, parseValue(line.trim()));
  
          if ("name".equals(key))
          {
            setName(b);
          }
          else if ("producer".equals(key))
          {
            setProducer(b);
          }
          else if ("description".equals(key))
          {
            setDescription(b);
          }
          else if ("subsystem".equals(key))
          {
            setSubSystem(b);
          }
          else if ("version.major".equals(key))
          {
            setMajorVersion(b);
          }
          else if ("version.minor".equals(key))
          {
            setMinorVersion(b);
          }
          else if ("version.patchlevel".equals(key))
          {
            setPatchLevel(b);
          }
        }
      }
    }
  
    /**
     * Parses an string to find the key section of the line. This section ends with
     * an colon.
     *
     * @param line the line which to parse
     * @return the key or null if no key is found.
     */
    private String parseKey(final String line)
    {
      final int idx = line.indexOf(':');
      if (idx == -1)
      {
        return null;
      }
      return line.substring(0, idx);
    }
  
    /**
     * Parses the value section of the given line.
     *
     * @param line the line that should be parsed
     * @return the value, never null
     */
    private String parseValue(final String line)
    {
      final int idx = line.indexOf(':');
      if (idx == -1)
      {
        return line;
      }
      if ((idx + 1) == line.length())
      {
        return "";
      }
      return line.substring(idx + 1);
    }
  
    /**
     * Reads an external module description. This describes either an optional or
     * a required module.
     *
     * @param reader the reader from where to read the module
     * @return the read module, never null
     * @throws IOException if an error occures.
     */
    private DefaultModuleInfo readExternalModule(final ReaderHelper reader)
        throws IOException
    {
      final DefaultModuleInfo mi = new DefaultModuleInfo();
  
      while (reader.hasNext())
      {
        final String lastLineRead = reader.next();
  
        if (Character.isWhitespace(lastLineRead.charAt(0)) == false)
        {
          // break if the current character is no whitespace ...
          reader.pushBack(lastLineRead);
          return mi;
        }
  
        final String line = lastLineRead.trim();
        final String key = parseKey(line);
        if (key != null)
        {
          final String b = readValue(reader, parseValue(line));
          if ("module".equals(key))
          {
            mi.setModuleClass(b);
          }
          else if ("version.major".equals(key))
          {
            mi.setMajorVersion(b);
          }
          else if ("version.minor".equals(key))
          {
            mi.setMinorVersion(b);
          }
          else if ("version.patchlevel".equals(key))
          {
            mi.setPatchLevel(b);
          }
        }
      }
      return mi;
    }
  
    /**
     * Returns the name of this module.
     *
     * @see Module#getName()
     *
     * @return the module name
     */
    public String getName()
    {
      return this.name;
    }
  
    /**
     * Defines the name of the module.
     *
     * @param name the module name.
     */
    protected void setName(final String name)
    {
      this.name = name;
    }
  
    /**
     * Returns the module description.
     * @see Module#getDescription()
     *
     * @return the description of the module.
     */
    public String getDescription()
    {
      return this.description;
    }
  
    /**
     * Defines the description of the module.
     *
     * @param description the module's desciption.
     */
    protected void setDescription(final String description)
    {
      this.description = description;
    }
  
    /**
     * Returns the producer of the module.
     *
     * @see Module#getProducer()
     *
     * @return the producer.
     */
    public String getProducer()
    {
      return this.producer;
    }
  
    /**
     * Defines the producer of the module.
     *
     * @param producer the producer.
     */
    protected void setProducer(final String producer)
    {
      this.producer = producer;
    }
  
    /**
     * Returns a copy of the required modules array. This array contains all
     * description of the modules that need to be present to make this module work.
     * @see Module#getRequiredModules()
     *
     * @return an array of all required modules.
     */
    public ModuleInfo[] getRequiredModules()
    {
      final ModuleInfo[] retval = new ModuleInfo[this.requiredModules.length];
      System.arraycopy(this.requiredModules, 0, retval, 0, this.requiredModules.length);
      return retval;
    }
  
    /**
     * Returns a copy of the required modules array. This array contains all
     * description of the optional modules that may improve the modules functonality.
     * @see Module#getRequiredModules()
     *
     * @return an array of all required modules.
     */
    public ModuleInfo[] getOptionalModules()
    {
      final ModuleInfo[] retval = new ModuleInfo[this.optionalModules.length];
      System.arraycopy(this.optionalModules, 0, retval, 0, this.optionalModules.length);
      return retval;
    }
  
    /**
     * Defines the required module descriptions for this module.
     *
     * @param requiredModules the required modules.
     */
    protected void setRequiredModules(final ModuleInfo[] requiredModules)
    {
      this.requiredModules = new ModuleInfo[requiredModules.length];
      System.arraycopy(requiredModules, 0, this.requiredModules, 0, requiredModules.length);
    }
  
    /**
     * Defines the optional module descriptions for this module.
     *
     * @param optionalModules the optional modules.
     */
    public void setOptionalModules(final ModuleInfo[] optionalModules)
    {
      this.optionalModules = new ModuleInfo[optionalModules.length];
      System.arraycopy(optionalModules, 0, this.optionalModules, 0, optionalModules.length);
    }
  
    /**
     * Returns a string representation of this module.
     * @see java.lang.Object#toString()
     *
     * @return the string representation of this module for debugging purposes.
     */
    public String toString()
    {
      final StringBuffer buffer = new StringBuffer();
      buffer.append("Module : ");
      buffer.append(getName());
      buffer.append("\n");
      buffer.append("ModuleClass : ");
      buffer.append(getModuleClass());
      buffer.append("\n");
      buffer.append("Version: ");
      buffer.append(getMajorVersion());
      buffer.append(".");
      buffer.append(getMinorVersion());
      buffer.append(".");
      buffer.append(getPatchLevel());
      buffer.append("\n");
      buffer.append("Producer: ");
      buffer.append(getProducer());
      buffer.append("\n");
      buffer.append("Description: ");
      buffer.append(getDescription());
      buffer.append("\n");
      return buffer.toString();
    }
  
    /**
     * Tries to load a class to indirectly check for the existence
     * of a certain library.
     *
     * @param name the name of the library class.
     * @return true, if the class could be loaded, false otherwise.
     * @deprecated use the method that passes in a context-class.
     */
    protected static boolean isClassLoadable(final String name)
    {
      try
      {
        final ClassLoader loader = ObjectUtilities.getClassLoader(AbstractModule.class);
        if (loader == null)
        {
          // this should not happen .. If it happens, it measn we dont even have a system-classloader.
          return false;
        }
        loader.loadClass(name);
        return true;
      }
      catch (Exception e)
      {
        return false;
      }
    }
  
    /**
     * Tries to load a class to indirectly check for the existence
     * of a certain library.
     *
     * @param name the name of the library class.
     * @param context the context class to get a classloader from.
     * @return true, if the class could be loaded, false otherwise.
     */
    protected static boolean isClassLoadable(final String name, final Class context)
    {
      try
      {
        ObjectUtilities.getClassLoader(context).loadClass(name);
        return true;
      }
      catch (Exception e)
      {
        return false;
      }
    }
  
    /**
     * Configures the module by loading the configuration properties and
     * adding them to the package configuration.
     *
     * @param subSystem  the subsystem.
     */
    public void configure(final SubSystem subSystem)
    {
      final InputStream in = ObjectUtilities.getResourceRelativeAsStream
              ("configuration.properties", getClass());
      if (in == null)
      {
        return;
      }
      try
      {
        subSystem.getPackageManager().getPackageConfiguration().load(in);
      }
      finally
      {
        try
        {
          in.close();
        }
        catch (IOException e)
        {
          // can be ignored ...
        }
      }
    }
  
    /**
     * Tries to load an module initializer and uses this initializer to initialize
     * the module.
     *
     * @param classname the class name of the initializer.
     * @throws ModuleInitializeException if an error occures
     * @deprecated Use the method that provides a class-context instead.
     */
    protected void performExternalInitialize(final String classname)
        throws ModuleInitializeException
    {
      try
      {
        final ModuleInitializer mi =
            (ModuleInitializer) ObjectUtilities.loadAndInstantiate(classname, AbstractModule.class, ModuleInitializer.class);
        if (mi == null)
        {
          throw new ModuleInitializeException("Failed to load specified initializer class.");
        }
        mi.performInit();
      }
      catch (ModuleInitializeException mie)
      {
        throw mie;
      }
      catch (Exception e)
      {
        throw new ModuleInitializeException("Failed to load specified initializer class.", e);
      }
    }
  
    /**
     * ???.
     *
     * @param classname ?
     * @param context ?
     * @throws ModuleInitializeException
     */
    protected void performExternalInitialize(final String classname, final Class context)
        throws ModuleInitializeException
    {
      try
      {
        final ModuleInitializer mi =
            (ModuleInitializer) ObjectUtilities.loadAndInstantiate(classname, context, ModuleInitializer.class);
        if (mi == null)
        {
          throw new ModuleInitializeException("Failed to load specified initializer class.");
        }
        mi.performInit();
      }
      catch (ModuleInitializeException mie)
      {
        throw mie;
      }
      catch (Exception e)
      {
        throw new ModuleInitializeException("Failed to load specified initializer class.", e);
      }
    }
  
    /**
     * Returns the modules subsystem. If this module is not part of an subsystem
     * then return the modules name, but never null.
     *
     * @return the name of the subsystem.
     */
    public String getSubSystem()
    {
      if (this.subsystem == null)
      {
        return getName();
      }
      return this.subsystem;
    }
  
    /**
     * Defines the subsystem name for this module.
     *
     * @param name the new name of the subsystem.
     */
    protected void setSubSystem (final String name)
    {
      this.subsystem = name;
    }
  }
  