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