001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * -------------------
028: * AbstractModule.java
029: * -------------------
030: * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
031: *
032: * Original Author: Thomas Morgner;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: AbstractModule.java,v 1.3 2005/10/18 13:14:50 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 05-Jul-2003 : Initial version
040: * 07-Jun-2004 : Added JCommon header (DG);
041: *
042: */
043:
044: package org.jfree.base.modules;
045:
046: import java.io.BufferedReader;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.InputStreamReader;
050: import java.util.ArrayList;
051:
052: import org.jfree.util.ObjectUtilities;
053:
054: /**
055: * The abstract module provides a default implementation of the module interface.
056: * <p>
057: * The module can be specified in an external property file. The file name of this
058: * specification defaults to "module.properties". This file is no real property file,
059: * it follows a more complex rule set.
060: * <p>
061: * Lines starting with '#' are considered comments.
062: * Section headers start at the beginning of the line, section properties
063: * are indented with at least one whitespace.
064: * <p>
065: * The first section is always the module info and contains the basic module
066: * properties like name, version and a short description.
067: * <p>
068: * <pre>
069: * module-info:
070: * name: xls-export-gui
071: * producer: The JFreeReport project - www.jfree.org/jfreereport
072: * description: A dialog component for the Excel table export.
073: * version.major: 0
074: * version.minor: 84
075: * version.patchlevel: 0
076: * </pre>
077: * The properties name, producer and description are simple strings. They may
078: * span multiple lines, but may not contain a colon (':').
079: * The version properties are integer values.
080: * <p>
081: * This section may be followed by one or more "depends" sections. These
082: * sections describe the base modules that are required to be active to make this
083: * module work. The package manager will enforce this policy and will deactivate this
084: * module if one of the base modules is missing.
085: * <p>
086: * <pre>
087: * depends:
088: * module: org.jfree.report.modules.output.table.xls.XLSTableModule
089: * version.major: 0
090: * version.minor: 84
091: * </pre>
092: * <p>
093: * The property module references to the module implementation of the module package.
094: *
095: * @author Thomas Morgner
096: */
097: public abstract class AbstractModule extends DefaultModuleInfo
098: implements Module {
099: /**
100: * The reader helper provides a pushback interface for the reader to read and
101: * buffer complete lines.
102: * @author Thomas Morgner
103: */
104: private static class ReaderHelper {
105: /** The line buffer containing the last line read. */
106: private String buffer;
107: /** The reader from which to read the text. */
108: private final BufferedReader reader;
109:
110: /**
111: * Creates a new reader helper for the given buffered reader.
112: *
113: * @param reader the buffered reader that is the source of the text.
114: */
115: public ReaderHelper(final BufferedReader reader) {
116: this .reader = reader;
117: }
118:
119: /**
120: * Checks, whether the reader contains a next line. Returns false if the end
121: * of the stream has been reached.
122: *
123: * @return true, if there is a next line to read, false otherwise.
124: * @throws IOException if an error occures.
125: */
126: public boolean hasNext() throws IOException {
127: if (this .buffer == null) {
128: this .buffer = readLine();
129: }
130: return this .buffer != null;
131: }
132:
133: /**
134: * Returns the next line.
135: *
136: * @return the next line.
137: */
138: public String next() {
139: final String line = this .buffer;
140: this .buffer = null;
141: return line;
142: }
143:
144: /**
145: * Pushes the given line back into the buffer. Only one line can be contained in
146: * the buffer at one time.
147: *
148: * @param line the line that should be pushed back into the buffer.
149: */
150: public void pushBack(final String line) {
151: this .buffer = line;
152: }
153:
154: /**
155: * Reads the next line skipping all comment lines.
156: *
157: * @return the next line, or null if no line can be read.
158: * @throws IOException if an IO error occures.
159: */
160: protected String readLine() throws IOException {
161: String line = this .reader.readLine();
162: while (line != null
163: && (line.length() == 0 || line.startsWith("#"))) {
164: // empty line or comment is ignored
165: line = this .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: this .reader.close();
177: }
178: }
179:
180: /** The list of required modules. */
181: private ModuleInfo[] requiredModules;
182: /** The list of optional modules. */
183: private ModuleInfo[] optionalModules;
184:
185: /** The name of the module. */
186: private String name;
187: /** A short description of the module. */
188: private String description;
189: /** The name of the module producer. */
190: private String producer;
191: /** The modules subsystem. */
192: private String subsystem;
193:
194: /**
195: * Default Constructor.
196: */
197: public AbstractModule() {
198: setModuleClass(this .getClass().getName());
199: }
200:
201: /**
202: * Loads the default module description from the file "module.properties". This file
203: * must be in the same package as the implementing class.
204: *
205: * @throws ModuleInitializeException if an error occurs.
206: */
207: protected void loadModuleInfo() throws ModuleInitializeException {
208: final InputStream in = ObjectUtilities
209: .getResourceRelativeAsStream("module.properties",
210: getClass());
211: if (in == null) {
212: throw new ModuleInitializeException(
213: "File 'module.properties' not found in module package.");
214: }
215: loadModuleInfo(in);
216: }
217:
218: /**
219: * Loads the module descriptiong from the given input stream. The module description
220: * must conform to the rules define in the class description. The file must be encoded
221: * with "ISO-8859-1" (like property files).
222: *
223: * @param in the input stream from where to read the file
224: * @throws ModuleInitializeException if an error occurs.
225: */
226: protected void loadModuleInfo(final InputStream in)
227: throws ModuleInitializeException {
228: try {
229: if (in == null) {
230: throw new NullPointerException(
231: "Given InputStream is null.");
232: }
233: final ReaderHelper rh = new ReaderHelper(
234: new BufferedReader(new InputStreamReader(in,
235: "ISO-8859-1")));
236:
237: final ArrayList optionalModules = new ArrayList();
238: final ArrayList dependendModules = new ArrayList();
239:
240: while (rh.hasNext()) {
241: final String lastLineRead = rh.next();
242: if (lastLineRead.startsWith("module-info:")) {
243: readModuleInfo(rh);
244: } else if (lastLineRead.startsWith("depends:")) {
245: dependendModules.add(readExternalModule(rh));
246: } else if (lastLineRead.startsWith("optional:")) {
247: optionalModules.add(readExternalModule(rh));
248: } else {
249: // we dont understand the current line, so we skip it ...
250: // should we throw a parse exception instead?
251: }
252: }
253: rh.close();
254:
255: this .optionalModules = (ModuleInfo[]) optionalModules
256: .toArray(new ModuleInfo[optionalModules.size()]);
257:
258: this .requiredModules = (ModuleInfo[]) dependendModules
259: .toArray(new ModuleInfo[dependendModules.size()]);
260: } catch (IOException ioe) {
261: throw new ModuleInitializeException(
262: "Failed to load properties", ioe);
263: }
264: }
265:
266: /**
267: * Reads a multiline value the stream. This will read the stream until
268: * a new key is found or the end of the file is reached.
269: *
270: * @param reader the reader from where to read.
271: * @param firstLine the first line (which was read elsewhere).
272: * @return the complete value, never null
273: * @throws IOException if an IO error occurs.
274: */
275: private String readValue(final ReaderHelper reader, String firstLine)
276: throws IOException {
277: final StringBuffer b = new StringBuffer(firstLine.trim());
278: boolean newLine = true;
279: while (isNextLineValueLine(reader)) {
280: firstLine = reader.next();
281: final String trimedLine = firstLine.trim();
282: if (trimedLine.length() == 0 && (newLine == false)) {
283: b.append("\n");
284: newLine = true;
285: } else {
286: if (newLine == false) {
287: b.append(" ");
288: }
289: b.append(parseValue(trimedLine));
290: newLine = false;
291: }
292: }
293: return b.toString();
294: }
295:
296: /**
297: * Checks, whether the next line in the reader is a value line.
298: *
299: * @param reader from where to read the lines.
300: * @return true, if the next line is a value line, false otherwise.
301: * @throws IOException if an IO error occurs.
302: */
303: private boolean isNextLineValueLine(final ReaderHelper reader)
304: throws IOException {
305: if (reader.hasNext() == false) {
306: return false;
307: }
308: final String firstLine = reader.next();
309: if (firstLine == null) {
310: return false;
311: }
312: if (parseKey(firstLine) != null) {
313: reader.pushBack(firstLine);
314: return false;
315: }
316: reader.pushBack(firstLine);
317: return true;
318: }
319:
320: /**
321: * Reads the module definition header. This header contains information about
322: * the module itself.
323: *
324: * @param reader the reader from where to read the content.
325: * @throws IOException if an error occures
326: */
327: private void readModuleInfo(final ReaderHelper reader)
328: throws IOException {
329: while (reader.hasNext()) {
330: final String lastLineRead = reader.next();
331:
332: if (Character.isWhitespace(lastLineRead.charAt(0)) == false) {
333: // break if the current character is no whitespace ...
334: reader.pushBack(lastLineRead);
335: return;
336: }
337:
338: final String line = lastLineRead.trim();
339: final String key = parseKey(line);
340: if (key != null) {
341: // parse error: Non data line does not contain a colon
342: final String b = readValue(reader, parseValue(line
343: .trim()));
344:
345: if (key.equals("name")) {
346: setName(b);
347: } else if (key.equals("producer")) {
348: setProducer(b);
349: } else if (key.equals("description")) {
350: setDescription(b);
351: } else if (key.equals("subsystem")) {
352: setSubSystem(b);
353: } else if (key.equals("version.major")) {
354: setMajorVersion(b);
355: } else if (key.equals("version.minor")) {
356: setMinorVersion(b);
357: } else if (key.equals("version.patchlevel")) {
358: setPatchLevel(b);
359: }
360: }
361: }
362: }
363:
364: /**
365: * Parses an string to find the key section of the line. This section ends with
366: * an colon.
367: *
368: * @param line the line which to parse
369: * @return the key or null if no key is found.
370: */
371: private String parseKey(final String line) {
372: final int idx = line.indexOf(':');
373: if (idx == -1) {
374: return null;
375: }
376: return line.substring(0, idx);
377: }
378:
379: /**
380: * Parses the value section of the given line.
381: *
382: * @param line the line that should be parsed
383: * @return the value, never null
384: */
385: private String parseValue(final String line) {
386: final int idx = line.indexOf(':');
387: if (idx == -1) {
388: return line;
389: }
390: if ((idx + 1) == line.length()) {
391: return "";
392: }
393: return line.substring(idx + 1);
394: }
395:
396: /**
397: * Reads an external module description. This describes either an optional or
398: * a required module.
399: *
400: * @param reader the reader from where to read the module
401: * @return the read module, never null
402: * @throws IOException if an error occures.
403: */
404: private DefaultModuleInfo readExternalModule(
405: final ReaderHelper reader) throws IOException {
406: final DefaultModuleInfo mi = new DefaultModuleInfo();
407:
408: while (reader.hasNext()) {
409: final String lastLineRead = reader.next();
410:
411: if (Character.isWhitespace(lastLineRead.charAt(0)) == false) {
412: // break if the current character is no whitespace ...
413: reader.pushBack(lastLineRead);
414: return mi;
415: }
416:
417: final String line = lastLineRead.trim();
418: final String key = parseKey(line);
419: if (key != null) {
420: final String b = readValue(reader, parseValue(line));
421: if (key.equals("module")) {
422: mi.setModuleClass(b);
423: } else if (key.equals("version.major")) {
424: mi.setMajorVersion(b);
425: } else if (key.equals("version.minor")) {
426: mi.setMinorVersion(b);
427: } else if (key.equals("version.patchlevel")) {
428: mi.setPatchLevel(b);
429: }
430: }
431: }
432: return mi;
433: }
434:
435: /**
436: * Returns the name of this module.
437: *
438: * @see Module#getName()
439: *
440: * @return the module name
441: */
442: public String getName() {
443: return this .name;
444: }
445:
446: /**
447: * Defines the name of the module.
448: *
449: * @param name the module name.
450: */
451: protected void setName(final String name) {
452: this .name = name;
453: }
454:
455: /**
456: * Returns the module description.
457: * @see Module#getDescription()
458: *
459: * @return the description of the module.
460: */
461: public String getDescription() {
462: return this .description;
463: }
464:
465: /**
466: * Defines the description of the module.
467: *
468: * @param description the module's desciption.
469: */
470: protected void setDescription(final String description) {
471: this .description = description;
472: }
473:
474: /**
475: * Returns the producer of the module.
476: *
477: * @see Module#getProducer()
478: *
479: * @return the producer.
480: */
481: public String getProducer() {
482: return this .producer;
483: }
484:
485: /**
486: * Defines the producer of the module.
487: *
488: * @param producer the producer.
489: */
490: protected void setProducer(final String producer) {
491: this .producer = producer;
492: }
493:
494: /**
495: * Returns a copy of the required modules array. This array contains all
496: * description of the modules that need to be present to make this module work.
497: * @see Module#getRequiredModules()
498: *
499: * @return an array of all required modules.
500: */
501: public ModuleInfo[] getRequiredModules() {
502: final ModuleInfo[] retval = new ModuleInfo[this .requiredModules.length];
503: System.arraycopy(this .requiredModules, 0, retval, 0,
504: this .requiredModules.length);
505: return retval;
506: }
507:
508: /**
509: * Returns a copy of the required modules array. This array contains all
510: * description of the optional modules that may improve the modules functonality.
511: * @see Module#getRequiredModules()
512: *
513: * @return an array of all required modules.
514: */
515: public ModuleInfo[] getOptionalModules() {
516: final ModuleInfo[] retval = new ModuleInfo[this .optionalModules.length];
517: System.arraycopy(this .optionalModules, 0, retval, 0,
518: this .optionalModules.length);
519: return retval;
520: }
521:
522: /**
523: * Defines the required module descriptions for this module.
524: *
525: * @param requiredModules the required modules.
526: */
527: protected void setRequiredModules(final ModuleInfo[] requiredModules) {
528: this .requiredModules = new ModuleInfo[requiredModules.length];
529: System.arraycopy(requiredModules, 0, this .requiredModules, 0,
530: requiredModules.length);
531: }
532:
533: /**
534: * Defines the optional module descriptions for this module.
535: *
536: * @param optionalModules the optional modules.
537: */
538: public void setOptionalModules(final ModuleInfo[] optionalModules) {
539: this .optionalModules = new ModuleInfo[optionalModules.length];
540: System.arraycopy(optionalModules, 0, this .optionalModules, 0,
541: optionalModules.length);
542: }
543:
544: /**
545: * Returns a string representation of this module.
546: * @see java.lang.Object#toString()
547: *
548: * @return the string representation of this module for debugging purposes.
549: */
550: public String toString() {
551: final StringBuffer buffer = new StringBuffer();
552: buffer.append("Module : ");
553: buffer.append(getName());
554: buffer.append("\n");
555: buffer.append("ModuleClass : ");
556: buffer.append(getModuleClass());
557: buffer.append("\n");
558: buffer.append("Version: ");
559: buffer.append(getMajorVersion());
560: buffer.append(".");
561: buffer.append(getMinorVersion());
562: buffer.append(".");
563: buffer.append(getPatchLevel());
564: buffer.append("\n");
565: buffer.append("Producer: ");
566: buffer.append(getProducer());
567: buffer.append("\n");
568: buffer.append("Description: ");
569: buffer.append(getDescription());
570: buffer.append("\n");
571: return buffer.toString();
572: }
573:
574: /**
575: * Tries to load a class to indirectly check for the existence
576: * of a certain library.
577: *
578: * @param name the name of the library class.
579: * @return true, if the class could be loaded, false otherwise.
580: */
581: protected static boolean isClassLoadable(final String name) {
582: try {
583: Thread.currentThread().getContextClassLoader().loadClass(
584: name);
585: return true;
586: } catch (Exception e) {
587: return false;
588: }
589: }
590:
591: /**
592: * Configures the module by loading the configuration properties and
593: * adding them to the package configuration.
594: *
595: * @param subSystem the subsystem.
596: */
597: public void configure(final SubSystem subSystem) {
598: final InputStream in = ObjectUtilities
599: .getResourceRelativeAsStream(
600: "configuration.properties", getClass());
601: if (in == null) {
602: return;
603: }
604: subSystem.getPackageManager().getPackageConfiguration()
605: .load(in);
606: }
607:
608: /**
609: * Tries to load an module initializer and uses this initializer to initialize
610: * the module.
611: *
612: * @param classname the class name of the initializer.
613: * @throws ModuleInitializeException if an error occures
614: */
615: protected void performExternalInitialize(final String classname)
616: throws ModuleInitializeException {
617: try {
618: final Class c = Thread.currentThread()
619: .getContextClassLoader().loadClass(classname);
620: final ModuleInitializer mi = (ModuleInitializer) c
621: .newInstance();
622: mi.performInit();
623: } catch (ModuleInitializeException mie) {
624: throw mie;
625: } catch (Exception e) {
626: throw new ModuleInitializeException(
627: "Failed to load specified initializer class.", e);
628: }
629: }
630:
631: /**
632: * Returns the modules subsystem. If this module is not part of an subsystem
633: * then return the modules name, but never null.
634: *
635: * @return the name of the subsystem.
636: */
637: public String getSubSystem() {
638: if (this .subsystem == null) {
639: return getName();
640: }
641: return this .subsystem;
642: }
643:
644: /**
645: * Defines the subsystem name for this module.
646: *
647: * @param name the new name of the subsystem.
648: */
649: protected void setSubSystem(final String name) {
650: this.subsystem = name;
651: }
652: }
|