/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2005 - 2009 Works, Inc. All rights reserved.
* http://www.works.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Licensed to Jaspersoft Corporation under a Contributer Agreement
*/
package net.sf.jasperreports.engine.base;
import java.awt.Graphics2D;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import net.sf.jasperreports.engine.JRConstants;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintImage;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRRenderable;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRVirtualizable;
import net.sf.jasperreports.engine.JRVirtualizationHelper;
import net.sf.jasperreports.engine.JRVirtualizer;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.fill.JRTemplateElement;
import net.sf.jasperreports.engine.fill.JRTemplatePrintElement;
import net.sf.jasperreports.engine.fill.JRVirtualizationContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A print page that can be virtualized to free heap memory.
*
* @author John Bindel
* @version $Id: JRVirtualPrintPage.java 3717 2010-04-09 10:01:33Z teodord $
*/
public class JRVirtualPrintPage implements JRPrintPage, JRVirtualizable, Serializable
{
protected static final Log log = LogFactory.getLog(JRVirtualPrintPage.class);
/**
* Identity objects are those that we want to replace when we devirtualize
* data. If object A was virtualized, and it is referenced outside the
* virtualized data, then we want to replace those references with object
* A', which is the version of the object that has been devirtualized. For
* example the Serialization mechanism creates a new version of the
* TextElement we want to be filled, but the bound object map references the
* original object A until we replace it with the new version A'.
*/
public static class ObjectIDPair implements Serializable {
/**
*
*/
private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
private final Object o;
private final int id;
public ObjectIDPair(Object o) {
this.o = o;
this.id = System.identityHashCode(o);
}
/**
* Gets the object.
*/
public Object getObject() {
return o;
}
/**
* Gets the identity of the object. The identity is the current object's
* identity hash code before we deserialize, but when we have
* deserialized it, the identity is that of the object that was
* serialized, not that of the newly deserialized object.
*/
public int getIdentity() {
return id;
}
}
/**
* Classes that want to deal with the identity data should implement this.
* The JRBaseFiller needs to do this.
*/
public static interface IdentityDataProvider {
/**
* Get identity data that the provider later want to handle when the
* virtual object is paged in.
*/
ObjectIDPair[] getIdentityData(JRVirtualPrintPage page);
/**
* Handle the identity data as necessary.
*/
void setIdentityData(JRVirtualPrintPage page,
ObjectIDPair[] identityData);
}
private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
private static final Random random = new Random(System.currentTimeMillis());
private static short counter = 1;
protected List elements = new ArrayList();
/**
* A unique identifier that is useful for serialization and deserialization
* to some persistence mechanism.
*/
private String uid;
/**
* The object that does the virtualization work.
*/
private transient JRVirtualizer virtualizer;
/**
* The filler object which has our identity data.
*/
private transient IdentityDataProvider[] identityProviders;
protected JRVirtualizationContext virtualizationContext;
/**
* Constructs a virtualizable page.
*/
public JRVirtualPrintPage(JasperPrint printObject, JRVirtualizer virtualizer, JRVirtualizationContext virtualizationContext) {
super();
this.virtualizationContext = virtualizationContext;
this.uid = makeUID(printObject);
this.virtualizer = virtualizer;
this.identityProviders = null;
if (virtualizer != null) {
virtualizer.registerObject(this);
}
}
/**
* Make some unique identifier for this object.
*/
private static String makeUID(JasperPrint printObject) {
synchronized (random) {
return Integer.toString(System.identityHashCode(printObject)) + "_"
+ (printObject.getPages().size()) + "_"
+ Integer.toString(counter++) + "_"
+ Integer.toString(random.nextInt());
}
}
/**
* Make a new identifier for an object.
*
* @return the new identifier
*/
private static String makeUID(JRVirtualPrintPage page) {
synchronized (random) {
return Integer.toString(System.identityHashCode(page)) + "_"
+ Integer.toString(counter++) + "_"
+ Integer.toString(random.nextInt());
}
}
public final String getUID() {
return this.uid;
}
public void setVirtualData(Object o)
{
elements = (List) o;
}
public Object getVirtualData()
{
return elements;
}
public void removeVirtualData() {
elements = null;
}
public void setIdentityData(Object o) {
if (identityProviders != null) {
for (int i = 0; i < identityProviders.length; ++i) {
identityProviders[i].setIdentityData(this, (ObjectIDPair[]) o);
}
}
}
public Object getIdentityData() {
ObjectIDPair[] data;
if (identityProviders != null) {
if (identityProviders.length == 1) {
data = identityProviders[0].getIdentityData(this);
} else if (identityProviders.length > 1) {
Set list = new HashSet();
for (int i = 0; i < identityProviders.length; ++i) {
ObjectIDPair[] pairs = identityProviders[i]
.getIdentityData(this);
if (pairs != null) {
for (int j = 0; j < pairs.length; ++j) {
list.add(pairs[j]);
}
}
}
data = (ObjectIDPair[]) list.toArray(new ObjectIDPair[list
.size()]);
} else {
data = null;
}
} else {
data = null;
}
return data;
}
public boolean isVirtualized() {
return elements == null;
}
/**
* Sets the virtualizer.
*/
public void setVirtualizer(JRVirtualizer virtualizer) {
this.virtualizer = virtualizer;
}
/**
* Gets the virtualizer.
*/
public JRVirtualizer getVirtualizer() {
return this.virtualizer;
}
public void addIdentityDataProvider(IdentityDataProvider p) {
if (identityProviders == null) {
identityProviders = new IdentityDataProvider[] { p };
} else {
IdentityDataProvider[] newList = new IdentityDataProvider[identityProviders.length + 1];
System.arraycopy(identityProviders, 0, newList, 0,
identityProviders.length);
newList[identityProviders.length] = p;
identityProviders = newList;
}
}
public void removeIdentityDataProvider(IdentityDataProvider p) {
if (identityProviders != null) {
int idx;
for (idx = 0; idx < identityProviders.length; ++idx) {
if (identityProviders[idx] == p) {
IdentityDataProvider[] newList = new IdentityDataProvider[identityProviders.length - 1];
System.arraycopy(identityProviders, 0, newList, 0, idx);
int remaining = identityProviders.length - idx - 1;
if (remaining > 0) {
System.arraycopy(identityProviders, idx + 1, newList,
idx, remaining);
}
identityProviders = newList;
break;
}
}
}
}
public List getElements()
{
ensureVirtualData();
return elements;
}
protected void ensureVirtualData()
{
if (this.virtualizer != null)
{
this.virtualizer.requestData(this);
}
}
public void setElements(List elements) {
cleanVirtualData();
this.elements = elements;
cacheInContext(this.elements);
}
protected void cleanVirtualData()
{
if (this.virtualizer != null)
{
this.virtualizer.clearData(this);
}
}
public void addElement(JRPrintElement element)
{
ensureVirtualData();
elements.add(element);
cacheInContext(element);
}
/**
* Dummy image renderer that only stores the ID of a cached renderer.
* When a page gets serialized, all image renderers that are cached in the
* virtualization context are replaced with dummy renderers that only store the ID.
* When a page gets deserialized, the original renderers are restored from the
* virtualization context based on the ID.
*/
protected static class JRIdHolderRenderer implements JRRenderable, Serializable
{
private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
protected final String id;
protected JRIdHolderRenderer(JRRenderable renderer)
{
this.id = renderer.getId();
}
public String getId()
{
return id;
}
public byte getType()
{
return TYPE_IMAGE;
}
public byte getImageType()
{
return IMAGE_TYPE_UNKNOWN;
}
public Dimension2D getDimension() throws JRException
{
return null;
}
public byte[] getImageData() throws JRException
{
return null;
}
public void render(Graphics2D grx, Rectangle2D rectanle) throws JRException
{
}
}
protected static class JRIdHolderTemplateElement extends JRTemplateElement
{
private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
protected JRIdHolderTemplateElement(String id)
{
super(id);
}
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
{
// we do not use the original ID as the source object might still be
// alive in the JVM
String oldUid = (String) in.readObject();
uid = makeUID(this);
if (log.isDebugEnabled())
{
log.debug("Original uid " + oldUid + "; new uid " + uid);
}
virtualizationContext = (JRVirtualizationContext) in.readObject();
int length = in.readInt();
byte[] buffer = new byte[length];
in.readFully(buffer);
ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, 0, buffer.length);
ObjectInputStream elementsStream = new ObjectInputStream(inputStream);
elements = (List) elementsStream.readObject();
afterInternalization();
setThreadVirtualizer();
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
ensureVirtualData();
beforeExternalization();
try
{
// maybe we should no longer serialize the id, as a new one is
// generated on deserialization
out.writeObject(uid);
out.writeObject(virtualizationContext);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(bout);
stream.writeObject(elements);
stream.flush();
byte[] bytes = bout.toByteArray();
out.writeInt(bytes.length);
out.write(bytes);
}
finally
{
afterExternalization();
}
}
private void setThreadVirtualizer()
{
JRVirtualizer threadVirtualizer = JRVirtualizationHelper.getThreadVirtualizer();
if (threadVirtualizer != null)
{
virtualizer = threadVirtualizer;
virtualizer.registerObject(this);
}
}
protected void finalize() throws Throwable //NOSONAR
{
if (virtualizer != null)
{
virtualizer.deregisterObject(this);
}
super.finalize();
}
/**
* Returns all the elements on the page, including the ones placed inside
* {@link JRPrintFrame frames}.
*
* @return all the elements on the page
*/
protected List getDeepElements()
{
List deepElements = new ArrayList(elements.size());
collectDeepElements(elements, deepElements);
return deepElements;
}
protected void collectDeepElements(List elementsList, List deepElements)
{
for (Iterator it = elementsList.iterator(); it.hasNext();)
{
JRPrintElement element = (JRPrintElement) it.next();
deepElements.add(element);
if (element instanceof JRPrintFrame)
{
JRPrintFrame frame = (JRPrintFrame) element;
collectDeepElements(frame.getElements(), deepElements);
}
}
}
public void beforeExternalization()
{
setElementsExternalData();
}
protected void setElementsExternalData()
{
traverseDeepElements(new ExternalizationElementVisitor());
}
protected void setExternalizationRenderer(JRPrintImage image)
{
JRRenderable renderer = image.getRenderer();
if (renderer != null && virtualizationContext.hasCachedRenderer(renderer.getId()))
{
image.setRenderer(new JRIdHolderRenderer(renderer));
}
}
protected void cacheInContext(List elementList)
{
if (elementList != null && !elementList.isEmpty())
{
for (Iterator it = elementList.iterator(); it.hasNext();)
{
JRPrintElement element = (JRPrintElement) it.next();
cacheInContext(element);
}
}
}
protected void cacheInContext(JRPrintElement element)
{
if (element instanceof JRTemplatePrintElement)
{
JRTemplatePrintElement templateElement = (JRTemplatePrintElement) element;
JRTemplateElement template = templateElement.getTemplate();
if (template != null)
{
virtualizationContext.cacheTemplate(template);
}
}
if (element instanceof JRPrintFrame)
{
JRPrintFrame frame = (JRPrintFrame) element;
cacheInContext(frame.getElements());
}
}
public void afterInternalization()
{
restoreElementsData();
}
protected void restoreElementsData()
{
traverseDeepElements(new InternalizationElementVisitor());
}
public JRVirtualizationContext getContext()
{
return virtualizationContext;
}
public void afterExternalization()
{
restoreElementsData();
}
/**
* Traverses all the elements on the page, including the ones placed inside
* {@link JRPrintFrame frames}.
*
* @param visitor element visitor
*/
protected void traverseDeepElements(ElementVisitor visitor)
{
traverseDeepElements(visitor, elements);
}
protected void traverseDeepElements(ElementVisitor visitor, List elementsList)
{
for (Iterator it = elementsList.iterator(); it.hasNext();)
{
JRPrintElement element = (JRPrintElement) it.next();
visitor.visitElement(element);
if (element instanceof JRPrintFrame)
{
JRPrintFrame frame = (JRPrintFrame) element;
traverseDeepElements(visitor, frame.getElements());
}
}
}
protected static interface ElementVisitor
{
void visitElement(JRPrintElement element);
}
protected class ExternalizationElementVisitor implements ElementVisitor
{
private final Map idTemplates = new HashMap();
public void visitElement(JRPrintElement element)
{
// replacing element template with dummy template that only stores the template ID
if (element instanceof JRTemplatePrintElement)
{
setExternalizationTemplate((JRTemplatePrintElement) element);
}
// replacing image renderer cached in the virtualization context
// with dummy renderer that only stores the renderer ID
if (element instanceof JRPrintImage)
{
setExternalizationRenderer((JRPrintImage) element);
}
}
protected void setExternalizationTemplate(JRTemplatePrintElement templateElement)
{
JRTemplateElement template = templateElement.getTemplate();
if (template != null)
{
if (virtualizationContext.hasCachedTemplate(template.getId()))
{
String templateId = template.getId();
JRIdHolderTemplateElement idTemplate = (JRIdHolderTemplateElement) idTemplates.get(templateId);
if (idTemplate == null)
{
idTemplate = new JRIdHolderTemplateElement(templateId);
idTemplates.put(templateId, idTemplate);
}
templateElement.setTemplate(idTemplate);
}
else
{
if (log.isDebugEnabled())
{
log.debug("Template " + template + " having id " + template.getId() + " not found in virtualization context cache");
}
}
}
}
}
protected class InternalizationElementVisitor implements ElementVisitor
{
public void visitElement(JRPrintElement element)
{
if (element instanceof JRTemplatePrintElement)
{
// restore the cached element template from the virtualization context
restoreTemplate((JRTemplatePrintElement) element);
}
if (element instanceof JRPrintImage)
{
// restore the cached image rendere from the virtualization context
restoreRenderer((JRPrintImage) element);
}
}
protected void restoreTemplate(JRTemplatePrintElement element)
{
JRTemplateElement template = element.getTemplate();
if (template != null && template instanceof JRIdHolderTemplateElement)
{
JRTemplateElement cachedTemplate = virtualizationContext.getCachedTemplate(template.getId());
if (cachedTemplate == null)
{
throw new JRRuntimeException("Template " + template.getId() + " not found in virtualization context.");
}
element.setTemplate(cachedTemplate);
}
}
protected void restoreRenderer(JRPrintImage image)
{
JRRenderable renderer = image.getRenderer();
if (renderer != null && renderer instanceof JRIdHolderRenderer)
{
JRRenderable cachedRenderer = virtualizationContext.getCachedRenderer(renderer.getId());
if (cachedRenderer == null)
{
throw new JRRuntimeException("Renderer " + renderer.getId() + " not found in virtualization context.");
}
image.setRenderer(cachedRenderer);
}
}
}
}