Java tutorial
/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved. * http://www.jaspersoft.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/>. */ package net.sf.jasperreports.engine.base; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import net.sf.jasperreports.engine.JRConstants; import net.sf.jasperreports.engine.JRPrintElement; import net.sf.jasperreports.engine.JRVirtualizable; import net.sf.jasperreports.engine.fill.JRVirtualizationContext; import net.sf.jasperreports.engine.fill.VirtualizationObjectInputStream; import net.sf.jasperreports.engine.fill.VirtualizationObjectOutputStream; import net.sf.jasperreports.engine.util.DeepPrintElementCounter; /** * @author Lucian Chirita (lucianc@users.sourceforge.net) */ public class ElementsBlock implements JRVirtualizable<VirtualElementsData>, ElementStore, Serializable { private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID; private static final Log log = LogFactory.getLog(ElementsBlock.class); private static final AtomicInteger uidCounter = new AtomicInteger(); private static final Random uidRandom = new Random(); private JRVirtualizationContext context; /** * A unique identifier that is useful for serialization and deserialization * to some persistence mechanism. */ private String uid; private List<JRPrintElement> elements; private transient volatile int size; private transient VirtualElementsData virtualData; private transient int deepElementCount; // only kept during the fill and does not get restored on deserialization private transient JRVirtualPrintPage page; public ElementsBlock(JRVirtualizationContext context, JRVirtualPrintPage page) { this.context = context; this.page = page; this.uid = makeUID(); if (log.isDebugEnabled()) { log.debug("generated uid " + uid + " for " + this); } this.elements = new ArrayList<JRPrintElement>(); this.size = 0; this.deepElementCount = 0; } public ElementsBlock(JRVirtualizationContext context, JRVirtualPrintPage page, String uid, int size) { this.context = context; this.page = page; this.uid = uid; this.size = size; //no need to set deepElementCount } private void lockContext() { //FIXME locking the whole context is too much, ideally we'd have a lock for this object only context.lock(); } private void unlockContext() { context.unlock(); } private void register() { if (context.getVirtualizer() != null) { context.getVirtualizer().registerObject(this); } } /** * Make a new identifier for an object. * * @return the new identifier */ private String makeUID() { //FIXME use UUID? return System.identityHashCode(context) + "_" + System.identityHashCode(this) + "_" + uidCounter.incrementAndGet() + "_" + uidRandom.nextInt(); } @Override public int size() { return size; } public boolean isEmpty() { return size == 0; } @Override public JRPrintElement get(int index) { lockContext(); try { ensureDataAndTouch(); return elements.get(index); } finally { unlockContext(); } } protected boolean preAdd(JRPrintElement element, boolean force) { boolean empty = elements != null && elements.isEmpty(); if (empty) { // if there are no elements, the object has not yet been registered with the virtualizer register(); } else { ensureDataAndTouch(); } // check whether we passed the element count threshold. // if the list is empty, allow at least one element to be added // no matter the element count. int elementCount = DeepPrintElementCounter.count(element); if (!force && !elements.isEmpty()) { int pageSize = context.getPageElementSize(); if (pageSize > 0 && deepElementCount + elementCount > pageSize) { if (log.isDebugEnabled()) { log.debug("overflow of block with size " + deepElementCount + ", adding " + elementCount); } return false; } } deepElementCount += elementCount; return true; } public boolean add(JRPrintElement element, boolean force) { lockContext(); try { boolean adding = preAdd(element, force); if (adding) { elements.add(element); ++size; } return adding; } finally { unlockContext(); } } @Override public boolean add(JRPrintElement element) { return add(element, false); } public boolean add(int index, JRPrintElement element, boolean force) { lockContext(); try { boolean adding = preAdd(element, force); if (adding) { elements.add(index, element); ++size; } return adding; } finally { unlockContext(); } } @Override public boolean add(int index, JRPrintElement element) { return add(index, element, false); } @Override public JRPrintElement set(int index, JRPrintElement element) { lockContext(); try { ensureDataAndTouch(); JRPrintElement oldElement = elements.get(index); deepElementCount -= DeepPrintElementCounter.count(oldElement); deepElementCount += DeepPrintElementCounter.count(element); return elements.set(index, element); } finally { unlockContext(); } } @Override public JRPrintElement remove(int index) { lockContext(); try { ensureDataAndTouch(); JRPrintElement element = elements.remove(index); --size; // decrement the deep count deepElementCount -= DeepPrintElementCounter.count(element); if (elements.isEmpty()) { // if the list is empty now, deregister with the virtualizer. // this helps with subreports by immediately releasing the external storage. deregister(); } return element; } finally { unlockContext(); } } @Override public String getUID() { return uid; } private void ensureDataAndTouch() { if (elements == null) { ensureData(); } else { if (context.getVirtualizer() != null) { context.getVirtualizer().touch(this); } } } @Override public void ensureVirtualData() { lockContext(); try { if (elements == null) { ensureData(); } } finally { unlockContext(); } } private void ensureData() { if (context.getVirtualizer() != null) { context.getVirtualizer().requestData(this); } } @Override public void setVirtualData(VirtualElementsData virtualData) { lockContext(); try { this.virtualData = virtualData; this.elements = virtualData.getElements(); //FIXME recheck size? } finally { unlockContext(); } } @Override public VirtualElementsData getVirtualData() { return virtualData; } @Override public void removeVirtualData() { lockContext(); try { virtualData = null; elements = null; } finally { unlockContext(); } } @Override public void beforeExternalization() { lockContext(); try { virtualData = new VirtualElementsData(elements); context.beforeExternalization(this); } finally { unlockContext(); } } @Override public void afterExternalization() { lockContext(); try { context.afterExternalization(this); } finally { unlockContext(); } } @Override public void afterInternalization() { lockContext(); try { context.afterInternalization(this); virtualData = null; } finally { unlockContext(); } } @Override public JRVirtualizationContext getContext() { return context; } @SuppressWarnings("unchecked") 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(); if (log.isDebugEnabled()) { log.debug("Original uid " + oldUid + "; new uid " + uid); } context = (JRVirtualizationContext) in.readObject(); int length = in.readInt(); //FIXME put a limit on the buffer byte[] buffer = new byte[length]; in.readFully(buffer); ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, 0, buffer.length); VirtualizationObjectInputStream elementsStream = new VirtualizationObjectInputStream(inputStream, context); elements = (List<JRPrintElement>) elementsStream.readObject(); size = elements.size(); if (!elements.isEmpty()) { register(); } } private void writeObject(java.io.ObjectOutputStream out) throws IOException { lockContext(); try { ensureDataAndTouch(); beforeExternalization(); try { // maybe we should no longer serialize the id, as a new one is // generated on deserialization out.writeObject(uid); out.writeObject(context); ByteArrayOutputStream bout = new ByteArrayOutputStream(); VirtualizationObjectOutputStream stream = new VirtualizationObjectOutputStream(bout, context); stream.writeObject(elements); stream.flush(); byte[] bytes = bout.toByteArray(); out.writeInt(bytes.length); out.write(bytes); } finally { afterExternalization(); } } finally { unlockContext(); } } // not implementing finalize because it can slow down GC to the point it can no longer handle the rate of newly created objects. // we're relying on the virtualizer to keep track of garbage collected objects via weak references. /* protected void finalize() throws Throwable //NOSONAR { dispose(); super.finalize(); } */ @Override public void dispose() { lockContext(); try { // if empty, the object is not registered if (elements == null || !elements.isEmpty()) { deregister(); } // removeVirtualData? letting GC do his thing for now. } finally { unlockContext(); } } private void deregister() { if (context.getVirtualizer() != null) { context.getVirtualizer().deregisterObject(this); } } @Override public JRVirtualPrintPage getPage() { return page; } }