/* * 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.model; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import org.pentaho.reporting.libraries.xmlns.common.AttributeList; import org.pentaho.reporting.libraries.xmlns.parser.ParseException; import org.pentaho.reporting.libraries.xmlns.writer.CharacterEntityParser; import org.pentaho.reporting.libraries.xmlns.writer.DefaultTagDescription; import org.pentaho.reporting.libraries.xmlns.writer.XmlWriter; import org.pentaho.reporting.libraries.xmlns.writer.XmlWriterSupport; import org.pentaho.reporting.tools.configeditor.ConfigEditorBoot; import org.pentaho.reporting.tools.configeditor.Messages; import org.pentaho.reporting.tools.configeditor.util.DOMUtilities; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.swing.*; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; /** * This list model implementation collects all config description entries defined in JFreeReport. This model is used to * create a configuration key definition; it directly manipulates the metadata for the keys as stored in the * config-description.xml file. * * @author Thomas Morgner */ public class ConfigDescriptionModel extends AbstractListModel { private static final Log logger = LogFactory.getLog( ConfigDescriptionModel.class ); /** * Compares an config description entry against an other entry. This simple implementation just compares the names of * the two entries. */ private static class ConfigEntryComparator implements Comparator<ConfigDescriptionEntry>, Serializable { /** * DefaultConstructor. */ protected ConfigEntryComparator() { } /** * 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.<p> * * @param o1 the first object to compare * @param o2 the second object to compare * @return an integer indicating the comparison result. */ public int compare( final ConfigDescriptionEntry o1, final ConfigDescriptionEntry o2 ) { if ( o1 == null && o2 == null ) { return 0; } if ( o1 == null ) { return -1; } if ( o2 == null ) { return 1; } return o1.getKeyName().compareTo( o2.getKeyName() ); } } /** * The content of this list; all config description entries. */ private final ArrayList<ConfigDescriptionEntry> content; /** * Provides access to externalized strings */ private Messages messages; /** * Creates a new, initially empty ConfigDescriptionModel. */ public ConfigDescriptionModel() { messages = Messages.getInstance(); content = new ArrayList<ConfigDescriptionEntry>(); } /** * Adds the given entry to the end of the list. * * @param entry the new entry. */ public void add( final ConfigDescriptionEntry entry ) { if ( entry == null ) { throw new NullPointerException( messages.getString( "ConfigDescriptionModel.ERROR_0001_ENTRY_IS_NULL" ) ); //$NON-NLS-1$ } // Only add unique elements ... final int index = findEntry( entry.getKeyName() ); if ( index == -1 ) { content.add( entry ); updated(); } else { content.set( index, entry ); } } private int findEntry( final String key ) { for ( int i = 0; i < content.size(); i++ ) { final ConfigDescriptionEntry configDescriptionEntry = content.get( i ); if ( key.equals( configDescriptionEntry.getKeyName() ) ) { return i; } } return -1; } /** * Removes the given entry from the list. * * @param entry the entry that should be removed. */ public void remove( final ConfigDescriptionEntry entry ) { if ( entry == null ) { throw new NullPointerException( messages.getString( "ConfigDescriptionModel.ERROR_0002_ENTRY_IS_NULL" ) ); //$NON-NLS-1$ } final int index = findEntry( entry.getKeyName() ); if ( index != -1 ) { content.remove( index ); updated(); } } public void removeAll( final int[] indices ) { if ( indices.length == 0 ) { return; } final Object[] entries = new Object[ indices.length ]; for ( int i = indices.length - 1; i >= 0; i-- ) { entries[ i ] = content.get( indices[ i ] ); } for ( int i = 0; i < entries.length; i++ ) { content.remove( entries[ i ] ); } updated(); } /** * Returns the entry stored on the given list position. * * @param pos the position * @return the entry * @throws IndexOutOfBoundsException if the position is invalid. */ public ConfigDescriptionEntry get( final int pos ) { return content.get( pos ); } /** * Fires an contents changed event for all elements in the list. */ public void updated() { fireContentsChanged( this, 0, getSize() ); } /** * Returns the index of the given entry or -1, if the entry is not in the list. * * @param entry the entry whose position should be searched. * @return the position of the entry */ public int indexOf( final ConfigDescriptionEntry entry ) { if ( entry == null ) { throw new NullPointerException( messages.getString( "ConfigDescriptionModel.ERROR_0003_ENTRY_IS_NULL" ) ); //$NON-NLS-1$ } return findEntry( entry.getKeyName() ); } /** * Checks whether the given entry is already contained in this list. * * @param entry the entry that should be checked. * @return true, if the entry is already added, false otherwise. */ public boolean contains( final ConfigDescriptionEntry entry ) { if ( entry == null ) { throw new NullPointerException( messages.getString( "ConfigDescriptionModel.ERROR_0004_ENTRY_IS_NULL" ) ); //$NON-NLS-1$ } return findEntry( entry.getKeyName() ) > -1; } /** * Sorts the entries of the list. Be aware that calling this method does not fire an updat event; you have to do this * manually. */ public void sort() { Collections.sort( content, new ConfigEntryComparator() ); updated(); } /** * Returns the contents of this model as object array. * * @return the contents of the model as array. */ public ConfigDescriptionEntry[] toArray() { return content.toArray( new ConfigDescriptionEntry[ content.size() ] ); } /** * Returns the length of the list. * * @return the length of the list */ public int getSize() { return content.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 ConfigDescriptionEntry entry = get( index ); if ( entry == null ) { return null; } return entry.getKeyName(); } /** * Imports all entries from the given report configuration. Only new entries will be added to the list. This does not * add report properties supplied via the System.properties. * * @param config the report configuration from where to add the entries. */ public void importFromConfig( final Configuration config ) { final Iterator it = config.findPropertyKeys( "" ); //$NON-NLS-1$ while ( it.hasNext() ) { final String keyname = (String) it.next(); if ( System.getProperties().containsKey( keyname ) ) { continue; } final TextConfigDescriptionEntry entry = new TextConfigDescriptionEntry( keyname ); if ( contains( entry ) == false ) { add( entry ); } } } /** * Loads the entries from the given xml file. The file must be in the format of the config-description.xml file. * * @param in the inputstream from where to read the file * @throws IOException if an error occured while reading the file * @throws SAXException if an XML parse error occurs. * @throws ParserConfigurationException if the XML parser could not be initialized. */ public void load( final InputStream in ) throws IOException, SAXException, ParserConfigurationException { content.clear(); final Document doc = DOMUtilities.parseInputStream( in ); final Element e = doc.getDocumentElement(); final NodeList list = e.getElementsByTagName( "key" ); //$NON-NLS-1$ for ( int i = 0; i < list.getLength(); i++ ) { final Element keyElement = (Element) list.item( i ); final String keyName = keyElement.getAttribute( "name" ); //$NON-NLS-1$ final boolean keyGlobal = "true".equals( keyElement.getAttribute( "global" ) ); //$NON-NLS-1$ final boolean keyHidden = "true".equals( keyElement.getAttribute( "hidden" ) ); //$NON-NLS-1$ final String descr = getDescription( keyElement ).trim(); final NodeList enumNodes = keyElement.getElementsByTagName( "enum" ); //$NON-NLS-1$ if ( enumNodes.getLength() != 0 ) { final String[] alteratives = collectEnumEntries( (Element) enumNodes.item( 0 ) ); final EnumConfigDescriptionEntry en = new EnumConfigDescriptionEntry( keyName ); en.setDescription( descr ); en.setGlobal( keyGlobal ); en.setHidden( keyHidden ); en.setOptions( alteratives ); add( en ); continue; } final NodeList classNodes = keyElement.getElementsByTagName( "class" ); //$NON-NLS-1$ if ( classNodes.getLength() != 0 ) { final Element classElement = (Element) classNodes.item( 0 ); final String className = classElement.getAttribute( "instanceof" ); //$NON-NLS-1$ if ( className == null ) { throw new ParseException( messages.getString( "ConfigDescriptionModel.ERROR_0005_MISSING_INSTANCEOF" ) ); //$NON-NLS-1$ } try { final ClassLoader classLoader = ObjectUtilities.getClassLoader( getClass() ); final Class baseClass = Class.forName( className, false, classLoader ); final ClassConfigDescriptionEntry ce = new ClassConfigDescriptionEntry( keyName ); ce.setBaseClass( baseClass ); ce.setDescription( descr ); ce.setGlobal( keyGlobal ); ce.setHidden( keyHidden ); add( ce ); continue; } catch ( Exception ex ) { final String message = messages.getString( "ConfigDescriptionModel.ERROR_0006_BASE_CLASS_LOAD_FAILED" ); //$NON-NLS-1$ ConfigDescriptionModel.logger.error( message, ex ); continue; } } final NodeList textNodes = keyElement.getElementsByTagName( "text" ); //$NON-NLS-1$ if ( textNodes.getLength() != 0 ) { final TextConfigDescriptionEntry textEntry = new TextConfigDescriptionEntry( keyName ); textEntry.setDescription( descr ); textEntry.setGlobal( keyGlobal ); textEntry.setHidden( keyHidden ); add( textEntry ); } } } /** * A parser helper method which collects all enumeration entries from the given element. * * @param element the element from where to read the enumeration entries. * @return the entries as string array. */ private String[] collectEnumEntries( final Element element ) { final NodeList nl = element.getElementsByTagName( "text" ); //$NON-NLS-1$ final String[] retval = new String[ nl.getLength() ]; for ( int i = 0; i < nl.getLength(); i++ ) { retval[ i ] = DOMUtilities.getText( (Element) nl.item( i ) ).trim(); } return retval; } /** * A parser helper method that returns the CDATA description of the given element. * * @param e the element from where to read the description. * @return the description text. */ private String getDescription( final Element e ) { final NodeList descr = e.getElementsByTagName( "description" ); //$NON-NLS-1$ if ( descr.getLength() == 0 ) { return ""; //$NON-NLS-1$ } return DOMUtilities.getText( (Element) descr.item( 0 ) ); } /** * Saves the model into an xml file. * * @param out the target output stream. * @param encoding the encoding of the content. * @throws IOException if an error occurs. * @noinspection IOResourceOpenedButNotSafelyClosed */ public void save( final OutputStream out, final String encoding ) throws IOException { // This print-writer will be flushed, but not closed, as closing the underlying stream is not desired here. final PrintWriter writer = new PrintWriter( new OutputStreamWriter( out, encoding ) ); final AttributeList attList = new AttributeList(); attList.addNamespaceDeclaration( "", ConfigEditorBoot.NAMESPACE ); //$NON-NLS-1$ final DefaultTagDescription tagDescription = new DefaultTagDescription(); tagDescription.setDefaultNamespace( ConfigEditorBoot.NAMESPACE ); tagDescription.setNamespaceHasCData( ConfigEditorBoot.NAMESPACE, false ); tagDescription.setElementHasCData( ConfigEditorBoot.NAMESPACE, "text", true ); tagDescription.setElementHasCData( ConfigEditorBoot.NAMESPACE, "description", true ); final XmlWriter dwriter = new XmlWriter( writer, tagDescription ); dwriter.writeXmlDeclaration( encoding ); dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "config-description", attList, XmlWriterSupport.OPEN ); //$NON-NLS-1$ final CharacterEntityParser parser = CharacterEntityParser.createXMLEntityParser(); for ( int i = 0; i < getSize(); i++ ) { final ConfigDescriptionEntry entry = get( i ); final AttributeList p = new AttributeList(); p.setAttribute( ConfigEditorBoot.NAMESPACE, "name", entry.getKeyName() ); //$NON-NLS-1$ p.setAttribute( ConfigEditorBoot.NAMESPACE, "global", String.valueOf( entry.isGlobal() ) ); //$NON-NLS-1$ p.setAttribute( ConfigEditorBoot.NAMESPACE, "hidden", String.valueOf( entry.isHidden() ) ); //$NON-NLS-1$ dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "key", p, XmlWriterSupport.OPEN ); //$NON-NLS-1$ if ( entry.getDescription() != null ) { dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "description", XmlWriterSupport.OPEN ); //$NON-NLS-1$ writer.write( parser.encodeEntities( entry.getDescription() ) ); dwriter.writeCloseTag(); } if ( entry instanceof ClassConfigDescriptionEntry ) { final ClassConfigDescriptionEntry ce = (ClassConfigDescriptionEntry) entry; if ( ce.getBaseClass() != null ) { dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "class", "instanceof", //$NON-NLS-1$ //$NON-NLS-2$ ce.getBaseClass().getName(), XmlWriterSupport.CLOSE ); } else { dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "class", "instanceof", //$NON-NLS-1$ //$NON-NLS-2$ "java.lang.Object", XmlWriterSupport.CLOSE ); //$NON-NLS-1$ } } else if ( entry instanceof TextConfigDescriptionEntry ) { dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "text", //$NON-NLS-1$ new AttributeList(), XmlWriterSupport.CLOSE ); } else if ( entry instanceof EnumConfigDescriptionEntry ) { final EnumConfigDescriptionEntry en = (EnumConfigDescriptionEntry) entry; dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "enum", XmlWriterSupport.OPEN ); //$NON-NLS-1$ final String[] alts = en.getOptions(); if ( alts != null ) { for ( int optCount = 0; optCount < alts.length; optCount++ ) { dwriter.writeTag( ConfigEditorBoot.NAMESPACE, "text", XmlWriterSupport.OPEN ); //$NON-NLS-1$ dwriter.writeTextNormalized( alts[ optCount ], false ); dwriter.writeCloseTag(); } } dwriter.writeCloseTag(); } dwriter.writeCloseTag(); } dwriter.writeCloseTag(); writer.flush(); } }