/*
* 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.engine.classic.core.modules.gui.commonswing;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import javax.swing.ComboBoxModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.libraries.base.util.Messages;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.fonts.encoding.EncodingRegistry;
/**
* A model for the 'encoding' combo box. This combobox model presents a selection for all available string encodings.
*
* @author Thomas Morgner.
*/
public class EncodingComboBoxModel implements ComboBoxModel {
private static final Log logger = LogFactory.getLog( EncodingComboBoxModel.class );
/**
* The property that defines which encodings are available in the export dialogs.
*/
public static final String AVAILABLE_ENCODINGS =
"org.pentaho.reporting.engine.classic.core.modules.gui.base.EncodingsAvailable"; //$NON-NLS-1$
/**
* The encodings available properties value for all properties.
*/
public static final String AVAILABLE_ENCODINGS_ALL = "all"; //$NON-NLS-1$
/**
* The encodings available properties value for properties defined in the properties file.
*/
public static final String AVAILABLE_ENCODINGS_FILE = "file"; //$NON-NLS-1$
/**
* The encodings available properties value for no properties defined. The encoding selection will be disabled.
*/
public static final String AVAILABLE_ENCODINGS_NONE = "none"; //$NON-NLS-1$
/**
* The name of the properties file used to define the available encodings. The property points to a resources in the
* classpath, not to a real file!
*/
public static final String ENCODINGS_DEFINITION_FILE =
"org.pentaho.reporting.engine.classic.core.modules.gui.base.EncodingsFile"; //$NON-NLS-1$
/**
* The default name for the encoding properties file. This property defaults to "
* /org/pentaho/reporting/engine/classic/core/jfreereport-encodings.properties".
*/
public static final String ENCODINGS_DEFINITION_FILE_DEFAULT =
"org/pentaho/reporting/engine/classic/core/modules/gui/commonswing/jfreereport-encodings.properties";
//$NON-NLS-1$
/**
* An encoding comparator.
*/
private static class EncodingCarrierComparator implements Comparator, Serializable {
protected EncodingCarrierComparator() {
}
/**
* Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
*
* @param o1
* the first object to be compared.
* @param o2
* the second object to be compared.
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater
* than the second.
* @throws java.lang.ClassCastException
* if the arguments' types prevent them from being compared by this Comparator.
*/
public int compare( final Object o1, final Object o2 ) {
final EncodingCarrier e1 = (EncodingCarrier) o1;
final EncodingCarrier e2 = (EncodingCarrier) o2;
final String name1 = e1.getName();
final String name2 = e2.getName();
if ( name1 == null && name2 == null ) {
return 0;
}
if ( name1 == null ) {
return -1;
}
if ( name2 == null ) {
return 1;
}
return name1.toLowerCase().compareTo( name2.toLowerCase() );
}
/**
* Returns <code>true</code> if this object is equal to <code>o</code>, and <code>false</code> otherwise.
*
* @param o
* the object.
* @return A boolean.
*/
public boolean equals( final Object o ) {
if ( o == null ) {
return false;
}
return getClass().equals( o.getClass() );
}
/**
* All comparators of this type are equal.
*
* @return A hash code.
*/
public int hashCode() {
return getClass().hashCode();
}
}
/**
* An encoding carrier.
*/
private static class EncodingCarrier {
/**
* The encoding name.
*/
private String name;
/**
* The encoding description.
*/
private String description;
/**
* The display name.
*/
private String displayName;
/**
* Creates a new encoding.
*
* @param name
* the name (<code>null</code> not permitted).
* @param description
* the description.
*/
protected EncodingCarrier( final String name, final String description ) {
this.name = name;
this.description = description;
if ( name == null ) {
this.displayName = null;
} else {
final StringBuffer dName = new StringBuffer( 100 );
dName.append( name );
dName.append( " (" ); //$NON-NLS-1$
dName.append( description );
dName.append( ')' );
this.displayName = dName.toString();
}
}
/**
* Returns the name.
*
* @return The name.
*/
public String getName() {
return name;
}
/**
* Returns the description.
*
* @return The description.
*/
public String getDescription() {
return description;
}
/**
* Returns the display name (the regular name followed by the description in brackets).
*
* @return The display name.
*/
public String getDisplayName() {
return displayName;
}
/**
* Returns <code>true</code> if the objects are equal, and <code>false</code> otherwise.
*
* @param o
* the object.
* @return A boolean.
*/
public boolean equals( final Object o ) {
if ( this == o ) {
return true;
}
if ( !( o instanceof EncodingCarrier ) ) {
return false;
}
final EncodingCarrier carrier = (EncodingCarrier) o;
if ( name == null ) {
if ( carrier.name != null ) {
return false;
}
} else if ( !name.equalsIgnoreCase( carrier.name ) ) {
return false;
}
return true;
}
/**
* Returns a hash code.
*
* @return The hash code.
*/
public int hashCode() {
if ( name == null ) {
return 0;
}
return name.hashCode();
}
}
/**
* Storage for the encodings.
*/
private final ArrayList encodings;
/**
* Storage for registered listeners.
*/
private ArrayList listDataListeners;
/**
* The selected index.
*/
private int selectedIndex;
/**
* The selected object.
*/
private Object selectedObject;
private ResourceBundle bundle;
public static final String BUNDLE_NAME =
"org.pentaho.reporting.engine.classic.core.modules.gui.commonswing.encoding-names"; //$NON-NLS-1$
private Messages messages;
private String ENCODING_DEFAULT_DESCRIPTION;
/**
* Creates a new model.
*
* @param locale
*/
public EncodingComboBoxModel( final Locale locale ) {
bundle = ResourceBundle.getBundle( EncodingComboBoxModel.BUNDLE_NAME, locale );
messages =
new Messages( locale, SwingCommonModule.BUNDLE_NAME, ObjectUtilities.getClassLoader( SwingCommonModule.class ) );
ENCODING_DEFAULT_DESCRIPTION = messages.getString( "EncodingComboBoxModel.USER_ENCODING_DEFAULT_DESCRIPTION" ); //$NON-NLS-1$
encodings = new ArrayList();
listDataListeners = null;
selectedIndex = -1;
}
/**
* Adds an encoding.
*
* @param name
* the name.
* @param description
* the description.
* @return <code>true</code> if the encoding is valid and added to the model, <code>false</code> otherwise.
*/
public boolean addEncoding( final String name, final String description ) {
if ( EncodingRegistry.getInstance().isSupportedEncoding( name ) ) {
encodings.add( new EncodingCarrier( name, description ) );
} else {
return false;
}
fireContentsChanged();
return true;
}
/**
* Adds an encoding to the model without checking its validity.
*
* @param name
* the name.
* @param description
* the description.
*/
public void addEncodingUnchecked( final String name, final String description ) {
encodings.add( new EncodingCarrier( name, description ) );
fireContentsChanged();
}
public void removeEncoding( final String name ) {
if ( encodings.remove( name ) ) {
fireContentsChanged();
}
}
/**
* Make sure, that this encoding is defined and selectable in the combobox model.
*
* @param encoding
* the encoding that should be verified.
*/
public void ensureEncodingAvailable( final String encoding ) {
if ( encoding == null ) {
throw new NullPointerException( messages
.getErrorString( "EncodingComboBoxModel.ERROR_0001_ENCODING_CANNOT_BE_NULL" ) ); //$NON-NLS-1$
}
final String desc = getEncodingDescription( encoding );
final EncodingCarrier ec = new EncodingCarrier( encoding, desc );
if ( encodings.contains( ec ) == false ) {
encodings.add( ec );
fireContentsChanged();
}
}
protected String getEncodingDescription( final String encoding ) {
try {
return bundle.getString( encoding );
} catch ( Exception e ) {
return ENCODING_DEFAULT_DESCRIPTION;
}
}
/**
* Sorts the encodings. Keep the selected object ...
*/
public void sort() {
final Object selectedObject = getSelectedItem();
Collections.sort( encodings, new EncodingCarrierComparator() );
setSelectedItem( selectedObject );
fireContentsChanged();
}
/**
* Notifies all registered listeners that the content of the model has changed.
*/
protected void fireContentsChanged() {
if ( listDataListeners == null ) {
return;
}
fireContentsChanged( 0, getSize() );
}
/**
* Notifies all registered listeners that the content of the model has changed.
*/
protected void fireContentsChanged( final int start, final int length ) {
if ( listDataListeners == null ) {
return;
}
final ListDataEvent evt = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, start, length );
for ( int i = 0; i < listDataListeners.size(); i++ ) {
final ListDataListener l = (ListDataListener) listDataListeners.get( i );
l.contentsChanged( evt );
}
}
/**
* Set the selected item. The implementation of this method should notify all registered <code>ListDataListener</code>
* s that the contents have changed.
*
* @param anItem
* the list object to select or <code>null</code> to clear the selection
*/
public void setSelectedItem( final Object anItem ) {
selectedObject = anItem;
if ( anItem instanceof String ) {
final int size = getSize();
for ( int i = 0; i < size; i++ ) {
if ( anItem.equals( getElementAt( i ) ) ) {
selectedIndex = i;
fireContentsChanged( -1, -1 );
return;
}
}
}
selectedIndex = -1;
fireContentsChanged( -1, -1 );
}
/**
* Returns the selected index.
*
* @return The index.
*/
public int getSelectedIndex() {
return selectedIndex;
}
/**
* Defines the selected index for this encoding model.
*
* @param index
* the selected index or -1 to clear the selection.
* @throws java.lang.IllegalArgumentException
* if the given index is invalid.
*/
public void setSelectedIndex( final int index ) {
if ( index == -1 ) {
selectedIndex = -1;
selectedObject = null;
fireContentsChanged( -1, -1 );
return;
}
if ( index < -1 || index >= getSize() ) {
throw new IllegalArgumentException( messages.getErrorString( "EncodingComboBoxModel.ERROR_0001_INVALID_INDEX" ) ); //$NON-NLS-1$
}
selectedIndex = index;
selectedObject = getElementAt( index );
fireContentsChanged( -1, -1 );
}
/**
* Returns the selected encoding.
*
* @return The encoding (name).
*/
public String getSelectedEncoding() {
if ( selectedIndex == -1 ) {
return null;
}
final EncodingCarrier ec = (EncodingCarrier) encodings.get( selectedIndex );
return ec.getName();
}
/**
* Returns the selected item.
*
* @return The selected item or <code>null</code> if there is no selection
*/
public Object getSelectedItem() {
return selectedObject;
}
/**
* Returns the length of the list.
*
* @return the length of the list
*/
public int getSize() {
return encodings.size();
}
/**
* Returns the value at the specified index.
*
* @param index
* the requested index
* @return the value at <code>index</code>
*/
public Object getElementAt( final int index ) {
final EncodingCarrier ec = (EncodingCarrier) encodings.get( index );
return ec.getDisplayName();
}
/**
* Adds a listener to the list that's notified each time a change to the data model occurs.
*
* @param l
* the <code>ListDataListener</code> to be added
*/
public void addListDataListener( final ListDataListener l ) {
if ( listDataListeners == null ) {
listDataListeners = new ArrayList( 5 );
}
listDataListeners.add( l );
}
/**
* Removes a listener from the list that's notified each time a change to the data model occurs.
*
* @param l
* the <code>ListDataListener</code> to be removed
*/
public void removeListDataListener( final ListDataListener l ) {
if ( listDataListeners == null ) {
return;
}
listDataListeners.remove( l );
}
/**
* Creates a default model containing a selection of encodings.
*
* @return The default model.
*/
public static EncodingComboBoxModel createDefaultModel( final Locale locale ) {
return createDefaultModel( locale, false );
}
public static EncodingComboBoxModel createDefaultModel( final Locale locale, boolean includeNull ) {
final EncodingComboBoxModel ecb = new EncodingComboBoxModel( locale );
if ( includeNull ) {
ecb.addEncodingUnchecked( null, "" );
}
final String availEncs = getAvailableEncodings();
final boolean allEncodings = availEncs.equalsIgnoreCase( AVAILABLE_ENCODINGS_ALL );
if ( allEncodings || availEncs.equals( AVAILABLE_ENCODINGS_FILE ) ) {
final String encFile = getEncodingsDefinitionFile();
final InputStream in = ObjectUtilities.getResourceAsStream( encFile, EncodingComboBoxModel.class );
if ( in == null ) {
final Messages messages =
new Messages( locale, SwingCommonModule.BUNDLE_NAME, ObjectUtilities
.getClassLoader( SwingCommonModule.class ) );
logger.warn( messages.getString( "EncodingComboBoxModel.WARN_ENCODING_FILE_NOT_FOUND", encFile ) ); //$NON-NLS-1$
} else {
try {
// final Properties defaultEncodings = getDefaultEncodings();
final Properties encDef = new Properties();
final BufferedInputStream bin = new BufferedInputStream( in );
try {
encDef.load( bin );
} finally {
bin.close();
}
final Enumeration en = encDef.keys();
while ( en.hasMoreElements() ) {
final String enc = (String) en.nextElement();
// if not set to "true"
if ( "true".equalsIgnoreCase( encDef.getProperty( enc, "false" ) ) ) { //$NON-NLS-1$ //$NON-NLS-2$
// if the encoding is disabled ...
ecb.addEncoding( enc, ecb.getEncodingDescription( enc ) );
}
}
} catch ( IOException e ) {
final Messages messages =
new Messages( locale, SwingCommonModule.BUNDLE_NAME, ObjectUtilities
.getClassLoader( SwingCommonModule.class ) );
logger.warn( messages.getString( "EncodingComboBoxModel.WARN_ERROR_READING_ENCODING_FILE" ) + encFile, e ); //$NON-NLS-1$
}
}
}
return ecb;
}
/**
* Returns the index of an encoding.
*
* @param encoding
* the encoding (name).
* @return The index.
*/
public int indexOf( final String encoding ) {
return encodings.indexOf( new EncodingCarrier( encoding, null ) );
}
/**
* Returns an encoding.
*
* @param index
* the index.
* @return The index.
*/
public String getEncoding( final int index ) {
final EncodingCarrier ec = (EncodingCarrier) encodings.get( index );
return ec.getName();
}
/**
* Returns a description.
*
* @param index
* the index.
* @return The description.
*/
public String getDescription( final int index ) {
final EncodingCarrier ec = (EncodingCarrier) encodings.get( index );
return ec.getDescription();
}
/**
* Defines the loader settings for the available encodings shown to the user. The property defaults to
* AVAILABLE_ENCODINGS_ALL.
*
* @return either AVAILABLE_ENCODINGS_ALL, AVAILABLE_ENCODINGS_FILE or AVAILABLE_ENCODINGS_NONE.
*/
public static String getEncodingsDefinitionFile() {
return ClassicEngineBoot.getInstance().getGlobalConfig().getConfigProperty( ENCODINGS_DEFINITION_FILE,
ENCODINGS_DEFINITION_FILE_DEFAULT );
}
/**
* Defines the loader settings for the available encodings shown to the user. The property defaults to
* AVAILABLE_ENCODINGS_ALL.
*
* @return either AVAILABLE_ENCODINGS_ALL, AVAILABLE_ENCODINGS_FILE or AVAILABLE_ENCODINGS_NONE.
*/
public static String getAvailableEncodings() {
return ClassicEngineBoot.getInstance().getGlobalConfig().getConfigProperty( AVAILABLE_ENCODINGS,
AVAILABLE_ENCODINGS_ALL );
}
public void setSelectedEncoding( final String encoding ) {
if ( encoding == null ) {
selectedIndex = -1;
selectedObject = null;
return;
}
final int size = encodings.size();
for ( int i = 0; i < size; i++ ) {
final EncodingCarrier carrier = (EncodingCarrier) encodings.get( i );
if ( encoding.equals( carrier.getName() ) ) {
selectedIndex = i;
selectedObject = carrier.getDisplayName();
fireContentsChanged( -1, -1 );
return;
}
}
// default fall-back to have a valid value ..
if ( size > 0 ) {
selectedIndex = 0;
selectedObject = getElementAt( 0 );
fireContentsChanged( -1, -1 );
}
}
}