001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
009: *
010: * This library is free software; you can redistribute it and/or modify it under the terms
011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
012: * either version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: * See the GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License along with this
019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020: * Boston, MA 02111-1307, USA.
021: *
022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023: * in the United States and other countries.]
024: *
025: * ------------
026: * ConfigEditor.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.gui.config;
030:
031: import java.awt.BorderLayout;
032: import java.awt.FlowLayout;
033: import java.awt.GridLayout;
034: import java.awt.event.ActionEvent;
035: import java.awt.event.WindowAdapter;
036: import java.awt.event.WindowEvent;
037: import java.io.BufferedInputStream;
038: import java.io.BufferedOutputStream;
039: import java.io.File;
040: import java.io.FileInputStream;
041: import java.io.FileOutputStream;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.io.OutputStreamWriter;
045: import java.io.PrintWriter;
046: import java.util.ArrayList;
047: import java.util.Collections;
048: import java.util.Enumeration;
049: import java.util.Properties;
050: import javax.swing.Action;
051: import javax.swing.BorderFactory;
052: import javax.swing.JComponent;
053: import javax.swing.JFileChooser;
054: import javax.swing.JFrame;
055: import javax.swing.JLabel;
056: import javax.swing.JOptionPane;
057: import javax.swing.JPanel;
058: import javax.swing.JScrollPane;
059: import javax.swing.JSplitPane;
060: import javax.swing.JTree;
061: import javax.swing.UIManager;
062: import javax.swing.border.EmptyBorder;
063: import javax.swing.event.TreeSelectionEvent;
064: import javax.swing.event.TreeSelectionListener;
065: import javax.swing.tree.DefaultTreeSelectionModel;
066: import javax.swing.tree.TreePath;
067: import javax.swing.tree.TreeSelectionModel;
068:
069: import org.jfree.base.config.HierarchicalConfiguration;
070: import org.jfree.report.JFreeReportBoot;
071: import org.jfree.report.modules.gui.config.editor.ConfigEditorPanel;
072: import org.jfree.report.modules.gui.config.model.ConfigDescriptionEntry;
073: import org.jfree.report.modules.gui.config.model.ConfigTreeModel;
074: import org.jfree.report.modules.gui.config.model.ConfigTreeModelException;
075: import org.jfree.report.modules.gui.config.model.ConfigTreeModuleNode;
076: import org.jfree.report.util.i18n.Messages;
077: import org.jfree.ui.FilesystemFilter;
078: import org.jfree.ui.action.AbstractActionDowngrade;
079: import org.jfree.ui.action.ActionButton;
080: import org.jfree.util.LineBreakIterator;
081: import org.jfree.util.Log;
082: import org.jfree.util.ObjectUtilities;
083: import org.jfree.util.ResourceBundleSupport;
084: import org.jfree.util.StringUtils;
085:
086: /**
087: * The ConfigEditor can be used to edit the global jfreereport.properties files. These files provide global settings for
088: * all reports and contain the system level configuration of JFreeReport.
089: *
090: * @author Thomas Morgner
091: */
092: public class ConfigEditor extends JFrame {
093: /**
094: * An Action to handle close requests.
095: */
096: private class CloseAction extends AbstractActionDowngrade {
097: /**
098: * DefaultConstructor.
099: */
100: protected CloseAction() {
101: putValue(Action.NAME, getResources().getString(
102: "action.exit.name")); //$NON-NLS-1$
103: }
104:
105: /**
106: * Invoked when an action occurs. The action invokes System.exit(0).
107: *
108: * @param e the action event.
109: */
110: public void actionPerformed(final ActionEvent e) {
111: attempClose();
112: }
113: }
114:
115: /**
116: * An action to handle save requests.
117: */
118: private class SaveAction extends AbstractActionDowngrade {
119: /**
120: * DefaultConstructor.
121: */
122: protected SaveAction() {
123: putValue(Action.NAME, getResources().getString(
124: "action.save.name")); //$NON-NLS-1$
125: putValue(Action.SMALL_ICON, getResources().getIcon(
126: "action.save.small-icon")); //$NON-NLS-1$
127: }
128:
129: /**
130: * Saves the configuration.
131: *
132: * @param e the action event.
133: */
134: public void actionPerformed(final ActionEvent e) {
135: save();
136: }
137: }
138:
139: /**
140: * An action to handle load requests.
141: */
142: private class LoadAction extends AbstractActionDowngrade {
143: /**
144: * DefaultConstructor.
145: */
146: protected LoadAction() {
147: putValue(Action.NAME, getResources().getString(
148: "action.load.name")); //$NON-NLS-1$
149: putValue(Action.SMALL_ICON, getResources().getIcon(
150: "action.load.small-icon")); //$NON-NLS-1$
151: }
152:
153: /**
154: * Loads the configuration.
155: *
156: * @param e the action event.
157: */
158: public void actionPerformed(final ActionEvent e) {
159: load();
160: }
161: }
162:
163: /**
164: * An action to handle new requests, which reset the report configuration.
165: */
166: private class NewAction extends AbstractActionDowngrade {
167: /**
168: * DefaultConstructor.
169: */
170: protected NewAction() {
171: putValue(Action.NAME, getResources().getString(
172: "action.new.name")); //$NON-NLS-1$
173: putValue(Action.SMALL_ICON, getResources().getIcon(
174: "action.new.small-icon")); //$NON-NLS-1$
175: }
176:
177: /**
178: * Reset the configuration.
179: *
180: * @param e the action event.
181: */
182: public void actionPerformed(final ActionEvent e) {
183: reset();
184: }
185: }
186:
187: /**
188: * This class handles the tree selection events and activates the detail editors.
189: */
190: private class ModuleTreeSelectionHandler implements
191: TreeSelectionListener {
192: /**
193: * DefaultConstructor.
194: */
195: protected ModuleTreeSelectionHandler() {
196: }
197:
198: /**
199: * Called whenever the value of the selection changes.
200: *
201: * @param e the event that characterizes the change.
202: */
203: public void valueChanged(final TreeSelectionEvent e) {
204: final TreePath path = e.getPath();
205: final Object lastPathElement = path.getLastPathComponent();
206: if (lastPathElement instanceof ConfigTreeModuleNode) {
207: final ConfigTreeModuleNode node = (ConfigTreeModuleNode) lastPathElement;
208: final ConfigEditorPanel detailEditorPane = getDetailEditorPane();
209: detailEditorPane.store();
210: detailEditorPane.editModule(node.getModule(), node
211: .getConfiguration(), node.getAssignedKeys());
212: }
213: }
214: }
215:
216: private class CloseHandler extends WindowAdapter {
217:
218: public CloseHandler() {
219: }
220:
221: /**
222: * Invoked when a window is in the process of being closed. The close operation can be overridden at this point.
223: */
224: public void windowClosing(final WindowEvent e) {
225: attempClose();
226: }
227: }
228:
229: /**
230: * The name of the condigu description filename
231: */
232: public static final String CONFIG_DESCRIPTION_FILENAME = "config-description.xml"; //$NON-NLS-1$
233:
234: /**
235: * A constant defining that text should be escaped in a way which is suitable for property keys.
236: */
237: private static final int ESCAPE_KEY = 0;
238: /**
239: * A constant defining that text should be escaped in a way which is suitable for property values.
240: */
241: private static final int ESCAPE_VALUE = 1;
242: /**
243: * A constant defining that text should be escaped in a way which is suitable for property comments.
244: */
245: private static final int ESCAPE_COMMENT = 2;
246:
247: /**
248: * A label that serves as status bar.
249: */
250: private JLabel statusHolder;
251: /**
252: * The resource bundle instance of this dialog.
253: */
254: private final ResourceBundleSupport resources;
255:
256: /**
257: * The detail editor for the currently selected tree node.
258: */
259: private final ConfigEditorPanel detailEditorPane;
260: /**
261: * The tree model used to display the structure of the report configuration.
262: */
263: private ConfigTreeModel treeModel;
264:
265: /**
266: * The currently used report configuration.
267: */
268: private final HierarchicalConfiguration currentReportConfiguration;
269: /**
270: * The file chooser used to load and save the report configuration.
271: */
272: private JFileChooser fileChooser;
273:
274: /**
275: * Externalized string access
276: */
277: private Messages messages;
278: private static final String PROPERTIES_FILE_EXTENSION = ".properties";
279:
280: /**
281: * Constructs a new ConfigEditor.
282: *
283: * @throws ConfigTreeModelException if the tree model could not be built.
284: */
285: public ConfigEditor() throws ConfigTreeModelException {
286: resources = new ResourceBundleSupport(getLocale(),
287: ConfigGUIModule.BUNDLE_NAME);
288: messages = new Messages(getLocale(),
289: ConfigGUIModule.BUNDLE_NAME);
290: currentReportConfiguration = new HierarchicalConfiguration(
291: JFreeReportBoot.getInstance().getGlobalConfig());
292: detailEditorPane = new ConfigEditorPanel();
293:
294: setTitle(resources.getString("config-editor.title")); //$NON-NLS-1$
295:
296: final JSplitPane splitPane = new JSplitPane(
297: JSplitPane.HORIZONTAL_SPLIT, createEntryTree(),
298: detailEditorPane);
299:
300: final JPanel contentPane = new JPanel();
301: contentPane.setLayout(new BorderLayout());
302: contentPane.add(splitPane, BorderLayout.CENTER);
303: contentPane.add(createButtonPane(), BorderLayout.SOUTH);
304:
305: final JPanel cPaneStatus = new JPanel();
306: cPaneStatus.setLayout(new BorderLayout());
307: cPaneStatus.add(contentPane, BorderLayout.CENTER);
308: cPaneStatus.add(createStatusBar(), BorderLayout.SOUTH);
309:
310: setContentPane(cPaneStatus);
311:
312: addWindowListener(new CloseHandler());
313:
314: }
315:
316: /**
317: * Returns the resource bundle of this editor for translating strings.
318: *
319: * @return the resource bundle.
320: */
321: protected ResourceBundleSupport getResources() {
322: return resources;
323: }
324:
325: /**
326: * Creates the JTree for the report configuration.
327: *
328: * @return the tree component.
329: * @throws ConfigTreeModelException if the model could not be built.
330: */
331: private JComponent createEntryTree()
332: throws ConfigTreeModelException {
333: final InputStream in = ObjectUtilities
334: .getResourceRelativeAsStream(
335: CONFIG_DESCRIPTION_FILENAME, ConfigEditor.class);
336: if (in == null) {
337: throw new IllegalStateException(
338: messages
339: .getErrorString(
340: "ConfigEditor.ERROR_0002_MISSING_RESOURCE", CONFIG_DESCRIPTION_FILENAME)); //$NON-NLS-1$
341: }
342: try {
343: treeModel = new ConfigTreeModel(in);
344: treeModel.init(currentReportConfiguration);
345:
346: final TreeSelectionModel selectionModel = new DefaultTreeSelectionModel();
347: selectionModel
348: .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
349:
350: final JTree tree = new JTree(treeModel);
351: tree.setSelectionModel(selectionModel);
352: tree.setCellRenderer(new ConfigTreeRenderer());
353: tree.setRootVisible(false);
354: tree.setShowsRootHandles(true);
355: tree
356: .addTreeSelectionListener(new ModuleTreeSelectionHandler());
357:
358: return new JScrollPane(tree,
359: JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
360: JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
361: } finally {
362: try {
363: in.close();
364: } catch (IOException e) {
365: // can be ignored ..
366: }
367: }
368: }
369:
370: /**
371: * Creates the button pane to hold all control buttons.
372: *
373: * @return the created panel with all control buttons.
374: */
375: private JPanel createButtonPane() {
376: final Action closeAction = new CloseAction();
377: final Action saveAction = new SaveAction();
378: final Action loadAction = new LoadAction();
379: final Action newAction = new NewAction();
380:
381: final JPanel panel = new JPanel();
382: panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
383: panel.setBorder(new EmptyBorder(5, 5, 5, 5));
384:
385: final JPanel buttonHolder = new JPanel();
386: buttonHolder.setLayout(new GridLayout(1, 4));
387: buttonHolder.add(new ActionButton(newAction));
388: buttonHolder.add(new ActionButton(loadAction));
389: buttonHolder.add(new ActionButton(saveAction));
390: buttonHolder.add(new ActionButton(closeAction));
391:
392: panel.add(buttonHolder);
393: return panel;
394: }
395:
396: /**
397: * Creates the statusbar for this frame. Use setStatus() to display text on the status bar.
398: *
399: * @return the status bar.
400: */
401: protected JPanel createStatusBar() {
402: final JPanel statusPane = new JPanel();
403: statusPane.setLayout(new BorderLayout());
404: statusPane.setBorder(BorderFactory.createLineBorder(UIManager
405: .getDefaults().getColor("controlShadow"))); //$NON-NLS-1$
406: statusHolder = new JLabel(" "); //$NON-NLS-1$
407: statusPane.setMinimumSize(statusHolder.getPreferredSize());
408: statusPane.add(statusHolder, BorderLayout.WEST);
409:
410: return statusPane;
411: }
412:
413: /**
414: * Defines the text to be displayed on the status bar. Setting text will replace any other previously defined text.
415: *
416: * @param text the new statul bar text.
417: */
418: private void setStatusText(final String text) {
419: statusHolder.setText(text);
420: }
421:
422: // private String getStatusText ()
423: // {
424: // return statusHolder.getText();
425: // }
426:
427: /**
428: * Loads the report configuration from a user selectable report properties file.
429: */
430: protected void load() {
431: setStatusText(messages
432: .getString("ConfigEditor.USER_LOADING_FILE")); //$NON-NLS-1$
433: if (fileChooser == null) {
434: fileChooser = new JFileChooser();
435: final FilesystemFilter filter = new FilesystemFilter(
436: PROPERTIES_FILE_EXTENSION,
437: messages
438: .getString("config-editor.file-description.properties")); //$NON-NLS-1$
439: fileChooser.addChoosableFileFilter(filter);
440: fileChooser.setMultiSelectionEnabled(false);
441: }
442:
443: final int option = fileChooser.showSaveDialog(this );
444: if (option == JFileChooser.APPROVE_OPTION) {
445: final File selFile = fileChooser.getSelectedFile();
446: String selFileName = selFile.getAbsolutePath();
447:
448: // Test if ends on .properties
449: if (StringUtils.endsWithIgnoreCase(selFileName,
450: PROPERTIES_FILE_EXTENSION) == false) {
451: selFileName = selFileName + PROPERTIES_FILE_EXTENSION;
452: }
453: final Properties prop = new Properties();
454: try {
455: final InputStream in = new BufferedInputStream(
456: new FileInputStream(selFileName));
457: try {
458: prop.load(in);
459: } finally {
460: in.close();
461: }
462: } catch (IOException ioe) {
463: Log
464: .debug(
465: messages
466: .getErrorString(
467: "ConfigEditor.ERROR_0003_FAILED_TO_LOAD_PROPERTIES", ioe.toString()), ioe); //$NON-NLS-1$
468: setStatusText(messages
469: .getString(
470: "ConfigEditor.ERROR_0003_FAILED_TO_LOAD_PROPERTIES", ioe.getMessage())); //$NON-NLS-1$
471: return;
472: }
473:
474: reset();
475:
476: final Enumeration keys = prop.keys();
477: while (keys.hasMoreElements()) {
478: final String key = (String) keys.nextElement();
479: final String value = prop.getProperty(key);
480: currentReportConfiguration
481: .setConfigProperty(key, value);
482: }
483: try {
484: treeModel.init(currentReportConfiguration);
485: setStatusText(messages
486: .getString("ConfigEditor.USER_LOAD_PROPS_COMPLETE")); //$NON-NLS-1$
487: } catch (ConfigTreeModelException e) {
488: final String error = messages
489: .getString("ConfigEditor.USER_FAILED_TO_UPDATE_MODEL"); //$NON-NLS-1$
490: Log.debug(error, e);
491: setStatusText(error);
492: }
493: }
494: }
495:
496: /**
497: * Resets all values.
498: */
499: protected void reset() {
500: // clear all previously set configuration settings ...
501: final Enumeration defaults = currentReportConfiguration
502: .getConfigProperties();
503: while (defaults.hasMoreElements()) {
504: final String key = (String) defaults.nextElement();
505: currentReportConfiguration.setConfigProperty(key, null);
506: }
507: }
508:
509: /**
510: * Saves the report configuration to a user selectable report properties file.
511: */
512: protected void save() {
513: setStatusText(messages.getString("ConfigEditor.USER_SAVING")); //$NON-NLS-1$
514: detailEditorPane.store();
515:
516: if (fileChooser == null) {
517: fileChooser = new JFileChooser();
518: final FilesystemFilter filter = new FilesystemFilter(
519: PROPERTIES_FILE_EXTENSION,
520: messages
521: .getString("config-editor.file-description.properties")); //$NON-NLS-1$
522: fileChooser.addChoosableFileFilter(filter);
523: fileChooser.setMultiSelectionEnabled(false);
524: }
525:
526: final int option = fileChooser.showSaveDialog(this );
527: if (option == JFileChooser.APPROVE_OPTION) {
528: final File selFile = fileChooser.getSelectedFile();
529: String selFileName = selFile.getAbsolutePath();
530:
531: // Test if ends on xls
532: if (StringUtils.endsWithIgnoreCase(selFileName,
533: PROPERTIES_FILE_EXTENSION) == false) {
534: selFileName = selFileName + PROPERTIES_FILE_EXTENSION;
535: }
536: write(selFileName);
537: }
538: }
539:
540: /**
541: * Writes the configuration into the file specified by the given file name.
542: *
543: * @param filename the target file name
544: */
545: private void write(final String filename) {
546: final Properties prop = new Properties();
547: final ArrayList names = new ArrayList();
548: // clear all previously set configuration settings ...
549: final Enumeration defaults = currentReportConfiguration
550: .getConfigProperties();
551: while (defaults.hasMoreElements()) {
552: final String key = (String) defaults.nextElement();
553: names.add(key);
554: prop.setProperty(key, currentReportConfiguration
555: .getConfigProperty(key));
556: }
557:
558: Collections.sort(names);
559:
560: PrintWriter out = null;
561: try {
562: out = new PrintWriter(new OutputStreamWriter(
563: new BufferedOutputStream(new FileOutputStream(
564: filename))));
565:
566: for (int i = 0; i < names.size(); i++) {
567: final String key = (String) names.get(i);
568: final String value = prop.getProperty(key);
569:
570: final ConfigDescriptionEntry entry = treeModel
571: .getEntryForKey(key);
572: if (entry != null) {
573: final String description = entry.getDescription();
574: writeDescription(description, out);
575: }
576: saveConvert(key, ESCAPE_KEY, out);
577: out.print("="); //$NON-NLS-1$
578: saveConvert(value, ESCAPE_VALUE, out);
579: out.println();
580: }
581: out.close();
582: setStatusText(messages
583: .getString("ConfigEditor.USER_SAVING_COMPLETE")); //$NON-NLS-1$
584: } catch (IOException ioe) {
585: Log
586: .debug(
587: messages
588: .getErrorString(
589: "ConfigEditor.ERROR_0004_FAILED_PROPERTIES_SAVE", ioe.toString()), ioe); //$NON-NLS-1$
590: setStatusText(messages
591: .getString(
592: "ConfigEditor.ERROR_0004_FAILED_PROPERTIES_SAVE", ioe.getMessage())); //$NON-NLS-1$
593: } finally {
594: if (out != null) {
595: out.close();
596: }
597: }
598: }
599:
600: /**
601: * Writes a descriptive comment into the given print writer.
602: *
603: * @param text the text to be written. If it contains more than one line, every line will be prepended by the
604: * comment character.
605: * @param writer the writer that should receive the content.
606: */
607: private void writeDescription(final String text,
608: final PrintWriter writer) {
609: // check if empty content ... this case is easy ...
610: if (text.length() == 0) {
611: return;
612: }
613:
614: writer.println("# "); //$NON-NLS-1$
615: final LineBreakIterator iterator = new LineBreakIterator(text);
616: while (iterator.hasNext()) {
617: writer.print("# "); //$NON-NLS-1$
618: saveConvert((String) iterator.next(), ESCAPE_COMMENT,
619: writer);
620: writer.println();
621: }
622: }
623:
624: /**
625: * Performs the necessary conversion of an java string into a property escaped string.
626: *
627: * @param text the text to be escaped
628: * @param escapeMode the mode that should be applied.
629: * @param writer the writer that should receive the content.
630: */
631: private void saveConvert(final String text, final int escapeMode,
632: final PrintWriter writer) {
633: final char[] string = text.toCharArray();
634: final char[] hexChars = { '0', '1', '2', '3', '4', '5', '6',
635: '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
636:
637: for (int x = 0; x < string.length; x++) {
638: final char aChar = string[x];
639: switch (aChar) {
640: case ' ': {
641: if ((escapeMode != ESCAPE_COMMENT)
642: && (x == 0 || escapeMode == ESCAPE_KEY)) {
643: writer.print('\\');
644: }
645: writer.print(' ');
646: break;
647: }
648: case '\\': {
649: writer.print('\\');
650: writer.print('\\');
651: break;
652: }
653: case '\t': {
654: if (escapeMode == ESCAPE_COMMENT) {
655: writer.print(aChar);
656: } else {
657: writer.print('\\');
658: writer.print('t');
659: }
660: break;
661: }
662: case '\n': {
663: writer.print('\\');
664: writer.print('n');
665: break;
666: }
667: case '\r': {
668: writer.print('\\');
669: writer.print('r');
670: break;
671: }
672: case '\f': {
673: if (escapeMode == ESCAPE_COMMENT) {
674: writer.print(aChar);
675: } else {
676: writer.print('\\');
677: writer.print('f');
678: }
679: break;
680: }
681: case '#':
682: case '"':
683: case '!':
684: case '=':
685: case ':': {
686: if (escapeMode == ESCAPE_COMMENT) {
687: writer.print(aChar);
688: } else {
689: writer.print('\\');
690: writer.print(aChar);
691: }
692: break;
693: }
694: default:
695: if ((aChar < 0x0020) || (aChar > 0x007e)) {
696: writer.print('\\');
697: writer.print('u');
698: writer.print(hexChars[(aChar >> 12) & 0xF]);
699: writer.print(hexChars[(aChar >> 8) & 0xF]);
700: writer.print(hexChars[(aChar >> 4) & 0xF]);
701: writer.print(hexChars[aChar & 0xF]);
702: } else {
703: writer.print(aChar);
704: }
705: }
706: }
707: }
708:
709: /**
710: * Closes this frame and exits the JavaVM.
711: */
712: protected void attempClose() {
713: System.exit(0);
714: }
715:
716: /**
717: * Returns the detail editor pane.
718: *
719: * @return the detail editor.
720: */
721: protected ConfigEditorPanel getDetailEditorPane() {
722: return detailEditorPane;
723: }
724:
725: /**
726: * main Method to start the editor.
727: *
728: * @param args not used.
729: */
730: public static void main(final String[] args) {
731: try {
732: JFreeReportBoot.getInstance().start();
733: final ConfigEditor ed = new ConfigEditor();
734: ed.pack();
735: ed.setVisible(true);
736: } catch (Exception e) {
737: final String message = new Messages(
738: ConfigGUIModule.BUNDLE_NAME)
739: .getString("ConfigEditor.ERROR_0001_FAILED_TO_INITIALIZE"); //$NON-NLS-1$
740: Log.debug(message, e);
741: JOptionPane.showMessageDialog(null, message);
742: }
743: }
744: }
|