SortBySiblingBlock(Node def) throws RuleBaseException { super(def); before = def.query().exists("@before"); String attrName = before ? "@before" : "@after"; if (!"sibling".equals(def.query().single(attrName).value())) throw new RuleBaseException("sort block " + attrName + " must have value 'sibling'"); }
@Override public void verify() throws TransformException { ItemList targets = mod.nearest(NodeTarget.class).targets(); List<Node> references = mod.references(); ItemList records = mod.supplementQuery().all("sort-proxy"); assert targets.size() == references.size(); assert records.size() == references.size(); for (int i = 0; i < targets.size(); i++) { Node target = targets.get(i).node(); Node restoredProxy = references.get(i); ItemList items = query.runOn(mod.scope(target.query())); if (items.size() != 1) throw new TransformException( "sort by corresponding node must select one node per target, but instead selected " + items.size() + ": " + items); Node proxy = items.get(0).node(); if (!restoredProxy.equals(proxy)) throw new TransformException("computed proxy changed"); if (!proxy .query() .single( "(count(preceding::*) + count(ancestor::*)) eq xs:integer($_1/@position)", records.get(i)) .booleanValue()) throw new SortingException("computed proxy moved"); } }
@Override public void restore() throws TransformException { ItemList targets = mod.nearest(NodeTarget.class).targets(); values = new ArrayList<Pair<String, Item>>(targets.size()); for (Node node : targets.nodes()) { ItemList items = query.runOn(mod.scope(node.query())); if (items.size() != 1) throw new TransformException( "sort by value query did not select exactly one item: " + items); values.add(Pair.of(node.query().single("@xml:id").value(), items.get(0).toAtomicItem())); } }
@AllowAttributes({"priority", "by", "as", "before", "after"}) @Override public Block define(Node def) throws RuleBaseException { int numDirectives = def.query().single("count(@by | @as | @before | @after)").intValue(); if (numDirectives > 1) throw new RuleBaseException( "sort block specified with more than one of @by, @as, @before and @after"); if (numDirectives < 1) throw new RuleBaseException("sort block specified without any @by, @as, @before or @after"); if (def.query().exists("@by")) return new SortByValueBlock(def); else if (def.query().exists("@as")) return new SortByProxyBlock(def); else return new SortBySiblingBlock(def); }
@Override void resolveOrder(Mod.Builder modBuilder, Node node) throws TransformException { ItemList siblings = query.runOn(modBuilder.customScope(node.query().single("..").query())); for (Node sibling : siblings.nodes()) { if (!node.query().single(".. is $_1/..", sibling).booleanValue()) throw new TransformException("query selected non-sibling node: " + sibling); modBuilder.reference(sibling); } modBuilder .supplement() .elem("sort-siblings") .attr("run-length", siblings.size()) .end("sort-siblings"); }
@Override void resolveOrder(Mod.Builder modBuilder, Node node) throws TransformException { ItemList items = query.runOn(modBuilder.customScope(node.query())); if (items.size() != 1) throw new TransformException( "sort by value must select one value per target, but instead selected " + items.size() + ": " + items); modBuilder .supplement() .elem("sort-value") .attr("refid", node.query().single("@xml:id").value()) .text(items.get(0).value()) .end("sort-value"); }
@Test(expected = TransformException.class) public void resolveOrderBadQuery() throws RuleBaseException, TransformException { SortBlock block = define("<sort as='corresponding'>*</sort>"); Node uc1 = content.query().single("/id('uc1')").node(); setModBuilderCustomScope(uc1.query()); block.resolveOrder(modBuilder, uc1); }
@Test(expected = TransformException.class) public void resolveOrderNotSibling() throws RuleBaseException, TransformException { SortBlock block = define("<sort before='sibling'>.//uml:operation</sort>"); Node cname = content.query().single("/id('cname')").node(); Node uc1 = content.query().single("/id('uc1')").node(); setModBuilderCustomScope(uc1.query()); block.resolveOrder(modBuilder, cname); }
@Test public void resolveOrder() throws RuleBaseException, TransformException { SortBlock block = define("<sort by='ascending'>@name</sort>"); Node m1 = content.query().single("/id('m1')").node(); setModBuilderCustomScope(m1.query()); supplement(); block.resolveOrder(modBuilder, m1); checkSupplement("<sort-value refid='m1'>start</sort-value>"); }
@Override public void verify() throws TransformException { for (Pair<Node, List<Node>> pair : siblingsByTarget) { Node target = pair.first; List<Node> storedSiblings = pair.second; for (Node sibling : storedSiblings) { if (!target.query().single(".. is $_1/..", sibling).booleanValue()) throw new TransformException( "previously selected node is no longer a sibling: " + sibling); } List<Node> actualSiblings = query.runOn(mod.scope(target.query().single("..").query())).nodes().asList(); if (!actualSiblings.equals(storedSiblings)) throw new TransformException( "sibling list changed for target " + target.query().single("@xml:id").value()); // No need to check sibling order, since any change in the node's document will trigger a // sort. } }
@Override void resolveOrder(Mod.Builder modBuilder, Node node) throws TransformException { // TODO: see if we can resolve /id in global scope while keeping node context ItemList items = query.runOn(modBuilder.customScope(node.query())); if (items.size() != 1) throw new TransformException( "sort by corresponding node must select one node per target, but instead selected " + items.size() + ": " + items); Node proxy = items.get(0).node(); modBuilder.reference(proxy); modBuilder .supplement() .elem("sort-proxy") .attr( "position", proxy.query().single("count(preceding::*) + count(ancestor::*)").value()) .end("sort-proxy"); }
SortBlock(Node def) throws RuleBaseException { Item priorityItem = def.query().optional("@priority"); try { priority = priorityItem.extant() ? priorityItem.intValue() : 0; } catch (DatabaseException e) { throw new RuleBaseException( "sort block specified bad priority '" + priorityItem.value() + "'", e); } query = new Query.Items(def); }
SortByValueBlock(Node def) throws RuleBaseException { super(def); // TODO: support collations String direction = def.query().single("@by").value(); if ("ascending".equals(direction)) ascending = true; else if ("descending".equals(direction)) ascending = false; else throw new RuleBaseException( "sort block @by must have value 'ascending' or 'descending', got '" + direction + "'"); }
@Test public void resolveOrderEmpty() throws RuleBaseException, TransformException { SortBlock block = define("<sort before='sibling'>()</sort>"); Node cname = content.query().single("/id('cname')").node(); Node uc1 = content.query().single("/id('uc1')").node(); setModBuilderCustomScope(uc1.query()); supplement(); block.resolveOrder(modBuilder, cname); checkSupplement("<sort-siblings run-length='0'/>"); }
@Test public void resolveOrder1() throws RuleBaseException, TransformException { SortBlock block = define("<sort after='sibling'>uml:name</sort>"); Node comp1 = content.query().single("/id('comp1')").node(); Node uc1 = content.query().single("/id('uc1')").node(); setModBuilderCustomScope(uc1.query()); reference(content.query().single("/id('cname')").node()); supplement(); block.resolveOrder(modBuilder, comp1); checkSupplement("<sort-siblings run-length='1'/>"); }
@Test public void resolveOrder() throws RuleBaseException, TransformException { SortBlock block = define("<sort as='corresponding'>$source</sort>"); Node um1 = content.query().single("/id('um1')").node(); Node m1 = content.query().single("/id('m1')").node(); setModBuilderCustomScope(um1.query().let("$source", m1)); reference(m1); supplement(); block.resolveOrder(modBuilder, um1); checkSupplement("<sort-proxy position='1'/>"); }
@SuppressWarnings("unchecked") @Test(expected = TransformException.class) public void verifyNotSibling() throws RuleBaseException, TransformException { Node uc1 = content.query().single("/id('uc1')").node(); Node comp1 = content.query().single("/id('comp1')").node(); Node um1 = content.query().single("/id('um1')").node(); setModScope(uc1.query()); SortBySiblingBlock block = define("<sort before='sibling'>.//uml:method</sort>"); SortBySiblingBlock.SortBySiblingSeg seg = (SortBySiblingBlock.SortBySiblingSeg) block.createSeg(mod); seg.siblingsByTarget = Arrays.<Pair<Node, List<Node>>>asList(new Pair[] {Pair.of(comp1, Arrays.asList(um1))}); seg.verify(); }
@SuppressWarnings("unchecked") @Test public void verify() throws RuleBaseException, TransformException { Node uc1 = content.query().single("/id('uc1')").node(); Node comp1 = content.query().single("/id('comp1')").node(); Node comp2 = content.query().single("/id('comp2')").node(); Node cname = content.query().single("/id('cname')").node(); setModScope(uc1.query()); SortBySiblingBlock block = define("<sort before='sibling'>uml:name</sort>"); SortBySiblingBlock.SortBySiblingSeg seg = (SortBySiblingBlock.SortBySiblingSeg) block.createSeg(mod); seg.siblingsByTarget = Arrays.<Pair<Node, List<Node>>>asList( new Pair[] { Pair.of(comp1, Arrays.asList(cname)), Pair.of(comp2, Arrays.asList(cname)) }); seg.verify(); }
private void runVerifyScenario(String proxyid, String modData) throws TransformException, RuleBaseException { Node um1 = content.query().single("/id('um1')").node(); Node m1 = content.query().single("/id('m1')").node(); setModNearestAncestorImplementing( NodeTarget.class, new NodeTarget() { public ItemList targets() throws TransformException { return content.query().all("/id('um1')"); } }); setModData(modData); setModReferences(content.query().single("/id($_1)", proxyid).node()); setModScope(um1.query().let("$source", m1)); SortByProxyBlock block = define("<sort as='corresponding'>$source</sort>"); SortByProxyBlock.SortByProxySeg seg = (SortByProxyBlock.SortByProxySeg) block.createSeg(mod); seg.proxies = new ArrayList<Pair<String, Node>>(); seg.proxies.add(Pair.of("um1", mod.references().get(0))); seg.verify(); }
SortByProxyBlock(Node def) throws RuleBaseException { super(def); if (!"corresponding".equals(def.query().single("@as").value())) throw new RuleBaseException("sort block @as must have value 'corresponding'"); }