/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.tools.configeditor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.boot.AbstractBoot;
import org.pentaho.reporting.libraries.base.config.HierarchicalConfiguration;
import org.pentaho.reporting.libraries.base.util.FilesystemFilter;
import org.pentaho.reporting.libraries.base.util.Messages;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.ResourceBundleSupport;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.tools.configeditor.model.ConfigTreeModelException;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Properties;
/**
* The ConfigEditor can be used to edit the global jfreereport.properties files. These files provide global settings for
* all reports and contain the system level configuration of JFreeReport.
*
* @author Thomas Morgner
*/
public class ConfigEditor extends JFrame {
private static final Log logger = LogFactory.getLog( ConfigEditor.class );
/**
* An Action to handle close requests.
*/
private class CloseAction extends AbstractAction {
/**
* DefaultConstructor.
*/
protected CloseAction() {
putValue( Action.NAME, getResources().getString( "action.exit.name" ) ); //$NON-NLS-1$
}
/**
* Invoked when an action occurs. The action invokes System.exit(0).
*
* @param e the action event.
*/
public void actionPerformed( final ActionEvent e ) {
attempClose();
}
}
/**
* An action to handle save requests.
*/
private class SaveAction extends AbstractAction {
/**
* DefaultConstructor.
*/
protected SaveAction() {
putValue( Action.NAME, getResources().getString( "action.save.name" ) ); //$NON-NLS-1$
putValue( Action.SMALL_ICON, getResources().getIcon( "action.save.small-icon" ) ); //$NON-NLS-1$
}
/**
* Saves the configuration.
*
* @param e the action event.
*/
public void actionPerformed( final ActionEvent e ) {
save();
}
}
/**
* An action to handle load requests.
*/
private class LoadAction extends AbstractAction {
/**
* DefaultConstructor.
*/
protected LoadAction() {
putValue( Action.NAME, getResources().getString( "action.load.name" ) ); //$NON-NLS-1$
putValue( Action.SMALL_ICON, getResources().getIcon( "action.load.small-icon" ) ); //$NON-NLS-1$
}
/**
* Loads the configuration.
*
* @param e the action event.
*/
public void actionPerformed( final ActionEvent e ) {
load();
}
}
/**
* An action to handle new requests, which reset the report configuration.
*/
private class NewAction extends AbstractAction {
/**
* DefaultConstructor.
*/
protected NewAction() {
putValue( Action.NAME, getResources().getString( "action.new.name" ) ); //$NON-NLS-1$
putValue( Action.SMALL_ICON, getResources().getIcon( "action.new.small-icon" ) ); //$NON-NLS-1$
}
/**
* Reset the configuration.
*
* @param e the action event.
*/
public void actionPerformed( final ActionEvent e ) {
reset();
}
}
private class CloseHandler extends WindowAdapter {
private CloseHandler() {
}
/**
* Invoked when a window is in the process of being closed. The close operation can be overridden at this point.
*/
public void windowClosing( final WindowEvent e ) {
attempClose();
}
}
private static final String PROPERTIES_FILE_EXTENSION = ".properties";
/**
* A constant defining that text should be escaped in a way which is suitable for property keys.
*/
private static final int ESCAPE_KEY = 0;
/**
* A constant defining that text should be escaped in a way which is suitable for property values.
*/
private static final int ESCAPE_VALUE = 1;
/**
* A constant defining that text should be escaped in a way which is suitable for property comments.
*/
private static final int ESCAPE_COMMENT = 2;
/**
* A label that serves as status bar.
*/
private JLabel statusHolder;
/**
* The resource bundle instance of this dialog.
*/
private final ResourceBundleSupport resources;
/**
* The file chooser used to load and save the report configuration.
*/
private JFileChooser fileChooser;
private HierarchicalConfiguration configuration;
private ConfigEditorPane editorPane;
public ConfigEditor() throws ConfigTreeModelException {
this( new HierarchicalConfiguration( ConfigEditorBoot.getInstance().getGlobalConfig() ),
ConfigEditorBoot.getInstance() );
}
public ConfigEditor( final HierarchicalConfiguration configuration,
final AbstractBoot packageManager ) throws ConfigTreeModelException {
this.configuration = configuration;
resources = new ResourceBundleSupport( getLocale(), ConfigEditorBoot.BUNDLE_NAME,
ObjectUtilities.getClassLoader( ConfigEditor.class ) );
editorPane = new ConfigEditorPane( packageManager, true );
editorPane.updateConfiguration( configuration );
setTitle( resources.getString( "config-editor.title" ) ); //$NON-NLS-1$
final JPanel contentPane = new JPanel();
contentPane.setLayout( new BorderLayout() );
contentPane.add( editorPane, BorderLayout.CENTER );
contentPane.add( createButtonPane(), BorderLayout.SOUTH );
final JPanel cPaneStatus = new JPanel();
cPaneStatus.setLayout( new BorderLayout() );
cPaneStatus.add( contentPane, BorderLayout.CENTER );
cPaneStatus.add( createStatusBar(), BorderLayout.SOUTH );
setContentPane( cPaneStatus );
addWindowListener( new CloseHandler() );
}
/**
* Returns the resource bundle of this editor for translating strings.
*
* @return the resource bundle.
*/
protected ResourceBundleSupport getResources() {
return resources;
}
/**
* Creates the button pane to hold all control buttons.
*
* @return the created panel with all control buttons.
*/
private JPanel createButtonPane() {
final Action closeAction = new CloseAction();
final Action saveAction = new SaveAction();
final Action loadAction = new LoadAction();
final Action newAction = new NewAction();
final JPanel buttonHolder = new JPanel();
buttonHolder.setLayout( new GridLayout( 1, 4, 5, 5 ) );
buttonHolder.add( new JButton( newAction ) );
buttonHolder.add( new JButton( loadAction ) );
buttonHolder.add( new JButton( saveAction ) );
buttonHolder.add( new JButton( closeAction ) );
final JPanel panel = new JPanel();
panel.setLayout( new FlowLayout( FlowLayout.RIGHT ) );
panel.setBorder( new EmptyBorder( 5, 5, 5, 5 ) );
panel.add( buttonHolder );
return panel;
}
/**
* Creates the statusbar for this frame. Use setStatus() to display text on the status bar.
*
* @return the status bar.
*/
protected JPanel createStatusBar() {
final JPanel statusPane = new JPanel();
statusPane.setLayout( new BorderLayout() );
statusPane.setBorder( BorderFactory.createLineBorder( UIManager.getDefaults().getColor(
"controlShadow" ) ) ); //$NON-NLS-1$
statusHolder = new JLabel( " " ); //$NON-NLS-1$
statusPane.setMinimumSize( statusHolder.getPreferredSize() );
statusPane.add( statusHolder, BorderLayout.WEST );
return statusPane;
}
/**
* Defines the text to be displayed on the status bar. Setting text will replace any other previously defined text.
*
* @param text the new statul bar text.
*/
private void setStatusText( final String text ) {
statusHolder.setText( text );
}
// private String getStatusText ()
// {
// return statusHolder.getText();
// }
/**
* Loads the report configuration from a user selectable report properties file.
*/
protected void load() {
setStatusText( resources.getString( "ConfigEditor.USER_LOADING_FILE" ) ); //$NON-NLS-1$
if ( fileChooser == null ) {
fileChooser = new JFileChooser();
final FilesystemFilter filter = new FilesystemFilter
( ConfigEditor.PROPERTIES_FILE_EXTENSION, resources.getString(
"config-editor.file-description.properties" ) ); //$NON-NLS-1$
fileChooser.addChoosableFileFilter( filter );
fileChooser.setMultiSelectionEnabled( false );
}
final int option = fileChooser.showOpenDialog( this );
if ( option == JFileChooser.APPROVE_OPTION ) {
final File selFile = fileChooser.getSelectedFile();
String selFileName = selFile.getAbsolutePath();
// Test if ends on .properties
if ( StringUtils.endsWithIgnoreCase( selFileName, ConfigEditor.PROPERTIES_FILE_EXTENSION ) == false ) {
selFileName = selFileName + ConfigEditor.PROPERTIES_FILE_EXTENSION;
}
final Properties prop = new Properties();
try {
final InputStream in = new BufferedInputStream( new FileInputStream( selFileName ) );
try {
prop.load( in );
} finally {
in.close();
}
} catch ( IOException ioe ) {
ConfigEditor.logger.debug( resources.getString( "ConfigEditor.ERROR_0003_FAILED_TO_LOAD_PROPERTIES",
ioe.toString() ), ioe ); //$NON-NLS-1$
setStatusText( resources.getString( "ConfigEditor.ERROR_0003_FAILED_TO_LOAD_PROPERTIES",
ioe.getMessage() ) ); //$NON-NLS-1$
return;
}
reset();
final Enumeration keys = prop.keys();
while ( keys.hasMoreElements() ) {
final String key = (String) keys.nextElement();
final String value = prop.getProperty( key );
configuration.setConfigProperty( key, value );
}
editorPane.updateConfiguration( configuration );
setStatusText( resources.getString( "ConfigEditor.USER_LOAD_PROPS_COMPLETE" ) ); //$NON-NLS-1$
}
}
protected void reset() {
editorPane.reset();
}
/**
* Saves the report configuration to a user selectable report properties file.
*/
protected void save() {
setStatusText( resources.getString( "ConfigEditor.USER_SAVING" ) ); //$NON-NLS-1$
editorPane.commit();
if ( fileChooser == null ) {
fileChooser = new JFileChooser();
final FilesystemFilter filter = new FilesystemFilter
( ConfigEditor.PROPERTIES_FILE_EXTENSION, resources.getString(
"config-editor.file-description.properties" ) ); //$NON-NLS-1$
fileChooser.addChoosableFileFilter( filter );
fileChooser.setMultiSelectionEnabled( false );
}
final int option = fileChooser.showSaveDialog( this );
if ( option == JFileChooser.APPROVE_OPTION ) {
final File selFile = fileChooser.getSelectedFile();
String selFileName = selFile.getAbsolutePath();
// Test if ends on xls
if ( StringUtils.endsWithIgnoreCase( selFileName, ConfigEditor.PROPERTIES_FILE_EXTENSION ) == false ) {
selFileName = selFileName + ConfigEditor.PROPERTIES_FILE_EXTENSION;
}
write( selFileName );
}
}
/**
* Writes the configuration into the file specified by the given file name.
*
* @param filename the target file name
*/
private void write( final String filename ) {
final Properties prop = new Properties();
final ArrayList<String> names = new ArrayList<String>();
// clear all previously set configuration settings ...
final Enumeration defaults = configuration.getConfigProperties();
while ( defaults.hasMoreElements() ) {
final String key = (String) defaults.nextElement();
names.add( key );
prop.setProperty( key, configuration.getConfigProperty( key ) );
}
Collections.sort( names );
PrintWriter out = null;
try {
out = new PrintWriter( new OutputStreamWriter( new BufferedOutputStream( new FileOutputStream( filename ) ) ) );
for ( int i = 0; i < names.size(); i++ ) {
final String key = names.get( i );
final String value = prop.getProperty( key );
final String description = editorPane.getDescriptionForKey( key );
if ( description != null ) {
writeDescription( description, out );
}
saveConvert( key, ConfigEditor.ESCAPE_KEY, out );
out.print( "=" ); //$NON-NLS-1$
saveConvert( value, ConfigEditor.ESCAPE_VALUE, out );
out.println();
}
out.close();
setStatusText( resources.getString( "ConfigEditor.USER_SAVING_COMPLETE" ) ); //$NON-NLS-1$
} catch ( IOException ioe ) {
ConfigEditor.logger.debug( resources.getString( "ConfigEditor.ERROR_0004_FAILED_PROPERTIES_SAVE",
ioe.toString() ), ioe ); //$NON-NLS-1$
setStatusText( resources.getString( "ConfigEditor.ERROR_0004_FAILED_PROPERTIES_SAVE",
ioe.getMessage() ) ); //$NON-NLS-1$
} finally {
if ( out != null ) {
out.close();
}
}
}
/**
* Writes a descriptive comment into the given print writer.
*
* @param text the text to be written. If it contains more than one line, every line will be prepended by the
* comment character.
* @param writer the writer that should receive the content.
* @noinspection NestedAssignment
*/
private void writeDescription( final String text, final PrintWriter writer ) {
// check if empty content ... this case is easy ...
if ( text.length() == 0 ) {
return;
}
writer.println( "# " ); //$NON-NLS-1$
try {
final BufferedReader br = new BufferedReader( new StringReader( text ) );
String s;
while ( ( s = br.readLine() ) != null ) {
writer.print( "# " ); //$NON-NLS-1$
saveConvert( s, ConfigEditor.ESCAPE_COMMENT, writer );
writer.println();
}
br.close();
} catch ( IOException e ) {
// does not happen, this is a string-reader
}
}
/**
* Performs the necessary conversion of an java string into a property escaped string.
*
* @param text the text to be escaped
* @param escapeMode the mode that should be applied.
* @param writer the writer that should receive the content.
*/
private void saveConvert( final String text, final int escapeMode,
final PrintWriter writer ) {
final char[] string = text.toCharArray();
final char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
for ( int x = 0; x < string.length; x++ ) {
final char aChar = string[ x ];
switch( aChar ) {
case ' ': {
if ( ( escapeMode != ConfigEditor.ESCAPE_COMMENT ) &&
( x == 0 || escapeMode == ConfigEditor.ESCAPE_KEY ) ) {
writer.print( '\\' );
}
writer.print( ' ' );
break;
}
case '\\': {
writer.print( '\\' );
writer.print( '\\' );
break;
}
case '\t': {
if ( escapeMode == ConfigEditor.ESCAPE_COMMENT ) {
writer.print( aChar );
} else {
writer.print( '\\' );
writer.print( 't' );
}
break;
}
case '\n': {
writer.print( '\\' );
writer.print( 'n' );
break;
}
case '\r': {
writer.print( '\\' );
writer.print( 'r' );
break;
}
case '\f': {
if ( escapeMode == ConfigEditor.ESCAPE_COMMENT ) {
writer.print( aChar );
} else {
writer.print( '\\' );
writer.print( 'f' );
}
break;
}
case '#':
case '"':
case '!':
case '=':
case ':': {
if ( escapeMode == ConfigEditor.ESCAPE_COMMENT ) {
writer.print( aChar );
} else {
writer.print( '\\' );
writer.print( aChar );
}
break;
}
default:
if ( ( aChar < 0x0020 ) || ( aChar > 0x007e ) ) {
writer.print( '\\' );
writer.print( 'u' );
writer.print( hexChars[ ( aChar >> 12 ) & 0xF ] );
writer.print( hexChars[ ( aChar >> 8 ) & 0xF ] );
writer.print( hexChars[ ( aChar >> 4 ) & 0xF ] );
writer.print( hexChars[ aChar & 0xF ] );
} else {
writer.print( aChar );
}
}
}
}
/**
* Closes this frame and exits the JavaVM.
*/
protected void attempClose() {
System.exit( 0 );
}
/**
* main Method to start the editor.
*
* @param args not used.
*/
public static void main( final String[] args ) {
try {
ConfigEditorBoot.getInstance().start();
final ConfigEditor ed = new ConfigEditor();
ed.pack();
ed.setVisible( true );
} catch ( Exception e ) {
final Messages messages = new Messages
( Locale.getDefault(), ConfigEditorBoot.BUNDLE_NAME, ObjectUtilities.getClassLoader( ConfigEditorBoot.class ) );
final String message = messages.getString(
"ConfigEditor.ERROR_0001_FAILED_TO_INITIALIZE" ); //$NON-NLS-1$
logger.debug( message, e );
JOptionPane.showMessageDialog( null, message );
}
}
}