All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jett.tag.BaseLoopTag Maven / Gradle / Ivy

package net.sf.jett.tag;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Sheet;

import net.sf.jett.event.TagLoopListener;
import net.sf.jett.event.TagLoopEvent;
import net.sf.jett.exception.TagParseException;
import net.sf.jett.model.BaseLoopTagStatus;
import net.sf.jett.model.Block;
import net.sf.jett.model.PastEndAction;
import net.sf.jett.model.WorkbookContext;
import net.sf.jett.transform.BlockTransformer;
import net.sf.jett.util.AttributeUtil;
import net.sf.jett.util.SheetUtil;

/**
 * 

The abstract class BaseLoopTag is the base class for all tags * that represent loops. *

* *
Attributes: *
    *
  • Inherits all attributes from {@link BaseTag}.
  • *
  • copyRight (optional): boolean
  • *
  • fixed (optional): boolean
  • *
  • pastEndAction (optional): String
  • *
  • replaceValue (optional): String
  • *
  • groupDir (optional): String
  • *
  • collapse (optional): boolean
  • *
  • onLoopProcessed (optional): TagLoopListener
  • *
  • varStatus (optional): String
  • *
* * @author Randy Gettman */ public abstract class BaseLoopTag extends BaseTag { private static final boolean DEBUG = false; /** * Attribute for forcing "copy right" behavior. (Default is copy down.) */ public static final String ATTR_COPY_RIGHT = "copyRight"; /** * Attribute for not shifting other content out of the way; works the same * as "fixed size collections". */ public static final String ATTR_FIXED = "fixed"; /** * Attribute for specifying the "past end action", an action for dealing * with content beyond the range of looping content. * @see #PAST_END_ACTION_CLEAR * @see #PAST_END_ACTION_REMOVE * @see #PAST_END_ACTION_REPLACE_EXPR */ public static final String ATTR_PAST_END_ACTION = "pastEndAction"; /** * Attribute for specifying the direction of the grouping. This defaults to * no grouping. * @since 0.2.0 * @see #GROUP_DIR_ROWS * @see #GROUP_DIR_COLS * @see #GROUP_DIR_NONE */ public static final String ATTR_GROUP_DIR = "groupDir"; /** * Attribute for specifying whether the group should be displayed collapsed. * The default is false, for not collapsed. It is ignored if * neither rows nor columns are being grouped. * @since 0.2.0 */ public static final String ATTR_COLLAPSE = "collapse"; /** * Attribute for specifying a TagLoopListener to listen for * TagLoopEvents. * @since 0.3.0 */ public static final String ATTR_ON_LOOP_PROCESSED = "onLoopProcessed"; /** * Attribute for specifying a replacement value for expressions that * reference a collection that is past the end of iteration. This defaults * to an empty string "", and is only relevant when the "past * end action" is replaceExpr. * @see #ATTR_PAST_END_ACTION * @see #PAST_END_ACTION_REPLACE_EXPR * @since 0.7.0 */ public static final String ATTR_REPLACE_VALUE = "replaceValue"; /** * Attribute for specifying the name of the {@link net.sf.jett.model.LoopTagStatus} * object that will be exposed in the beans map. If this attribute is not * present, or the value is null, then no such object will be * exposed. * @since 0.9.1 */ public static final String ATTR_VAR_STATUS = "varStatus"; /** * The "past end action" value to clear the content of cells. */ public static final String PAST_END_ACTION_CLEAR = "clear"; /** * The "past end action" value to remove the cells, including things like * borders and formatting. */ public static final String PAST_END_ACTION_REMOVE = "remove"; /** * The "past end action" value to replace only the expressions that contain * past-end references. * @since 0.7.0 */ public static final String PAST_END_ACTION_REPLACE_EXPR = "replaceExpr"; /** * The "group dir" value to specify that columns should be grouped. * @since 0.2.0 */ public static final String GROUP_DIR_COLS = "cols"; /** * The "group dir" value to specify that rows should be grouped. * @since 0.2.0 */ public static final String GROUP_DIR_ROWS = "rows"; /** * The "group dir" value to specify that neither rows nor columns should be * grouped. * @since 0.2.0 */ public static final String GROUP_DIR_NONE = "none"; private static final List OPT_ATTRS = new ArrayList(Arrays.asList(ATTR_COPY_RIGHT, ATTR_FIXED, ATTR_PAST_END_ACTION, ATTR_REPLACE_VALUE, ATTR_GROUP_DIR, ATTR_COLLAPSE, ATTR_ON_LOOP_PROCESSED, ATTR_VAR_STATUS)); private boolean amIExplicitlyCopyingRight = false; private boolean amIFixed = false; private PastEndAction myPastEndAction = PastEndAction.CLEAR_CELL; private String myReplaceExprValue = ""; private Block.Direction myGroupDir; private boolean amICollapsed; private TagLoopListener myTagLoopListener; private String myVarStatusName; /** * Sets whether the repeated blocks are to be copied to the right (true) or * downward (default, false). * @param copyRight Whether the repeated blocks are to be copied to the right (true) or * downward (default, false). * @since 0.3.0 */ public void setCopyRight(boolean copyRight) { amIExplicitlyCopyingRight = copyRight; } /** * Sets "fixed" mode, which doesn't shift other content out of the way when * copying repeated blocks of cells. * @param fixed Whether to execute in "fixed" mode. * @since 0.3.0 */ public void setFixed(boolean fixed) { amIFixed = fixed; } /** * Sets the PastEndAction. * @param pae The PastEndAction. * @since 0.3.0 */ public void setPastEndAction(PastEndAction pae) { myPastEndAction = pae; } /** * Sets the replacement expression value. This defaults to an empty string * "". This is only relevant if a past end action of * "replaceExpr" is used. * @param value The replacement expression value. * @see #setPastEndAction * @see #PAST_END_ACTION_REPLACE_EXPR * @since 0.7.0 */ public void setReplaceExprValue(String value) { myReplaceExprValue = value; } /** * Sets the directionality of the Excel Group to be created, if any. * @param direction The directionality. * @since 0.3.0 */ public void setGroupDirection(Block.Direction direction) { myGroupDir = direction; } /** * Sets whether any Excel Group created is collapsed. * @param collapsed Whether any Excel group created is collapsed. * @since 0.3.0 */ public void setCollapsed(boolean collapsed) { amICollapsed = collapsed; } /** * Sets the TagLoopListener. * @param listener The TagLoopListener. * @since 0.3.0 */ public void setOnLoopProcessed(TagLoopListener listener) { myTagLoopListener = listener; } /** * There are no required attributes that all BaseLoopTags * support. * @return An empty List. */ protected List getRequiredAttributes() { return super.getRequiredAttributes(); } /** * All BaseLoopTags support the optional copy down tag. * @return A List of optional attribute names. */ protected List getOptionalAttributes() { List optAttrs = new ArrayList(super.getOptionalAttributes()); optAttrs.addAll(OPT_ATTRS); return optAttrs; } /** * Ensure that the past end action (if specified) is a valid value. Ensure * that the group direction (if specified) is a valid value. * @throws TagParseException If the attribute values are illegal or * unacceptable. */ protected void validateAttributes() throws TagParseException { super.validateAttributes(); TagContext context = getContext(); Map beans = context.getBeans(); Map attributes = getAttributes(); Block block = context.getBlock(); amIExplicitlyCopyingRight = AttributeUtil.evaluateBoolean(this, attributes.get(ATTR_COPY_RIGHT), beans, false); if (amIExplicitlyCopyingRight) block.setDirection(Block.Direction.HORIZONTAL); amIFixed = AttributeUtil.evaluateBoolean(this, attributes.get(ATTR_FIXED), beans, false); String strPastEndAction = AttributeUtil.evaluateStringSpecificValues(this, attributes.get(ATTR_PAST_END_ACTION), beans, ATTR_PAST_END_ACTION, Arrays.asList(PAST_END_ACTION_CLEAR, PAST_END_ACTION_REMOVE, PAST_END_ACTION_REPLACE_EXPR), PAST_END_ACTION_CLEAR); if (PAST_END_ACTION_CLEAR.equalsIgnoreCase(strPastEndAction)) myPastEndAction = PastEndAction.CLEAR_CELL; else if (PAST_END_ACTION_REMOVE.equalsIgnoreCase(strPastEndAction)) myPastEndAction = PastEndAction.REMOVE_CELL; else if (PAST_END_ACTION_REPLACE_EXPR.equalsIgnoreCase(strPastEndAction)) myPastEndAction = PastEndAction.REPLACE_EXPR; myReplaceExprValue = AttributeUtil.evaluateString(this, attributes.get(ATTR_REPLACE_VALUE), beans, ""); String strGroupDir = AttributeUtil.evaluateStringSpecificValues(this, attributes.get(ATTR_GROUP_DIR), beans, ATTR_GROUP_DIR, Arrays.asList(GROUP_DIR_ROWS, GROUP_DIR_COLS, GROUP_DIR_NONE), GROUP_DIR_NONE); if (GROUP_DIR_ROWS.equals(strGroupDir)) myGroupDir = Block.Direction.VERTICAL; else if (GROUP_DIR_COLS.equals(strGroupDir)) myGroupDir = Block.Direction.HORIZONTAL; else if (GROUP_DIR_NONE.equals(strGroupDir)) myGroupDir = Block.Direction.NONE; amICollapsed = AttributeUtil.evaluateBoolean(this, attributes.get(ATTR_COLLAPSE), beans, false); myTagLoopListener = AttributeUtil.evaluateObject(this, attributes.get(ATTR_ON_LOOP_PROCESSED), beans, ATTR_ON_LOOP_PROCESSED, TagLoopListener.class, null); myVarStatusName = AttributeUtil.evaluateString(this, attributes.get(ATTR_VAR_STATUS), beans, null); } /** * Returns the PastEndAction, which is controlled by the * attribute specified by ATTR_PAST_END_ACTION. It defaults to * CLEAR_CELL. * @return A PastEndAction. * @see PastEndAction */ protected PastEndAction getPastEndAction() { return myPastEndAction; } /** * Returns the replacement expression value, which defaults to am empty * string "". This is only relevant if the past end action is * "replaceExpr". * @return The replacement expression value. * @see #getPastEndAction * @see #PAST_END_ACTION_REPLACE_EXPR * @see #ATTR_REPLACE_VALUE * @since 0.7.0 */ protected String getReplacementExprValue() { return myReplaceExprValue; } /** *

Provide a generic way to process a tag that loops, with the Template * Method pattern.

*
    *
  1. Decide whether content needs to be shifted out of the way, and shift * the content out of the way if necessary. This involves calling * getCollectionNames() to determine if any of the collection * names are "fixed". This also involves calling getVarNames() * to determine which, if any, past end actions, need to be taken.
  2. *
  3. Call getNumIterations to determine the number of Blocks * needed.
  4. *
  5. Copy the Block the needed number of times.
  6. *
  7. Get the loop iterator by calling getLoopIterator().
  8. *
  9. Over each loop of the iterator...
  10. *
      *
    1. Create a Block for the iteration.
    2. *
    3. If the collection values are exhausted, apply any "past end actions". *
    4. *
    5. Call beforeBlockProcessed()
    6. . *
    7. Process the current Block with a * BlockTransformer.
    8. *
    9. Call afterBlockProcessed()
    10. . *
    *
* @return Whether the first Cell in the Block * associated with this Tag was processed. * @see #getCollectionNames * @see #getVarNames * @see #getNumIterations * @see #getLoopIterator * @see #beforeBlockProcessed * @see #afterBlockProcessed */ public boolean process() { TagContext context = getContext(); Block block = context.getBlock(); WorkbookContext workbookContext = getWorkbookContext(); // Important for formulas, so different cell reference map entries from // different loops can be distinguished. workbookContext.incrSequenceNbr(); Sheet sheet = context.getSheet(); Map beans = context.getBeans(); // Decide whether this is "fixed" in 2 ways: // 1. A fixed size collection name was specified and is present. // 2. The "fixed" attribute is true. boolean fixed = amIFixed; if (!fixed) { // Shallow copy. List fixedSizeCollNames = new ArrayList( workbookContext.getFixedSizedCollectionNames()); List collNames = getCollectionNames(); if (collNames != null) { if (DEBUG) { for (String collName : collNames) System.err.println("BaseLoopTag: collName found: \"" + collName + "\"."); } // Remove all collection names not found. for (Iterator itr = fixedSizeCollNames.iterator(); itr.hasNext(); ) { String fixedSizeCollName = itr.next(); if (!collNames.contains(fixedSizeCollName)) itr.remove(); } } else { fixedSizeCollNames.clear(); } fixed = !fixedSizeCollNames.isEmpty(); } int numIterations = getNumIterations(); List varNames = getVarNames(); if (DEBUG) System.err.println("BaseLoopTag: numIterations=" + numIterations); if (numIterations == 0) { // If fixed, no shifting is to occur for the removed block. if (fixed) { switch(myPastEndAction) { case CLEAR_CELL: clearBlock(); break; case REMOVE_CELL: deleteBlock(); break; case REPLACE_EXPR: SheetUtil.takePastEndAction(sheet, block, varNames, myPastEndAction, myReplaceExprValue); block.collapse(); break; default: throw new IllegalStateException("BaseLoopTag: Unknown PastEndAction: " + myPastEndAction); } } else removeBlock(); return false; } else { BlockTransformer transformer = new BlockTransformer(); List blocksToProcess = new ArrayList(numIterations); // Create room for the additional Blocks; the Block knows the proper // direction (right or down). // Don't create room if the collection is "fixed size", i.e. we can // assume that room exists already. if (!fixed) shiftForBlock(); // Copy the Block. for (int i = 0; i < numIterations; i++) { Block copy = copyBlock(i); if (DEBUG) System.err.println(" Adding copied block: " + copy); blocksToProcess.add(copy); } int index = 0; Iterator iterator = getLoopIterator(); BaseLoopTagStatus status = null; if (myVarStatusName != null && !myVarStatusName.isEmpty()) { status = getLoopTagStatus(); beans.put(myVarStatusName, status); } int right, bottom, colGrowth, rowGrowth; int maxRight = 0; int maxBottom = 0; while(iterator.hasNext()) { Object item = iterator.next(); Block currBlock = blocksToProcess.get(index); // Off the end of the collection! if (index >= getCollectionSize()) { switch(myPastEndAction) { case CLEAR_CELL: SheetUtil.clearBlock(sheet, currBlock, getWorkbookContext()); break; case REMOVE_CELL: SheetUtil.deleteBlock(sheet, context, currBlock, getWorkbookContext()); break; case REPLACE_EXPR: SheetUtil.takePastEndAction(sheet, currBlock, varNames, myPastEndAction, myReplaceExprValue); break; default: throw new IllegalStateException("BaseLoopTag: Unknown PastEndAction: " + myPastEndAction); } } // Before Block Processing. beforeBlockProcessed(context, currBlock, item, index); // Fire a before tag loop processed event here, after the Before // Block Processing occurs. if (fireBeforeTagLoopProcessedEvent(currBlock, index)) { // Process the block. TagContext blockContext = new TagContext(); blockContext.setSheet(sheet); blockContext.setBeans(beans); blockContext.setBlock(currBlock); blockContext.setProcessedCellsMap(context.getProcessedCellsMap()); blockContext.setDrawing(context.getDrawing()); blockContext.setMergedRegions(context.getMergedRegions()); blockContext.setCurrentTag(this); if (DEBUG) System.err.println(" Block Before: " + currBlock); right = currBlock.getRightColNum(); bottom = currBlock.getBottomRowNum(); transformer.transform(blockContext, workbookContext); // See if the block transformation grew or shrunk the current block. if (DEBUG) System.err.println(" Block After: " + currBlock); colGrowth = currBlock.getRightColNum() - right; rowGrowth = currBlock.getBottomRowNum() - bottom; // If it did, then all pending blocks must react! if (colGrowth != 0 || rowGrowth != 0) { if (DEBUG) System.err.println(" colGrowth is " + colGrowth + ", rowGrowth is " + rowGrowth); for (int j = index + 1; j < numIterations; j++) { Block pendingBlock = blocksToProcess.get(j); if (DEBUG) System.err.println(" Reacting Block: " + pendingBlock); pendingBlock.reactToGrowth(currBlock, colGrowth, rowGrowth); } } // Get max right/bottom to expand the tag's block later. if (currBlock.getRightColNum() > maxRight) maxRight = currBlock.getRightColNum(); if (currBlock.getBottomRowNum() > maxBottom) maxBottom = currBlock.getBottomRowNum(); // Fire a tag loop processed event here, before the After Block Processing // occurs. fireTagLoopProcessedEvent(currBlock, index); } // After Block Processing. afterBlockProcessed(context, currBlock, item, index); // End of loop processing. if (status != null) { status.incrementIndex(this); } index++; } // End while loop over collection items if (status != null) { beans.remove(myVarStatusName); } // Expand the tag block. block.expand(maxRight - block.getRightColNum(), maxBottom - block.getBottomRowNum()); // Grouping - only if there was at least one item to process. groupRowsOrCols(sheet, context.getBlock(), blocksToProcess.get(blocksToProcess.size() - 1)); } return true; } /** * If there is a TagLoopListener, then create and fire a * TagLoopEvent, with beans and sheet taken from this * BaseLoopTag, and with the given loop index and given * Block. * @param block The current Block. * @param index The zero-based loop index. * @return Whether processing of the Tag loop iteration should * occur. If the TagLoopListener's * beforeTagLoopProcessed method returns false, * then this method returns false. * @since 0.8.0 */ private boolean fireBeforeTagLoopProcessedEvent(Block block, int index) { if (myTagLoopListener != null) { TagContext context = getContext(); TagLoopEvent tagLoopEvent = new TagLoopEvent(context.getSheet(), block, context.getBeans(), index); return myTagLoopListener.beforeTagLoopProcessed(tagLoopEvent); } return true; } /** * If there is a TagLoopListener, then create and fire a * TagLoopEvent, with beans and sheet taken from this * BaseLoopTag, and with the given loop index and given * Block. * @param block The current Block. * @param index The zero-based loop index. */ private void fireTagLoopProcessedEvent(Block block, int index) { if (myTagLoopListener != null) { TagContext context = getContext(); TagLoopEvent tagLoopEvent = new TagLoopEvent(context.getSheet(), block, context.getBeans(), index); myTagLoopListener.onTagLoopProcessed(tagLoopEvent); } } /** * Decide to and place an Excel Group for rows, columns, or nothing, * depending on attribute settings and the first and last * Blocks. * @param sheet The Sheet on which to group rows or columns. * @param first The first Block. * @param last The last Block. */ private void groupRowsOrCols(Sheet sheet, Block first, Block last) { int begin, end; if (DEBUG) System.err.println("BLT.gROC: " + myGroupDir + ", " + amICollapsed); switch(myGroupDir) { case VERTICAL: begin = first.getTopRowNum(); end = last.getBottomRowNum(); SheetUtil.groupRows(sheet, begin, end, amICollapsed); break; case HORIZONTAL: begin = first.getLeftColNum(); end = last.getRightColNum(); SheetUtil.groupColumns(sheet, begin, end, amICollapsed); break; // Do nothing on NONE. } } /** * Shifts cells out of the way of where copied blocks will go. */ private void shiftForBlock() { TagContext context = getContext(); Block block = context.getBlock(); Sheet sheet = context.getSheet(); int numIterations = getNumIterations(); SheetUtil.shiftForBlock(sheet, context, block, getWorkbookContext(), numIterations); } /** * Copies the Block in a particular direction. * @param numBlocksAway How many blocks away the Block will be * copied. * @return The newly copied Block. */ private Block copyBlock(int numBlocksAway) { TagContext context = getContext(); Block block = context.getBlock(); Sheet sheet = context.getSheet(); return SheetUtil.copyBlock(sheet, context, block, getWorkbookContext(), numBlocksAway); } /** * Returns the names of the Collections that are being used in * this BaseLoopTag. * @return A List collection names, or null if * not operating on any Collections. */ protected abstract List getCollectionNames(); /** * Returns the names of the variables that are being used in this * BaseLoopTag. * @return A List of variable names, or null if * there are not any variable names into any Collections. * @since 0.7.0 */ protected abstract List getVarNames(); /** * Returns the number of iterations. * @return The number of iterations. */ protected abstract int getNumIterations(); /** * Returns the size of the collection being iterated. This may be different * than the number of iterations because of the "limit" attribute. * @return The size of the collection being iterated. */ protected abstract int getCollectionSize(); /** * Returns a BaseLoopTagStatus that will be exposed in the * beans map if the appropriate attribute is given. Subclasses may want to * override this method to return an object that provides more information. * @return A BaseLoopTagStatus. * @since 0.9.1 */ protected BaseLoopTagStatus getLoopTagStatus() { return new BaseLoopTagStatus(this, getNumIterations()); } /** * Returns an Iterator that iterates over some * Collection of objects. The Iterator doesn't * need to support the remove operation. * @return An Iterator. */ protected abstract Iterator getLoopIterator(); /** * This method is called once per iteration loop, immediately before the * given Block is processed. An iteration index is supplied as * well. * @param context The TagContext. * @param currBlock The Block that is about to processed. * @param item The Object that resulted from the iterator. * @param index The iteration index (0-based). */ protected abstract void beforeBlockProcessed(TagContext context, Block currBlock, Object item, int index); /** * This method is called once per iteration loop, immediately after the * given Block is processed. An iteration index is supplied as * well. * @param context The TagContext. * @param currBlock The Block that was just processed. * @param item The Object that resulted from the iterator. * @param index The iteration index (0-based). */ protected abstract void afterBlockProcessed(TagContext context, Block currBlock, Object item, int index); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy