/*!
* 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.layout.output;
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.DataRow;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.Section;
import org.pentaho.reporting.engine.classic.core.metadata.AttributeMetaData;
import org.pentaho.reporting.engine.classic.core.metadata.ElementMetaData;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class ElementChangeChecker {
private static class NeedEvalResult implements Serializable {
private boolean needToRun;
private long changeTracker;
private long styleChangeTracker;
private long styleModificationCounter;
private HashMap<String, Object> fieldsAndValues;
private NeedEvalResult( final boolean needToRun, final ReportElement e,
final HashMap<String, Object> fieldsAndValues ) {
this.needToRun = needToRun;
this.changeTracker = e.getChangeTracker();
this.fieldsAndValues = fieldsAndValues;
this.styleChangeTracker = e.getStyle().getChangeTracker();
this.styleModificationCounter = e.getStyle().getModificationCount();
}
public boolean isNeedToRun() {
return needToRun;
}
public long getChangeTracker() {
return changeTracker;
}
public boolean isValid( final ReportElement e, final DataRow dataRow ) {
if ( changeTracker != e.getChangeTracker() || styleChangeTracker != e.getStyle().getChangeTracker()
|| styleModificationCounter != e.getStyle().getModificationCount() ) {
return false;
}
for ( final Map.Entry<String, Object> entry : fieldsAndValues.entrySet() ) {
final String field = entry.getKey();
final Object oldValue = entry.getValue();
final Object currentValue = dataRow.get( field );
if ( ObjectUtilities.equal( oldValue, currentValue ) == false ) {
return false;
}
}
return true;
}
}
private static class ElementMetaDataEvaluationResult implements Serializable {
private long changeTracker;
private long styleChangeTracker;
private long styleModificationCounter;
private HashMap<String, Object> seenFields;
private ElementMetaDataEvaluationResult( final ReportElement e, final HashMap<String, Object> seenFields ) {
this.seenFields = seenFields;
changeTracker = e.getChangeTracker();
styleChangeTracker = e.getStyle().getChangeTracker();
styleModificationCounter = e.getStyle().getModificationCount();
}
public boolean isValid( final ReportElement e, final DataRow dataRow ) {
if ( changeTracker != e.getChangeTracker() || styleChangeTracker != e.getStyle().getChangeTracker()
|| styleModificationCounter != e.getStyle().getModificationCount() ) {
return false;
}
for ( final Map.Entry<String, Object> entry : seenFields.entrySet() ) {
final String field = entry.getKey();
final Object oldValue = entry.getValue();
final Object currentValue = dataRow.get( field );
if ( ObjectUtilities.equal( oldValue, currentValue ) == false ) {
return false;
}
}
return true;
}
}
private static class PerformanceCollector {
public int totalEvaluations;
public int evaluations;
public int skippedEvaluations;
}
private final Log performanceLogger = LogFactory.getLog( getClass() );
private PerformanceCollector performanceCollector;
private String attrName;
private String elementAttribute;
private DataRow currentDataRow;
private HashMap<String, Object> currentFieldsAndValues;
public ElementChangeChecker() {
performanceCollector = new PerformanceCollector();
attrName = "ElementChangeTracker-NeedResult@" + System.identityHashCode( this );
elementAttribute = "ElementChangeTracker-DetailResult@" + System.identityHashCode( this );
currentFieldsAndValues = new HashMap<String, Object>();
}
public boolean isBandChanged( final Section b, final DataRow dataRow ) {
this.currentFieldsAndValues.clear();
this.currentDataRow = dataRow;
try {
return processRootBand( b );
} finally {
this.currentFieldsAndValues.clear();
this.currentDataRow = null;
}
}
/**
* Evaluates all style expressions from all elements and updates the style-sheet if needed.
*
* @param b
* the band.
* @return true if the element needs reprinting.
*/
protected final boolean processRootBand( final Section b ) {
if ( b == null ) {
return false;
}
final NeedEvalResult needToRun = (NeedEvalResult) b.getAttribute( AttributeNames.Internal.NAMESPACE, attrName );
if ( needToRun != null ) {
if ( needToRun.isNeedToRun() == false ) {
if ( needToRun.isValid( b, currentDataRow ) ) {
recordCacheHit( b );
return false;
}
}
}
recordCacheMiss( b );
final boolean needToRunVal = processBand( b );
b.setAttribute( AttributeNames.Internal.NAMESPACE, attrName, new NeedEvalResult( needToRunVal, b,
(HashMap<String, Object>) currentFieldsAndValues.clone() ), false );
return true;
}
protected boolean evaluateElement( final ReportElement e ) {
final ElementMetaDataEvaluationResult evalResult =
(ElementMetaDataEvaluationResult) e.getAttribute( AttributeNames.Internal.NAMESPACE, elementAttribute );
if ( evalResult != null && evalResult.isValid( e, currentDataRow ) ) {
currentFieldsAndValues.putAll( evalResult.seenFields );
return false;
}
final HashMap<String, Object> values = new HashMap<String, Object>();
final ElementMetaData metaData = e.getElementType().getMetaData();
final AttributeMetaData[] attributeDescriptions = metaData.getAttributeDescriptions();
for ( int i = 0; i < attributeDescriptions.length; i++ ) {
final AttributeMetaData attributeDescription = attributeDescriptions[i];
final Object attribute = e.getAttribute( attributeDescription.getNameSpace(), attributeDescription.getName() );
if ( attribute == null ) {
continue;
}
final String[] referencedFields = attributeDescription.getReferencedFields( e, attribute );
for ( int j = 0; j < referencedFields.length; j++ ) {
final String field = referencedFields[j];
final Object value = currentDataRow.get( field );
values.put( field, value );
currentFieldsAndValues.put( field, value );
}
}
final ElementMetaDataEvaluationResult current = new ElementMetaDataEvaluationResult( e, values );
e.setAttribute( AttributeNames.Internal.NAMESPACE, elementAttribute, current, false );
return true;
}
protected final boolean processBand( final Section b ) {
boolean hasAttrExpressions = evaluateElement( b );
final int length = b.getElementCount();
for ( int i = 0; i < length; i++ ) {
final Element element = b.getElement( i );
final ElementMetaData.TypeClassification reportElementType = element.getMetaData().getReportElementType();
if ( reportElementType == ElementMetaData.TypeClassification.DATA
|| reportElementType == ElementMetaData.TypeClassification.CONTROL
|| reportElementType == ElementMetaData.TypeClassification.SUBREPORT || element instanceof Section == false ) {
if ( evaluateElement( element ) ) {
hasAttrExpressions = true;
}
} else {
final Section section = (Section) element;
if ( processBand( section ) ) {
hasAttrExpressions = true;
}
}
}
return hasAttrExpressions;
}
protected void recordCacheHit( final ReportElement e ) {
performanceCollector.totalEvaluations += 1;
performanceCollector.skippedEvaluations += 1;
}
protected void recordCacheMiss( final ReportElement e ) {
performanceCollector.totalEvaluations += 1;
performanceCollector.evaluations += 1;
}
protected void reportCachePerformance() {
if ( performanceLogger.isInfoEnabled() ) {
performanceLogger.info( String.format( "Performance: %s => total=%d, evaluated=%d (%f%%), avoided=%d (%f%%)",
getClass(), performanceCollector.totalEvaluations, performanceCollector.evaluations, 100f
* performanceCollector.evaluations / Math.max( 1.0f, performanceCollector.totalEvaluations ),
performanceCollector.skippedEvaluations, 100f * performanceCollector.skippedEvaluations
/ Math.max( 1.0f, performanceCollector.totalEvaluations ) ) );
}
}
}