/** * Copyright (C) 2004 Orbeon, Inc. * * This program 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 * 2.1 of the License, or (at your option) any later version. * * 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. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.processor.pipeline.ast; import org.apache.commons.collections.CollectionUtils; import org.orbeon.dom.Attribute; import org.orbeon.dom.Element; import org.orbeon.dom.Node; import org.orbeon.dom.Node$; import org.orbeon.oxf.common.ValidationException; import org.orbeon.oxf.processor.pipeline.foreach.AbstractForEachProcessor; import org.orbeon.oxf.xml.dom4j.LocationData; import java.util.Collection; import java.util.Iterator; import java.util.List; public abstract class ASTNodeContainer { private Node node; private LocationData locationData; public Node getNode() { return node; } public void setNode(Node node) { this.node = node; } public void setLocationData(final LocationData locDat) { locationData = locDat; } /** * Use specified location if there is one. Otherwise if a node has been provided use the * location of the node. If no node and no location then return hull. */ public LocationData getLocationData() { final LocationData ret; if (locationData != null) { ret = locationData; } else if (node == null) { ret = null; } else if (node instanceof Element) { ret = (LocationData) ((org.orbeon.dom.Element) node).getData(); } else if (node instanceof Attribute) { ret = (LocationData) ((org.orbeon.dom.Attribute) node).getData(); } else { ret = null; } return ret; } public abstract void walk(ASTHandler handler); public void walkChildren(ASTHandler handler) { } protected void walk(List astNodes, ASTHandler handler) { for (Iterator i = astNodes.iterator(); i.hasNext();) { ASTNodeContainer astNode = (ASTNodeContainer) i.next(); astNode.walk(handler); } } public IdInfo getIdInfo() { class IdInfoASTHandler extends ASTSimpleHandler { final IdInfo idInfo = new IdInfo(); /** * Returns the id collected after this walked went through an AST. */ public IdInfo getIdInfo() { return idInfo; } public void hrefId(ASTHrefId hrefId) { idInfo.getInputRefs().add(hrefId.getId()); } public void output(ASTOutput output) { if (output.getId() != null) idInfo.getOutputIds().add(output.getId()); if (output.getRef() != null) { if (idInfo.getOutputRefs().contains(output.getRef())) throw new ValidationException("Output id '" + output.getRef() + "' can be referenced only once", output.getLocationData()); idInfo.getOutputRefs().add(output.getRef()); } } public boolean startChoose(ASTChoose choose) { choose.getHref().walk(this); // Get idInfo for each branch final IdInfo[] whenIdInfos; { List whens = choose.getWhen(); whenIdInfos = new IdInfo[whens.size()]; int count = 0; for (Iterator i = whens.iterator(); i.hasNext();) { ASTWhen astWhen = (ASTWhen) i.next(); IdInfoASTHandler idInfoASTHandler = new IdInfoASTHandler(); astWhen.walk(idInfoASTHandler); whenIdInfos[count] = idInfoASTHandler.getIdInfo(); Collection intersection = CollectionUtils.intersection (whenIdInfos[count].getInputRefs(), whenIdInfos[count].getOutputIds()); whenIdInfos[count].getInputRefs().removeAll(intersection); whenIdInfos[count].getOutputIds().removeAll(intersection); count++; } } // Make sure the output ids and output refs are the same for every branch if (whenIdInfos.length > 1) { for (int i = 1; i < whenIdInfos.length; i++) { if (!CollectionUtils.isEqualCollection(whenIdInfos[0].getOutputIds(), whenIdInfos[i].getOutputIds())) throw new ValidationException("ASTChoose branch number " + (i + 1) + " does not declare the same ids " + whenIdInfos[0].getOutputIds().toString() + " as the previous branches " + whenIdInfos[i].getOutputIds().toString(), choose.getLocationData()); if (!CollectionUtils.isEqualCollection(whenIdInfos[0].getOutputRefs(), whenIdInfos[i].getOutputRefs())) throw new ValidationException("ASTChoose branch number " + (i + 1) + " does not declare the same ids " + whenIdInfos[0].getOutputRefs().toString() + " as the previous branches " + whenIdInfos[i].getOutputRefs().toString(), choose.getLocationData()); } } // Add ids from all the branches for (int i = 0; i < whenIdInfos.length; i++) idInfo.getInputRefs().addAll(whenIdInfos[i].getInputRefs()); // Add output ids and output refs from first branch (they are the same for each branch) idInfo.getOutputIds().addAll(whenIdInfos[0].getOutputIds()); idInfo.getOutputRefs().addAll(whenIdInfos[0].getOutputRefs()); return false; } public boolean startForEach(ASTForEach forEach) { // Add contribution from <p:for-each> attributes forEach.getHref().walk(this); if (forEach.getRef() != null) idInfo.getOutputRefs().add(forEach.getRef()); if (forEach.getId() != null) idInfo.getOutputIds().add(forEach.getId()); forEach.getHref().walk(this); // Collect idInfo for all the statements final IdInfo statementsIdInfo; { IdInfoASTHandler statementsIdInfoASTHandler = new IdInfoASTHandler(); statementsIdInfoASTHandler.getIdInfo().getOutputIds().add(AbstractForEachProcessor.FOR_EACH_CURRENT_INPUT); for (Iterator i = forEach.getStatements().iterator(); i.hasNext();) { ASTStatement statement = (ASTStatement) i.next(); statement.walk(statementsIdInfoASTHandler); } statementsIdInfo = statementsIdInfoASTHandler.getIdInfo(); Collection intersection = CollectionUtils.intersection (statementsIdInfo.getInputRefs(), statementsIdInfo.getOutputIds()); statementsIdInfo.getInputRefs().removeAll(intersection); statementsIdInfo.getOutputIds().removeAll(intersection); } // Check the refs if (forEach.getId() == null && forEach.getRef() == null) { if (statementsIdInfo.getOutputRefs().size() != 0) throw new ValidationException("The statements in a <for-each> cannot have output ref; they reference " + statementsIdInfo.getOutputRefs().toString(), forEach.getLocationData()); } else if (statementsIdInfo.getOutputRefs().size() != 1) { throw new ValidationException("The statements in a <for-each> must have exactly one output ref; " + (statementsIdInfo.getOutputRefs().isEmpty() ? "this <for-each> has none" : "this <for-each> defined: " + statementsIdInfo.getOutputRefs().toString()), forEach.getLocationData()); } else { String statementsRef = (String) statementsIdInfo.getOutputRefs().iterator().next(); if (forEach.getId() != null) { if (!forEach.getId().equals(statementsRef)) throw new ValidationException("The statements in a <for-each> referenced the id '" + statementsRef + "' but the id declared on the <for-each> is '" + forEach.getId() + "'", forEach.getLocationData()); } else if (!forEach.getRef().equals(statementsRef)) { throw new ValidationException("The statements in a <for-each> referenced the id '" + statementsRef + "' but the ref declared on the <for-each> is '" + forEach.getRef() + "'", forEach.getLocationData()); } } // Add ids referenced inside <for-each> idInfo.getInputRefs().addAll(statementsIdInfo.getInputRefs()); return false; } } // Run the handler declared above on this node IdInfoASTHandler idInfoASTHandler = new IdInfoASTHandler(); walk(idInfoASTHandler); return idInfoASTHandler.getIdInfo(); } }