/*
* 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.style;
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.config.Configuration;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* A style key represents a (key, class) pair. Style keys are used to access style attributes defined in a
* <code>BandStyleSheet</code> or an <code>ElementStyleSheet</code>
* <p/>
* Note that this class also defines a static Hashtable in which all defined keys are stored.
*
* @author Thomas Morgner
* @see BandStyleKeys
* @see ElementStyleSheet
*/
public final class StyleKey implements Serializable, Cloneable {
private static final Log logger = LogFactory.getLog( StyleKey.class );
/**
* Shared storage for the defined keys.
*/
private static HashMap definedKeys;
private static int definedKeySize;
private static StyleKey[] definedKeysArray;
private static List<StyleKey> definedKeysList;
private static boolean locked;
/**
* The name of the style key.
*/
public final String name;
/**
* The class of the value.
*/
private Class valueType;
/**
* A unique int-key for the stylekey.
*/
public final int identifier;
/**
* Whether this stylekey is transient. Transient keys will not be written when serializing a report.
*/
private boolean trans;
/**
* Whether this stylekey is inheritable.
*/
private boolean inheritable;
/**
* Creates a new style key.
*
* @param name
* the name (never null).
* @param valueType
* the class of the value for this key (never null).
* @param inheritable
* a flag indicating whether the value will be inherited from parent bands to child elements.
* @param trans
* a flag indicating whether the style property should be saved. Transient properties are temporary artifacts
* and should not be stored in report definitions.
*/
private StyleKey( final String name, final Class valueType, final boolean trans, final boolean inheritable ) {
if ( name == null ) {
throw new NullPointerException( "StyleKey.setName(...): null not permitted." );
}
if ( valueType == null ) {
throw new NullPointerException( "ValueType must not be null" );
}
this.valueType = valueType;
this.name = name;
this.identifier = StyleKey.definedKeys.size();
this.trans = trans;
this.inheritable = inheritable;
}
/**
* Returns the name of the key.
*
* @return the name.
*/
public String getName() {
return name;
}
/**
* Returns the class of the value for this key.
*
* @return the class.
*/
public Class<?> getValueType() {
return valueType;
}
/**
* Returns the key with the specified name. The given type is not checked against a possibly alredy defined
* definition, it is assumed that the type is only given for a new key definition.
*
* @param name
* the name.
* @param valueType
* the class.
* @return the style key.
*/
public static StyleKey getStyleKey( final String name, final Class valueType ) {
return getStyleKey( name, valueType, false, true );
}
/**
* Returns the key with the specified name. The given type is not checked against a possibly alredy defined
* definition, it is assumed that the type is only given for a new key definition.
*
* @param name
* the name.
* @param valueType
* the class.
* @param inheritable
* a flag indicating whether the value will be inherited from parent bands to child elements.
* @param trans
* a flag indicating whether the style property should be saved. Transient properties are temporary artifacts
* and should not be stored in report definitions.
* @return the style key.
*/
public static synchronized StyleKey getStyleKey( final String name, final Class valueType, final boolean trans,
final boolean inheritable ) {
if ( locked ) {
throw new IllegalStateException( "StyleKeys have been locked after booting was completed." );
}
if ( definedKeys == null ) {
definedKeys = new HashMap();
definedKeySize = 0;
}
StyleKey key = (StyleKey) definedKeys.get( name );
if ( key == null ) {
key = new StyleKey( name, valueType, trans, inheritable );
definedKeys.put( name, key );
definedKeySize = definedKeys.size();
definedKeysArray = null;
definedKeysList = null;
}
return key;
}
public static synchronized void lock() {
locked = true;
}
/**
* Returns the key with the specified name.
*
* @param name
* the name.
* @return the style key.
*/
public static synchronized StyleKey getStyleKey( final String name ) {
if ( definedKeys == null ) {
return null;
} else {
return (StyleKey) definedKeys.get( name );
}
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param o
* the reference object with which to compare.
* @return <code>true</code> if this object is the same as the obj argument; <code>false</code> otherwise.
*/
public boolean equals( final Object o ) {
if ( this == o ) {
return true;
}
if ( !( o instanceof StyleKey ) ) {
return false;
}
final StyleKey key = (StyleKey) o;
if ( !name.equals( key.name ) ) {
return false;
}
if ( !valueType.equals( key.valueType ) ) {
return false;
}
return true;
}
/**
* Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those
* provided by <code>java.util.Hashtable</code>.
* <p/>
*
* @return a hash code value for this object.
*/
public int hashCode() {
return identifier;
}
/**
* Replaces the automaticly generated instance with one of the defined stylekey instances or creates a new stylekey.
*
* @return the resolved element
* @throws ObjectStreamException
* if the element could not be resolved.
*/
private Object readResolve() throws ObjectStreamException {
synchronized ( StyleKey.class ) {
final StyleKey key = StyleKey.getStyleKey( name );
if ( key != null ) {
return key;
}
return StyleKey.getStyleKey( name, valueType, trans, inheritable );
}
}
public boolean isTransient() {
return trans;
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object.
*/
public String toString() {
final StringBuffer b = new StringBuffer( 100 );
b.append( "StyleKey={name='" );
b.append( getName() );
b.append( "', valueType='" );
b.append( getValueType() );
b.append( "'}" );
return b.toString();
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public boolean isInheritable() {
return inheritable;
}
public int getIdentifier() {
return identifier;
}
public static int getDefinedStyleKeyCount() {
return definedKeySize;
}
public static synchronized List<StyleKey> getDefinedStyleKeysList() {
if ( definedKeysList != null ) {
return definedKeysList;
}
definedKeysList = Collections.unmodifiableList( Arrays.asList( getDefinedStyleKeys() ) );
return definedKeysList;
}
public static synchronized StyleKey[] getDefinedStyleKeys() {
if ( definedKeys == null ) {
throw new IllegalStateException(
"The engine has not been booted and the default keys have no been registered yet." );
}
if ( definedKeysArray != null ) {
assertNoNullEntries();
return definedKeysArray.clone();
}
final StyleKey[] keys = (StyleKey[]) definedKeys.values().toArray( new StyleKey[definedKeys.size()] );
definedKeysArray = keys.clone();
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey key = keys[i];
definedKeysArray[key.identifier] = key;
}
assertNoNullEntries();
return definedKeysArray.clone();
}
public static void assertNoNullEntries() {
for ( int i = 0; i < definedKeysArray.length; i++ ) {
final StyleKey styleKey = definedKeysArray[i];
if ( styleKey == null ) {
throw new NullPointerException();
}
}
}
/**
* @noinspection ProhibitedExceptionCaught
*/
public static synchronized void registerDefaults() {
final Configuration config = ClassicEngineBoot.getInstance().getGlobalConfig();
final Iterator it = config.findPropertyKeys( "org.pentaho.reporting.engine.classic.core.stylekeys." );
final ClassLoader classLoader = ObjectUtilities.getClassLoader( StyleKey.class );
while ( it.hasNext() ) {
final String key = (String) it.next();
final String keyClass = config.getConfigProperty( key );
try {
final Class c = Class.forName( keyClass, false, classLoader );
registerClass( c );
} catch ( ClassNotFoundException e ) {
// ignore that class
logger.warn( "Unable to register keys from " + keyClass );
} catch ( NullPointerException e ) {
// ignore invalid values as well.
logger.warn( "Unable to register keys from " + keyClass );
}
}
}
public static synchronized void registerClass( final Class c ) {
// Log.debug ("Registering stylekeys from " + c);
try {
final Field[] fields = c.getFields();
for ( int i = 0; i < fields.length; i++ ) {
final Field field = fields[i];
final int modifiers = field.getModifiers();
if ( Modifier.isPublic( modifiers ) && Modifier.isStatic( modifiers ) ) {
if ( Modifier.isFinal( modifiers ) == false ) {
logger.warn( "Invalid implementation: StyleKeys should be 'public static final': " + c );
}
if ( field.getType().isAssignableFrom( StyleKey.class ) ) {
// noinspection UnusedDeclaration
final StyleKey value = (StyleKey) field.get( null );
// ignore the returned value, all we want is to trigger the key
// creation
// Log.debug ("Loaded key " + value);
}
}
}
} catch ( IllegalAccessException e ) {
// wont happen, we've checked it..
logger.warn( "Unable to register keys from " + c.getName() );
}
}
@Deprecated // for tests only!
static synchronized StyleKey addTestKey( String name, Class valueType, boolean trans, boolean inheritable ) {
boolean wasLocked = locked;
try {
locked = false;
return getStyleKey( name, valueType, trans, inheritable );
} finally {
locked = wasLocked;
definedKeysArray = null;
definedKeysList = null;
}
}
@Deprecated // for tests only!
static synchronized void removeTestKey( String name ) {
try {
if ( definedKeys != null ) {
definedKeys.remove( name );
}
definedKeySize = definedKeys.size();
} finally {
definedKeysArray = null;
definedKeysList = null;
}
}
}