/*!
* 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) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.function.sys;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.ReportDefinition;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.event.ReportEvent;
import org.pentaho.reporting.engine.classic.core.function.AbstractElementFormatFunction;
import org.pentaho.reporting.engine.classic.core.function.FunctionUtilities;
import org.pentaho.reporting.engine.classic.core.function.ProcessingContext;
import org.pentaho.reporting.engine.classic.core.function.StructureFunction;
import org.pentaho.reporting.engine.classic.core.layout.style.DefaultStyleCache;
import org.pentaho.reporting.engine.classic.core.layout.style.StyleCache;
import org.pentaho.reporting.engine.classic.core.states.ReportState;
import org.pentaho.reporting.engine.classic.core.states.process.ReportProcessStore;
import org.pentaho.reporting.engine.classic.core.style.ResolverStyleSheet;
import org.pentaho.reporting.engine.classic.core.style.css.CSSStyleResolver;
import org.pentaho.reporting.engine.classic.core.style.resolver.StyleResolver;
import org.pentaho.reporting.engine.classic.core.util.DoubleKeyedCounter;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.libraries.base.config.ExtendedConfiguration;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
public class StyleResolvingEvaluator extends AbstractElementFormatFunction implements StructureFunction {
private static class StyleResolverCacheEntry implements Serializable {
private long elementChangeTracker;
private long styleChangeHash;
private long styleModificationCount;
private StyleResolverCacheEntry( final long elementChangeTracker,
final long styleChangeHash,
final long styleModificationCount ) {
this.elementChangeTracker = elementChangeTracker;
this.styleChangeHash = styleChangeHash;
this.styleModificationCount = styleModificationCount;
}
public StyleResolverCacheEntry( final StyleResolverCacheEntry parentEntry, final ReportElement e ) {
if ( parentEntry == null ) {
this.elementChangeTracker = e.getChangeTracker();
this.styleChangeHash = e.getStyle().getChangeTrackerHash();
this.styleModificationCount = e.getStyle().getModificationCount();
} else {
this.elementChangeTracker = parentEntry.getElementChangeTracker() * 31 + e.getChangeTracker();
this.styleChangeHash = parentEntry.getStyleChangeHash() * 31 + e.getStyle().getChangeTrackerHash();
this.styleModificationCount =
parentEntry.getStyleModificationCount() * 31 + e.getStyle().getModificationCount();
}
}
public long getElementChangeTracker() {
return elementChangeTracker;
}
public long getStyleChangeHash() {
return styleChangeHash;
}
public long getStyleModificationCount() {
return styleModificationCount;
}
public boolean equals( final Object o ) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
final StyleResolverCacheEntry that = (StyleResolverCacheEntry) o;
if ( elementChangeTracker != that.elementChangeTracker ) {
return false;
}
if ( styleChangeHash != that.styleChangeHash ) {
return false;
}
if ( styleModificationCount != that.styleModificationCount ) {
return false;
}
return true;
}
public int hashCode() {
int result = (int) ( elementChangeTracker ^ ( elementChangeTracker >>> 32 ) );
result = 31 * result + (int) ( styleChangeHash ^ ( styleChangeHash >>> 32 ) );
result = 31 * result + (int) ( styleModificationCount ^ ( styleModificationCount >>> 32 ) );
return result;
}
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append( "StyleResolverCacheEntry" );
sb.append( "{elementChangeTracker=" ).append( elementChangeTracker );
sb.append( ", styleChangeHash=" ).append( styleChangeHash );
sb.append( ", styleModificationCount=" ).append( styleModificationCount );
sb.append( '}' );
return sb.toString();
}
}
private static final Log logger = LogFactory.getLog( StyleResolvingEvaluator.class );
private static final StyleResolverCacheEntry INVALID = new StyleResolverCacheEntry( 0, 0, 0 );
private static final String DETAILED_STATISTICS_CONFIG =
StyleResolvingEvaluator.class.getName() + ".CollectDetailedStatistics";
private transient StyleResolver resolver;
private transient ResolverStyleSheet styleSheet;
private transient StyleCache styleCache;
private DoubleKeyedCounter<String, Long> statisticsHit;
private DoubleKeyedCounter<String, Long> statisticsMiss;
private boolean collectDetailedStatistics;
public StyleResolvingEvaluator() {
statisticsHit = new DoubleKeyedCounter<String, Long>();
statisticsMiss = new DoubleKeyedCounter<String, Long>();
ExtendedConfiguration config = ClassicEngineBoot.getInstance().getExtendedConfig();
collectDetailedStatistics = config.getBoolProperty( DETAILED_STATISTICS_CONFIG );
}
public void reportInitialized( final ReportEvent event ) {
if ( FunctionUtilities.isLayoutLevel( event ) == false ) {
// dont do anything if there is no printing done ...
return;
}
ReportDefinition reportDefinition = locateMasterReport( event.getState() );
resolver = createStyleResolver( reportDefinition,
getRuntime().getProcessingContext(), event.getState().getProcessStore() );
styleCache = createCache( event.getReport(), event.getState().getProcessStore() );
styleSheet = createSharedResolverStyleSheet( reportDefinition, event.getState().getProcessStore() );
super.reportInitialized( event );
}
/**
* The resolver style sheet is a temporary, reused style sheet. We always create a copy at the
* end, and thus this object can be safely shared across StyleResolvingEvaluator instances across
* the same report process.
*
* @return the shared resolver stylesheet.
*/
private ResolverStyleSheet createSharedResolverStyleSheet( ReportDefinition report,
ReportProcessStore store ) {
Map<InstanceID, ResolverStyleSheet> cache = store.get( StyleResolvingEvaluator.class.getName() + "#ResolverStyleSheet" );
ResolverStyleSheet c = cache.get( report.getObjectID() );
if ( c == null ) {
c = new ResolverStyleSheet();
cache.put( report.getObjectID(), c );
}
return c;
}
/**
* The style cache is stored per defined (sub)report. This keeps similar report elements together,
* without creating too many parallel cache instances when the same subreports get instantiated over
* and over again.
*
* @return the cache for this report.
*/
private StyleCache createCache( ReportDefinition report, ReportProcessStore store ) {
Map<InstanceID, StyleCache> cache = store.get( StyleResolvingEvaluator.class.getName() + "#Cache" );
StyleCache c = cache.get( report.getObjectID() );
if ( c == null ) {
c = new DefaultStyleCache( "StyleResolver" );
cache.put( report.getObjectID(), c );
}
return c;
}
private ReportDefinition locateMasterReport( final ReportState state ) {
if ( state.isSubReportEvent() ) {
ReportState parentState = state.getParentState();
if ( parentState != null ) {
return locateMasterReport( parentState );
}
}
return state.getReport();
}
/**
* Returns a potentially shared instance (per report process, thus thread-safe) of the style resolver.
* The stylesheets are defined on the master-report level, and thus only need to be loaded once. Subreports
* that want to resolve styles can all share the same definitions.
*
* @param reportDefinition
* @param pc
* @param store
* @return
*/
private StyleResolver createStyleResolver( final ReportDefinition reportDefinition,
final ProcessingContext pc,
final ReportProcessStore store ) {
Map<InstanceID, StyleResolver> cache = store.get( StyleResolvingEvaluator.class.getName() + "#Resolver" );
StyleResolver o = cache.get( reportDefinition.getObjectID() );
if ( o != null ) {
return o;
}
final ResourceManager resourceManager = pc.getResourceManager();
final ResourceKey contentBase = pc.getContentBase();
final StyleResolver res =
CSSStyleResolver.createDesignTimeResolver( reportDefinition, resourceManager, contentBase, false );
cache.put( reportDefinition.getObjectID(), res );
return res;
}
protected void recordCacheHit( final ReportElement e ) {
super.recordCacheHit( e );
if ( collectDetailedStatistics ) {
statisticsHit.increaseCounter( e.getElementType().getMetaData().getName(), e.getChangeTracker() );
}
}
protected void recordCacheMiss( final ReportElement e ) {
super.recordCacheMiss( e );
if ( collectDetailedStatistics ) {
statisticsMiss.increaseCounter( e.getElementType().getMetaData().getName(), e.getChangeTracker() );
}
}
protected void reportCachePerformance() {
super.reportCachePerformance();
if ( collectDetailedStatistics ) {
logger.debug( statisticsHit.printStatistic() + "\n" + statisticsMiss.printStatistic() );
}
}
protected boolean evaluateElement( final ReportElement e ) {
final StyleResolverCacheEntry parentEntry = get( e.getParentSection() );
final StyleResolverCacheEntry existingEntry = get( e );
final StyleResolverCacheEntry currentEntry = new StyleResolverCacheEntry( parentEntry, e );
if ( currentEntry.equals( existingEntry ) ) {
return false;
}
try {
e.setAttribute( AttributeNames.Internal.NAMESPACE, "style-resolver-change-tracker", currentEntry, false );
resolver.resolve( e, styleSheet );
e.setComputedStyle( styleCache.getStyleSheet( styleSheet ) );
return true;
} catch ( Exception ex ) {
throw new InvalidReportStateException( "Failed to resolve style.", ex );
}
}
private StyleResolverCacheEntry get( final ReportElement element ) {
if ( element == null ) {
return null;
}
final Object attribute = element.getAttribute( AttributeNames.Internal.NAMESPACE, "style-resolver-change-tracker" );
final StyleResolverCacheEntry cacheEntry;
if ( attribute instanceof StyleResolverCacheEntry ) {
cacheEntry = (StyleResolverCacheEntry) attribute;
} else {
cacheEntry = null;
}
return cacheEntry;
}
public int getProcessingPriority() {
// run after the style-expressions have been evaluated. Hard-coded styles on the element have the effect
// of a style-attribute in HTML - they override everything that has been resolved elsewhere.
return 50000;
}
public void reportDone( final ReportEvent event ) {
if ( FunctionUtilities.isLayoutLevel( event ) == false ) {
// dont do anything if there is no printing done ...
return;
}
super.reportDone( event );
// logger.info(styleCache.printPerformanceStats() + "\n" + resolver.toString());
}
/**
* Helper method for serialization.
*
* @param in the input stream from where to read the serialized object.
* @throws java.io.IOException when reading the stream fails.
* @throws ClassNotFoundException if a class definition for a serialized object could not be found.
*/
private void readObject( final ObjectInputStream in )
throws IOException, ClassNotFoundException {
in.defaultReadObject();
}
public StyleResolvingEvaluator getInstance() {
final StyleResolvingEvaluator expression = (StyleResolvingEvaluator) super.getInstance();
expression.statisticsHit = new DoubleKeyedCounter<String, Long>();
expression.statisticsMiss = new DoubleKeyedCounter<String, Long>();
return expression;
}
}