/*
* 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 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.style;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
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;
/**
* 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 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;
}
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 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());
}
}
}