1 /* ========================================================================
2  * JCommon : a free general purpose class library for the Java(tm) platform
3  * ========================================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jcommon/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * -------------------
28  * AbstractModule.java
29  * -------------------
30  * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
31  *
32  * Original Author:  Thomas Morgner;
33  * Contributor(s):   David Gilbert (for Object Refinery Limited);
34  *
35  * $Id: AbstractModule.java,v 1.7 2008/09/10 09:16:54 mungady Exp $
36  *
37  * Changes
38  * -------
39  * 05-Jul-2003 : Initial version
40  * 07-Jun-2004 : Added JCommon header (DG);
41  *
42  */
43 
44 package org.jfree.base.modules;
45 
46 import java.io.BufferedReader;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.util.ArrayList;
51 
52 import org.jfree.util.ObjectUtilities;
53 
54 
55 /**
56  * The abstract module provides a default implementation of the module interface.
57  * <p>
58  * The module can be specified in an external property file. The file name of this
59  * specification defaults to "module.properties". This file is no real property file,
60  * it follows a more complex rule set.</p>
61  * <p>
62  * Lines starting with '#' are considered comments.
63  * Section headers start at the beginning of the line, section properties
64  * are indented with at least one whitespace.</p>
65  * <p>
66  * The first section is always the module info and contains the basic module
67  * properties like name, version and a short description.</p>
68  *
69  * <pre>
70  * module-info:
71  *   name: xls-export-gui
72  *   producer: The JFreeReport project - www.jfree.org/jfreereport
73  *   description: A dialog component for the Excel table export.
74  *   version.major: 0
75  *   version.minor: 84
76  *   version.patchlevel: 0
77  * </pre>
78  * The properties name, producer and description are simple strings. They may
79  * span multiple lines, but may not contain a colon (':').
80  * The version properties are integer values.
81  * <p>
82  * This section may be followed by one or more "depends" sections. These
83  * sections describe the base modules that are required to be active to make this
84  * module work. The package manager will enforce this policy and will deactivate this
85  * module if one of the base modules is missing.</p>
86  *
87  * <pre>
88  * depends:
89  *   module: org.jfree.report.modules.output.table.xls.XLSTableModule
90  *   version.major: 0
91  *   version.minor: 84
92  * </pre>
93  * <p>
94  * The property module references to the module implementation of the module package.
95  *
96  * @author Thomas Morgner
97  */
98 public abstract class AbstractModule extends DefaultModuleInfo implements Module
99 {
100   /**
101    * The reader helper provides a pushback interface for the reader to read and
102    * buffer  complete lines.
103    * @author Thomas Morgner
104    */
105   private static class ReaderHelper
106   {
107     /** The line buffer containing the last line read. */
108     private String buffer;
109     /** The reader from which to read the text. */
110     private final BufferedReader reader;
111 
112     /**
113      * Creates a new reader helper for the given buffered reader.
114      *
115      * @param reader the buffered reader that is the source of the text.
116      */
ReaderHelper(final BufferedReader reader)117     protected ReaderHelper(final BufferedReader reader)
118     {
119       this.reader = reader;
120     }
121 
122     /**
123      * Checks, whether the reader contains a next line. Returns false if the end
124      * of the stream has been reached.
125      *
126      * @return true, if there is a next line to read, false otherwise.
127      * @throws IOException if an error occures.
128      */
hasNext()129     public boolean hasNext() throws IOException
130     {
131       if (this.buffer == null)
132       {
133         this.buffer = readLine();
134       }
135       return this.buffer != null;
136     }
137 
138     /**
139      * Returns the next line.
140      *
141      * @return the next line.
142      */
next()143     public String next()
144     {
145       final String line = this.buffer;
146       this.buffer = null;
147       return line;
148     }
149 
150     /**
151      * Pushes the given line back into the buffer. Only one line can be contained in
152      * the buffer at one time.
153      *
154      * @param line the line that should be pushed back into the buffer.
155      */
pushBack(final String line)156     public void pushBack(final String line)
157     {
158       this.buffer = line;
159     }
160 
161     /**
162      * Reads the next line skipping all comment lines.
163      *
164      * @return the next line, or null if no line can be read.
165      * @throws IOException if an IO error occures.
166      */
readLine()167     protected String readLine() throws IOException
168     {
169       String line = this.reader.readLine();
170       while (line != null && (line.length() == 0 || line.startsWith("#")))
171       {
172         // empty line or comment is ignored
173         line = this.reader.readLine();
174       }
175       return line;
176     }
177 
178     /**
179      * Closes the reader.
180      *
181      * @throws IOException if an IOError occurs.
182      */
close()183     public void close() throws IOException
184     {
185       this.reader.close();
186     }
187   }
188 
189   /** The list of required modules. */
190   private ModuleInfo[] requiredModules;
191   /** The list of optional modules. */
192   private ModuleInfo[] optionalModules;
193 
194   /** The name of the module. */
195   private String name;
196   /** A short description of the module. */
197   private String description;
198   /** The name of the module producer. */
199   private String producer;
200   /** The modules subsystem. */
201   private String subsystem;
202 
203   /**
204    * Default Constructor.
205    */
AbstractModule()206   public AbstractModule()
207   {
208     setModuleClass(this.getClass().getName());
209   }
210 
211   /**
212    * Loads the default module description from the file "module.properties". This file
213    * must be in the same package as the implementing class.
214    *
215    * @throws ModuleInitializeException if an error occurs.
216    */
loadModuleInfo()217   protected void loadModuleInfo() throws ModuleInitializeException
218   {
219     final InputStream in = ObjectUtilities.getResourceRelativeAsStream
220             ("module.properties", getClass());
221     if (in == null)
222     {
223       throw new ModuleInitializeException
224           ("File 'module.properties' not found in module package.");
225     }
226 
227     loadModuleInfo(in);
228   }
229 
230   /**
231    * Loads the module descriptiong from the given input stream. The module description
232    * must conform to the rules define in the class description. The file must be encoded
233    * with "ISO-8859-1" (like property files).
234    *
235    * @param in the input stream from where to read the file
236    * @throws ModuleInitializeException if an error occurs.
237    */
loadModuleInfo(final InputStream in)238   protected void loadModuleInfo(final InputStream in) throws ModuleInitializeException
239   {
240     if (in == null)
241     {
242       throw new NullPointerException
243           ("Given InputStream is null.");
244     }
245 
246     try
247     {
248       final ArrayList optionalModules = new ArrayList();
249       final ArrayList dependendModules = new ArrayList();
250       final ReaderHelper rh = new ReaderHelper(new BufferedReader
251           (new InputStreamReader(in, "ISO-8859-1")));
252       try
253       {
254         while (rh.hasNext())
255         {
256           final String lastLineRead = rh.next();
257           if (lastLineRead.startsWith("module-info:"))
258           {
259             readModuleInfo(rh);
260           }
261           else if (lastLineRead.startsWith("depends:"))
262           {
263             dependendModules.add(readExternalModule(rh));
264           }
265           else if (lastLineRead.startsWith("optional:"))
266           {
267             optionalModules.add(readExternalModule(rh));
268           }
269           else
270           {
271             // we dont understand the current line, so we skip it ...
272             // should we throw a parse exception instead?
273           }
274         }
275       }
276       finally
277       {
278         rh.close();
279       }
280 
281       this.optionalModules = (ModuleInfo[])
282           optionalModules.toArray(new ModuleInfo[optionalModules.size()]);
283 
284       this.requiredModules = (ModuleInfo[])
285           dependendModules.toArray(new ModuleInfo[dependendModules.size()]);
286     }
287     catch (IOException ioe)
288     {
289       throw new ModuleInitializeException("Failed to load properties", ioe);
290     }
291   }
292 
293   /**
294    * Reads a multiline value the stream. This will read the stream until
295    * a new key is found or the end of the file is reached.
296    *
297    * @param reader the reader from where to read.
298    * @param firstLine the first line (which was read elsewhere).
299    * @return the complete value, never null
300    * @throws IOException if an IO error occurs.
301    */
readValue(final ReaderHelper reader, String firstLine)302   private String readValue(final ReaderHelper reader, String firstLine) throws IOException
303   {
304     final StringBuffer b = new StringBuffer(firstLine.trim());
305     boolean newLine = true;
306     while (isNextLineValueLine(reader))
307     {
308       firstLine = reader.next();
309       final String trimedLine = firstLine.trim();
310       if (trimedLine.length() == 0 && (newLine == false))
311       {
312         b.append ("\n");
313         newLine = true;
314       }
315       else
316       {
317         if (newLine == false)
318         {
319           b.append(" ");
320         }
321         b.append(parseValue(trimedLine));
322         newLine = false;
323       }
324     }
325     return b.toString();
326   }
327 
328   /**
329    * Checks, whether the next line in the reader is a value line.
330    *
331    * @param reader from where to read the lines.
332    * @return true, if the next line is a value line, false otherwise.
333    * @throws IOException if an IO error occurs.
334    */
isNextLineValueLine(final ReaderHelper reader)335   private boolean isNextLineValueLine (final ReaderHelper reader) throws IOException
336   {
337     if (reader.hasNext() == false)
338     {
339       return false;
340     }
341     final String firstLine = reader.next();
342     if (firstLine == null)
343     {
344       return false;
345     }
346     if (parseKey(firstLine) != null)
347     {
348       reader.pushBack(firstLine);
349       return false;
350     }
351     reader.pushBack(firstLine);
352     return true;
353   }
354 
355   /**
356    * Reads the module definition header. This header contains information about
357    * the module itself.
358    *
359    * @param reader the reader from where to read the content.
360    * @throws IOException if an error occures
361    */
readModuleInfo(final ReaderHelper reader)362   private void readModuleInfo(final ReaderHelper reader) throws IOException
363   {
364     while (reader.hasNext())
365     {
366       final String lastLineRead = reader.next();
367 
368       if (Character.isWhitespace(lastLineRead.charAt(0)) == false)
369       {
370         // break if the current character is no whitespace ...
371         reader.pushBack(lastLineRead);
372         return;
373       }
374 
375       final String line = lastLineRead.trim();
376       final String key = parseKey(line);
377       if (key != null)
378       {
379         // parse error: Non data line does not contain a colon
380         final String b = readValue(reader, parseValue(line.trim()));
381 
382         if ("name".equals(key))
383         {
384           setName(b);
385         }
386         else if ("producer".equals(key))
387         {
388           setProducer(b);
389         }
390         else if ("description".equals(key))
391         {
392           setDescription(b);
393         }
394         else if ("subsystem".equals(key))
395         {
396           setSubSystem(b);
397         }
398         else if ("version.major".equals(key))
399         {
400           setMajorVersion(b);
401         }
402         else if ("version.minor".equals(key))
403         {
404           setMinorVersion(b);
405         }
406         else if ("version.patchlevel".equals(key))
407         {
408           setPatchLevel(b);
409         }
410       }
411     }
412   }
413 
414   /**
415    * Parses an string to find the key section of the line. This section ends with
416    * an colon.
417    *
418    * @param line the line which to parse
419    * @return the key or null if no key is found.
420    */
parseKey(final String line)421   private String parseKey(final String line)
422   {
423     final int idx = line.indexOf(':');
424     if (idx == -1)
425     {
426       return null;
427     }
428     return line.substring(0, idx);
429   }
430 
431   /**
432    * Parses the value section of the given line.
433    *
434    * @param line the line that should be parsed
435    * @return the value, never null
436    */
parseValue(final String line)437   private String parseValue(final String line)
438   {
439     final int idx = line.indexOf(':');
440     if (idx == -1)
441     {
442       return line;
443     }
444     if ((idx + 1) == line.length())
445     {
446       return "";
447     }
448     return line.substring(idx + 1);
449   }
450 
451   /**
452    * Reads an external module description. This describes either an optional or
453    * a required module.
454    *
455    * @param reader the reader from where to read the module
456    * @return the read module, never null
457    * @throws IOException if an error occures.
458    */
readExternalModule(final ReaderHelper reader)459   private DefaultModuleInfo readExternalModule(final ReaderHelper reader)
460       throws IOException
461   {
462     final DefaultModuleInfo mi = new DefaultModuleInfo();
463 
464     while (reader.hasNext())
465     {
466       final String lastLineRead = reader.next();
467 
468       if (Character.isWhitespace(lastLineRead.charAt(0)) == false)
469       {
470         // break if the current character is no whitespace ...
471         reader.pushBack(lastLineRead);
472         return mi;
473       }
474 
475       final String line = lastLineRead.trim();
476       final String key = parseKey(line);
477       if (key != null)
478       {
479         final String b = readValue(reader, parseValue(line));
480         if ("module".equals(key))
481         {
482           mi.setModuleClass(b);
483         }
484         else if ("version.major".equals(key))
485         {
486           mi.setMajorVersion(b);
487         }
488         else if ("version.minor".equals(key))
489         {
490           mi.setMinorVersion(b);
491         }
492         else if ("version.patchlevel".equals(key))
493         {
494           mi.setPatchLevel(b);
495         }
496       }
497     }
498     return mi;
499   }
500 
501   /**
502    * Returns the name of this module.
503    *
504    * @see Module#getName()
505    *
506    * @return the module name
507    */
getName()508   public String getName()
509   {
510     return this.name;
511   }
512 
513   /**
514    * Defines the name of the module.
515    *
516    * @param name the module name.
517    */
setName(final String name)518   protected void setName(final String name)
519   {
520     this.name = name;
521   }
522 
523   /**
524    * Returns the module description.
525    * @see Module#getDescription()
526    *
527    * @return the description of the module.
528    */
getDescription()529   public String getDescription()
530   {
531     return this.description;
532   }
533 
534   /**
535    * Defines the description of the module.
536    *
537    * @param description the module's desciption.
538    */
setDescription(final String description)539   protected void setDescription(final String description)
540   {
541     this.description = description;
542   }
543 
544   /**
545    * Returns the producer of the module.
546    *
547    * @see Module#getProducer()
548    *
549    * @return the producer.
550    */
getProducer()551   public String getProducer()
552   {
553     return this.producer;
554   }
555 
556   /**
557    * Defines the producer of the module.
558    *
559    * @param producer the producer.
560    */
setProducer(final String producer)561   protected void setProducer(final String producer)
562   {
563     this.producer = producer;
564   }
565 
566   /**
567    * Returns a copy of the required modules array. This array contains all
568    * description of the modules that need to be present to make this module work.
569    * @see Module#getRequiredModules()
570    *
571    * @return an array of all required modules.
572    */
getRequiredModules()573   public ModuleInfo[] getRequiredModules()
574   {
575     final ModuleInfo[] retval = new ModuleInfo[this.requiredModules.length];
576     System.arraycopy(this.requiredModules, 0, retval, 0, this.requiredModules.length);
577     return retval;
578   }
579 
580   /**
581    * Returns a copy of the required modules array. This array contains all
582    * description of the optional modules that may improve the modules functonality.
583    * @see Module#getRequiredModules()
584    *
585    * @return an array of all required modules.
586    */
getOptionalModules()587   public ModuleInfo[] getOptionalModules()
588   {
589     final ModuleInfo[] retval = new ModuleInfo[this.optionalModules.length];
590     System.arraycopy(this.optionalModules, 0, retval, 0, this.optionalModules.length);
591     return retval;
592   }
593 
594   /**
595    * Defines the required module descriptions for this module.
596    *
597    * @param requiredModules the required modules.
598    */
setRequiredModules(final ModuleInfo[] requiredModules)599   protected void setRequiredModules(final ModuleInfo[] requiredModules)
600   {
601     this.requiredModules = new ModuleInfo[requiredModules.length];
602     System.arraycopy(requiredModules, 0, this.requiredModules, 0, requiredModules.length);
603   }
604 
605   /**
606    * Defines the optional module descriptions for this module.
607    *
608    * @param optionalModules the optional modules.
609    */
setOptionalModules(final ModuleInfo[] optionalModules)610   public void setOptionalModules(final ModuleInfo[] optionalModules)
611   {
612     this.optionalModules = new ModuleInfo[optionalModules.length];
613     System.arraycopy(optionalModules, 0, this.optionalModules, 0, optionalModules.length);
614   }
615 
616   /**
617    * Returns a string representation of this module.
618    * @see java.lang.Object#toString()
619    *
620    * @return the string representation of this module for debugging purposes.
621    */
toString()622   public String toString()
623   {
624     final StringBuffer buffer = new StringBuffer();
625     buffer.append("Module : ");
626     buffer.append(getName());
627     buffer.append("\n");
628     buffer.append("ModuleClass : ");
629     buffer.append(getModuleClass());
630     buffer.append("\n");
631     buffer.append("Version: ");
632     buffer.append(getMajorVersion());
633     buffer.append(".");
634     buffer.append(getMinorVersion());
635     buffer.append(".");
636     buffer.append(getPatchLevel());
637     buffer.append("\n");
638     buffer.append("Producer: ");
639     buffer.append(getProducer());
640     buffer.append("\n");
641     buffer.append("Description: ");
642     buffer.append(getDescription());
643     buffer.append("\n");
644     return buffer.toString();
645   }
646 
647   /**
648    * Tries to load a class to indirectly check for the existence
649    * of a certain library.
650    *
651    * @param name the name of the library class.
652    * @return true, if the class could be loaded, false otherwise.
653    * @deprecated use the method that passes in a context-class.
654    */
isClassLoadable(final String name)655   protected static boolean isClassLoadable(final String name)
656   {
657     try
658     {
659       final ClassLoader loader = ObjectUtilities.getClassLoader(AbstractModule.class);
660       if (loader == null)
661       {
662         // this should not happen .. If it happens, it measn we dont even have a system-classloader.
663         return false;
664       }
665       loader.loadClass(name);
666       return true;
667     }
668     catch (Exception e)
669     {
670       return false;
671     }
672   }
673 
674   /**
675    * Tries to load a class to indirectly check for the existence
676    * of a certain library.
677    *
678    * @param name the name of the library class.
679    * @param context the context class to get a classloader from.
680    * @return true, if the class could be loaded, false otherwise.
681    */
isClassLoadable(final String name, final Class context)682   protected static boolean isClassLoadable(final String name, final Class context)
683   {
684     try
685     {
686       ObjectUtilities.getClassLoader(context).loadClass(name);
687       return true;
688     }
689     catch (Exception e)
690     {
691       return false;
692     }
693   }
694 
695   /**
696    * Configures the module by loading the configuration properties and
697    * adding them to the package configuration.
698    *
699    * @param subSystem  the subsystem.
700    */
configure(final SubSystem subSystem)701   public void configure(final SubSystem subSystem)
702   {
703     final InputStream in = ObjectUtilities.getResourceRelativeAsStream
704             ("configuration.properties", getClass());
705     if (in == null)
706     {
707       return;
708     }
709     try
710     {
711       subSystem.getPackageManager().getPackageConfiguration().load(in);
712     }
713     finally
714     {
715       try
716       {
717         in.close();
718       }
719       catch (IOException e)
720       {
721         // can be ignored ...
722       }
723     }
724   }
725 
726   /**
727    * Tries to load an module initializer and uses this initializer to initialize
728    * the module.
729    *
730    * @param classname the class name of the initializer.
731    * @throws ModuleInitializeException if an error occures
732    * @deprecated Use the method that provides a class-context instead.
733    */
performExternalInitialize(final String classname)734   protected void performExternalInitialize(final String classname)
735       throws ModuleInitializeException
736   {
737     try
738     {
739       final ModuleInitializer mi =
740           (ModuleInitializer) ObjectUtilities.loadAndInstantiate(classname, AbstractModule.class, ModuleInitializer.class);
741       if (mi == null)
742       {
743         throw new ModuleInitializeException("Failed to load specified initializer class.");
744       }
745       mi.performInit();
746     }
747     catch (ModuleInitializeException mie)
748     {
749       throw mie;
750     }
751     catch (Exception e)
752     {
753       throw new ModuleInitializeException("Failed to load specified initializer class.", e);
754     }
755   }
756 
757   /**
758    * ???.
759    *
760    * @param classname ?
761    * @param context ?
762    * @throws ModuleInitializeException if there is an initialisation error.
763    */
performExternalInitialize(final String classname, final Class context)764   protected void performExternalInitialize(final String classname, final Class context)
765       throws ModuleInitializeException
766   {
767     try
768     {
769       final ModuleInitializer mi =
770           (ModuleInitializer) ObjectUtilities.loadAndInstantiate(classname, context, ModuleInitializer.class);
771       if (mi == null)
772       {
773         throw new ModuleInitializeException("Failed to load specified initializer class.");
774       }
775       mi.performInit();
776     }
777     catch (ModuleInitializeException mie)
778     {
779       throw mie;
780     }
781     catch (Exception e)
782     {
783       throw new ModuleInitializeException("Failed to load specified initializer class.", e);
784     }
785   }
786 
787   /**
788    * Returns the modules subsystem. If this module is not part of an subsystem
789    * then return the modules name, but never null.
790    *
791    * @return the name of the subsystem.
792    */
getSubSystem()793   public String getSubSystem()
794   {
795     if (this.subsystem == null)
796     {
797       return getName();
798     }
799     return this.subsystem;
800   }
801 
802   /**
803    * Defines the subsystem name for this module.
804    *
805    * @param name the new name of the subsystem.
806    */
setSubSystem(final String name)807   protected void setSubSystem (final String name)
808   {
809     this.subsystem = name;
810   }
811 }
812