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