com.adobe.xfa.data.DataWindow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
package com.adobe.xfa.data;
import com.adobe.xfa.Arg;
import com.adobe.xfa.Document;
import com.adobe.xfa.Element;
import com.adobe.xfa.LogMessage;
import com.adobe.xfa.Node;
import com.adobe.xfa.NodeList;
import com.adobe.xfa.Obj;
import com.adobe.xfa.ScriptTable;
import com.adobe.xfa.STRS;
import com.adobe.xfa.XFA;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.IntegerHolder;
import java.util.ArrayList;
import java.util.List;
/**
*
* @exclude from published api -- Mike Tardif, May 2006.
*/
public final class DataWindow extends Obj {
private static final int DEFAULT_GRANULARITY = 16;
private String maRecordName;
private boolean mbDefined;
private boolean mbLazyLoading;
private boolean mbUpToDate;
private DataModel mDataModel;
private final List mFilters = new ArrayList();
private int mnAbsRecordIndex;
private int mnCurrentRecordIndex;
private int mnDesiredRecordsAfter;
private int mnDesiredRecordsBefore;
private int mnRecordLevel;
private int mnRecordsAfter;
private int mnRecordsBefore;
private int mnSpecifiedRecordLevel;
private final List mRecordIndexes = new ArrayList();
private final ArrayList mRecords = new ArrayList(DEFAULT_GRANULARITY);
private Element mStartNode;
public DataWindow() {
}
void addFilter(DataWindowFilter oFilter) {
mFilters.add(oFilter);
}
/**
* @exclude from public api.
*/
public void addRecordGroup(DataNode dataGroup) {
if (mRecords.size() == DEFAULT_GRANULARITY)
mRecords.ensureCapacity(1024);
for (int i = 0; i < mFilters.size(); i++) {
DataWindowFilter f = mFilters.get(i);
if (! f.filterRecord(dataGroup,mnAbsRecordIndex)) {
mnAbsRecordIndex++;
return; // stop looping it didn't pass all filters so don't add it
}
}
//
// Add record.
//
mRecords.add(dataGroup);
mRecordIndexes.add(Integer.valueOf(mnAbsRecordIndex));
mnAbsRecordIndex++;
}
private void adjustRecordLevel(boolean bUseRecordName /* = true */) {
assert(mnRecordLevel == 0);
Element oNewRoot = findStartNode(mStartNode, true);
if (oNewRoot == null)
return; // no data
Element parent = oNewRoot.getXFAParent();
Document doc = oNewRoot.getOwnerDocument();
while (parent != doc && parent != null) {
mnRecordLevel++;
parent = parent.getXFAParent();
}
mnRecordLevel += mnSpecifiedRecordLevel;
//
// Vantive 591676: check record name!
// initialize() call this with bUseRecordName set to false, because
// it does its own searching for records by name.
//
if (bUseRecordName && maRecordName != null) {
IntegerHolder recordLevel = new IntegerHolder(mnRecordLevel);
if (findRecordByName(oNewRoot, recordLevel, maRecordName))
mnRecordLevel = recordLevel.value;
}
}
/**
* Return the current record number.
*
* @return the current record number.
*/
public int currentRecordNumber() {
validate(false);
return mnCurrentRecordIndex;
}
@FindBugsSuppress(code="ES")
void dataGroupAddedOrRemoved (DataNode dataGroup, boolean bAdded) {
// Is this DataWindow initialized yet?
if (mDataModel == null)
return;
//
// The main purpose of this routine is to (potentially) invalidate this data window.
// If it's already invalid, there's nothing to do.
//
if (! mbUpToDate)
return;
if (mbLazyLoading)
return;
//
// First check if the name matches our record name criteria. If it doesn't, we're
// not interested in this event.
//
if (maRecordName != null && maRecordName != dataGroup.getName())
return;
//
// Check up the data group's lineage to see that its parent is $data (the alias node).
// If not, then it's not a record group, so we're not interested in this event.
//
Element oAliasNode = mDataModel.getAliasNode();
// watson bug 1649097, if the alias node is removed invalidate the data window.
if (oAliasNode == dataGroup) {
invalidate();
return;
}
Element oParent = dataGroup;
while (oParent != null) {
if (oParent == oAliasNode)
break; // we are inside $data
oParent = oParent.getXFAParent();
}
if (oParent == null)
return; // didn't hit the alias node, so the data group is not under $data
//
// Next check the record level to see if it matches.
//
if (mnRecordLevel == 0) {
adjustRecordLevel(true);
if (mnRecordLevel == 0)
return;
}
//
// Ensure the node add/removed isn't at the record depth or above.
//
DataNode oDomPeer = dataGroup;
if (oDomPeer == null)
return;
Element parentCheck = oDomPeer.getXFAParent();
int nLevel = 1;
while (parentCheck != null /* && parentCheck.getNodeType() != Node.DOCUMENT_NODE */ ) {
nLevel++;
if (nLevel > mnRecordLevel)
return;
parentCheck = parentCheck.getXFAParent();
}
if (nLevel == mnRecordLevel)
invalidate(); // mark data window as out-of-date for next time somebody calls us
}
/**
* Find a record by name
* @return the record level if found. null if not found
*/
@FindBugsSuppress(code="ES")
private boolean findRecordByName(Element root, IntegerHolder recordLevel, String aRecordName) {
String aName = root.getLocalName();
String aNewName = mDataModel.getMapping(aName);
if (aNewName != "")
aName = aNewName;
if (aRecordName == aName && ! isExcluded(root))
return recordLevel.value > 0;
Node node = root.getFirstXMLChild();
while (node != null) {
recordLevel.value++;
if (node instanceof Element && findRecordByName((Element) node, recordLevel, aRecordName))
return recordLevel.value > 0;
recordLevel.value--;
node = node.getNextXMLSibling();
}
return false;
}
/**
* Do a depth-first search starting at node, looking for a start node that is not
* in a compatible namespace and is not in an excluded namespace.
*/
private Element findStartNode(Element node, boolean bCheckParent /* = true */) {
boolean bHasModelParent = false;
if (bCheckParent) {
String aNS = node.getNS();
if (mDataModel.isCompatibleNS(aNS) && node.getLocalName() == STRS.DATASETS)
bHasModelParent = true;
}
if (! bHasModelParent && ! isExcluded(node))
return node; // this node is OK to use
// dig through children
for (Node childNode = node.getFirstXMLChild(); childNode != null; childNode = childNode.getNextXMLSibling()) {
if (childNode instanceof Element) {
Element childElement = (Element)childNode;
Element search = null;
// Only get xfa:data Child.
// Watson 1114483 - legacy XPF data has "xfa:Data" - so need to check both.
if (bHasModelParent) {
String aNS = childElement.getNS();
String aLocalName = childElement.getLocalName();
if (mDataModel.isCompatibleNS(aNS) &&
(aLocalName == XFA.DATA || aLocalName == STRS.DATA)) {
search = findStartNode(childElement, false);
}
}
else
search = findStartNode(childElement, false);
if (search != null)
return search;
}
}
return null;
}
public String getClassAtom() {
return "dataWindow";
}
public String getClassName() {
return "dataWindow";
}
public Arg getScriptProperty(String sPropertyName) {
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataWindow#getScriptProperty");
}
public ScriptTable getScriptTable() {
return DataWindowScript.getScriptTable();
}
/**
* Move the current record to a specific record.
*
* @param newRecord the absolute record number.
*/
public void gotoRecord(int newRecord) {
validate(false);
if ((mbDefined) && (newRecord == currentRecordNumber()))
return;
if (mbLazyLoading) {
int newLastRecord; // last record in our mRecords Array
newLastRecord = newRecord + mnDesiredRecordsAfter;
//
// mnDesiredRecordsAfter might be UINT_MAX -- check to see if we
// exceeded the maximum value of a int by checking if
// we ended up with a smaller number (this will only happen
// when the value wraps around).
//
if (newLastRecord < newRecord)
newLastRecord = Integer.MAX_VALUE;
if (mnCurrentRecordIndex + mnRecordsAfter < newLastRecord)
loadToRecord(newLastRecord);
else if (mRecords.get(newRecord) == null) { // check if it's been unloaded
mbDefined = false;
throw new ExFull(ResId.InvalidRecordException);
}
}
if ((newRecord < 0) || (newRecord >= mRecords.size())) {
mbDefined = false;
return;
}
mnCurrentRecordIndex = newRecord;
mbDefined = true;
mnRecordsAfter = mRecords.size() - mnCurrentRecordIndex - 1;
if (mnRecordsAfter > mnDesiredRecordsAfter)
mnRecordsAfter = mnDesiredRecordsAfter;
mnRecordsBefore = mnCurrentRecordIndex;
if (mnRecordsBefore > mnDesiredRecordsBefore)
mnRecordsBefore = mnDesiredRecordsBefore;
//
// Evaluate script if applicable.
// Disable this for now because you can't script on the first record:ready
// (the script get registered before the event does).
// $form:ready can be used for scripting (it occurs after a record ready)
//
// mDataModel.getEventManager().eventOccurred("ready", /* $record object */);
//
// Notify peers that the record has changed.
//
notifyPeers(Peer.UPDATED, XFA.RECORD, null);
}
@FindBugsSuppress(code="ES")
void initialize(DataModel root,
Element startNode,
int recordLevel,
String aRecordName,
int nDesiredRecordsBefore,
int nDesiredRecordsAfter) {
mbUpToDate = false; // Setting this to false effectively short-circuits updateFromPeer
// for efficiency. It will be set to true after file loading is complete,
// via updateAfterLoad().
mStartNode = startNode;
mnSpecifiedRecordLevel = recordLevel;
maRecordName = aRecordName;
Document doc = startNode.getOwnerDocument();
mbLazyLoading = doc.isIncrementalLoad();
mnDesiredRecordsBefore = nDesiredRecordsBefore;
mnDesiredRecordsAfter = nDesiredRecordsAfter;
mDataModel = root;
if (mnRecordLevel == 0)
adjustRecordLevel(false);
if (maRecordName != null) {
Element startNodeCopy = findStartNode(startNode, true);
if (startNodeCopy == null) {
mDataModel.addErrorList(new ExFull(ResId.CantFindRecordException), LogMessage.MSG_WARNING, null);
return;
}
IntegerHolder recordLevelHolder = new IntegerHolder(mnRecordLevel);
//
// Update recordLevel & verify that recordName exists.
//
if (! findRecordByName(startNodeCopy, recordLevelHolder, maRecordName)) {
if (! mbLazyLoading) {
mDataModel.addErrorList(new ExFull(ResId.CantFindRecordException), LogMessage.MSG_WARNING, null);
return;
}
//
// If lazy loading, it's possible we just haven't loaded enough of the file yet
// to see the first record -- load elements until we hit the end of file or
// we see the named record.
//
Element e = doc.loadToNextElement(null);
while (e != null) {
String aName = e.getLocalName();
if (aName == maRecordName && ! isExcluded(e)) {
if (! findRecordByName(startNodeCopy, recordLevelHolder, maRecordName)) {
mDataModel.addErrorList(new ExFull(ResId.CantFindRecordException), LogMessage.MSG_WARNING, null);
return;
}
break;
}
e = doc.loadToNextElement(null);
}
if (e == null) {
mDataModel.addErrorList(new ExFull(ResId.CantFindRecordException), LogMessage.MSG_WARNING, null);
return;
}
}
mnRecordLevel = recordLevelHolder.value;
}
}
private void invalidate() {
mbUpToDate = false;
mRecords.clear();
mRecordIndexes.clear();
//
// TODO !!!!!! Review this.
// Vantive roach #565412 for the XFA Plugin
// this will notify the plugin that our window may have
// changed. Since the plugin is the only code currently
// using notifyPeer, and all it does is set a flag, this is
// ok for now. If it was to ask for next record when it got
// notified, this would not work.
//
notifyPeers(Peer.UPDATED, "", null);
}
/**
* Determine if the data window is currently in a defined (valid) state. A data window
* is in this state if the current record index indicates a valid record. A data window
* is not defined if there are no records, or if the current record index has
* been positioned beyond the end of the records.
*
* @return true if dataWindow is defined, false otherwise.
* @exclude from public api.
*/
public boolean isDefined() {
validate(false);
return mbDefined;
}
private boolean isExcluded(Element node) {
String aNS = node.getNS();
return mDataModel.isCompatibleNS(aNS) || mDataModel.excludeNS(aNS);
}
// boolean isRecordDepth(int depth) {
// return depth == mnRecordLevel;
// }
boolean isRecordDepth(Node node) {
Element parentCheck = node.getXMLParent();
int nLevel = 1;
Node oDoc = node.getOwnerDocument();
while (parentCheck != null && parentCheck != oDoc)
{
nLevel++;
if (nLevel > mnRecordLevel)
return false;
parentCheck = parentCheck.getXMLParent();
}
return nLevel == mnRecordLevel;
}
/**
* Determine if a data group is a record.
*
* @param dataGroup the data group in question.
* @return true if dataGroup is a data group, false otherwise.
*/
@FindBugsSuppress(code="ES")
public boolean isRecordGroup(DataNode dataGroup) {
//
// If a name was specified in record criteria, and the group's name doesn't
// match that name, return false.
//
if (maRecordName != null && maRecordName != dataGroup.getName())
return false;
//
// Check the level of the XML DOM peer of the node. If it's the same as
// mnRecordLevel, then it's a record.
//
if (dataGroup.getXFAParent() == mDataModel)
return false;
Node peer = dataGroup.getXmlPeer();
return isRecordDepth(peer);
}
public boolean isUpToDate () {
return mbUpToDate;
}
/*
* newRecord is 0-based
*/
private void loadToRecord(int newRecord) {
assert(mbLazyLoading);
if (newRecord < mnCurrentRecordIndex) { // can't go backward when lazy loading
mbDefined = false;
throw new ExFull(ResId.InvalidRecordException);
}
while (mDataModel.loadToNextRecord() != null) {
//
// Right now the entire record array is stored and never goes away, although most
// of the entries will be null and hence won't take up much memory. Since when
// lazy-loading we only move forward in the file, run backward through the
// list deleting records and setting the corresponding entry in mRecords to null.
//
int nRecordsSize = mRecords.size();
int nWindowSize = mnDesiredRecordsAfter + mnDesiredRecordsBefore + 1;
if (nRecordsSize > nWindowSize) {
//
// nLast is the last (i.e. greatest) index to be deleted. This loop normally
// only executes once.
//
int nLast = nRecordsSize - nWindowSize - 1;
for (int i = nLast; ; i--) {
DataNode dataGroup = mRecords.get(i);
if (dataGroup == null)
break;
dataGroup.remove(); // delete this record and corresponding XML DOM subtree
mRecords.set(i, null);
if (i == 0)
break;
}
}
//
// exit early if we have enough records
//
if (nRecordsSize >= newRecord)
return;
}
}
/**
* Move the current record by an amount relative to the current record.
*
* @param recordOffset the number of records from the current record, positive
* or negative, by which the current record should move.
*/
public void moveCurrentRecord(int recordOffset) {
validate(false);
gotoRecord(currentRecordNumber() + recordOffset);
}
private void populate(DataNode root) {
NodeList children = root.getNodes();
int numChildren = children.length();
for (int i = 0; i < numChildren; i++) {
DataNode node = (DataNode) children.item(i);
//
// Only operate on datagroups -- ignore datavalues (they won't be recognized
// as a record).
//
if (! node.isSameClass(XFA.DATAGROUP))
continue;
if (isRecordGroup(node)) {
addRecordGroup(node);
}
else {
//
// Recursively descend into the tree looking for elements
// at the proper level. The children of records are not
// searched because a record can't contain another record.
//
populate(node);
}
}
}
void postLoad() {
if (mbDefined) {
//
// Evaluate script if applicable (for record 1).
// Disable this for now because you can't script on the first record:ready
// (the script get registered before the event does).
// $form:ready can be used for scripting (it occurs after a record ready)
//
// mDataModel.getEventManager().eventOccurred("ready", /* $record object */);
}
}
/**
* Return a record relative to the current record.
*
* @param recordOffset the number of records from the current record, positive
* or negative (0 returns current record).
* @return the requested record.
* @exclude from public api.
*/
public DataNode record(int recordOffset) {
validate(true);
if (recordOffset < 0 && -recordOffset > recordsBefore())
throw new ExFull(ResId.OutsideDataWindowException);
if (recordOffset > 0 && recordOffset > recordsAfter())
throw new ExFull(ResId.OutsideDataWindowException);
return mRecords.get(recordOffset + mnCurrentRecordIndex);
}
/**
* @exclude from public api.
*/
public int recordAbsIndex(int recordOffset) {
validate(true);
if (recordOffset < 0 && -recordOffset > recordsBefore())
throw new ExFull(ResId.OutsideDataWindowException);
if (recordOffset > 0 && recordOffset > recordsAfter())
throw new ExFull(ResId.OutsideDataWindowException);
return (mRecordIndexes.get(recordOffset + mnCurrentRecordIndex)).intValue();
}
/**
* Determine the number of records after the current record in this window.
*
* @return the number of records after the current record.
*/
public int recordsAfter() {
validate(true);
return mnRecordsAfter;
}
/**
* Determine the number of records before the current record in this window.
*
* @return the number of records before the current record.
*/
public int recordsBefore() {
validate(true);
return mnRecordsBefore;
}
/**
* @exclude from public api.
*/
public boolean removeRecordGroup(DataNode dataGroup) {
for (int i = 0; i < mRecords.size(); i++) {
if (mRecords.get(i) == dataGroup) {
mRecords.remove(i);
mRecordIndexes.remove(i);
mnAbsRecordIndex--;
return true;
}
}
return false;
}
private void reset() {
mnCurrentRecordIndex = 0;
mnRecordsBefore = 0;
mnRecordsAfter = 0;
mbDefined = false;
if (mRecords.size() > 0) {
mnRecordsAfter = mRecords.size() - 1;
if (mnRecordsAfter > mnDesiredRecordsAfter)
mnRecordsAfter = mnDesiredRecordsAfter;
mbDefined = true;
}
}
public void resetRecordDepth() {
//
// Resets mnRecordLevel according to the absolute
// depth in the XML DOM called if envoloping xml
// elements are added to the data.
//
mnRecordLevel = 0;
adjustRecordLevel(true);
mbUpToDate = false;
validate(false);
}
public void setScriptProperty(String sPropertyName, Arg propertyValue) {
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataWindow#setScriptProperty");
}
// void setStartNode(DataNode oStartNode) {
// mStartNode = oStartNode;
// }
void uninitialize() {
mnRecordLevel = 0;
mDataModel = null;
mStartNode = null;
}
/**
* @exclude from public api.
*/
public void updateAfterLoad() {
if (mbLazyLoading) {
//
// load enough records to fill the data window
//
int nLastRecord;
if (mnDesiredRecordsAfter == Integer.MAX_VALUE)
nLastRecord = Integer.MAX_VALUE;
else
nLastRecord = 1 + mnDesiredRecordsAfter;
loadToRecord(nLastRecord);
}
mbUpToDate = true;
reset();
}
private void validate(boolean bMustBeDefined) {
if (! mbUpToDate && mDataModel != null) {
invalidate();
//
// initialize record list
//
populate((DataNode) mDataModel.getAliasNode());
reset();
mbUpToDate = true;
}
if (bMustBeDefined && ! mbDefined)
throw new ExFull(ResId.UndefinedDataWindowException);
}
}