Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpelder2009-03-16 10:42:30 -0400
committerpelder2009-03-16 10:42:30 -0400
commit68597a7cc2cbac795a42be1c36cd7f0ae35a691a (patch)
treea660460b529488e9556950ad4e6aa731e87db0a3
parente205df27c429e06e051423649658d1d00aa9b744 (diff)
downloadorg.eclipse.jet-68597a7cc2cbac795a42be1c36cd7f0ae35a691a.tar.gz
org.eclipse.jet-68597a7cc2cbac795a42be1c36cd7f0ae35a691a.tar.xz
org.eclipse.jet-68597a7cc2cbac795a42be1c36cd7f0ae35a691a.zip
[268793] Make navigating recursive structures easier
-rw-r--r--doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepContentTag.html63
-rw-r--r--doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepIterateTag.html114
-rw-r--r--doc/org.eclipse.jet.doc/references/taglibs/controlTags/overview.html14
-rw-r--r--doc/org.eclipse.jet.doc/toccontrolTags.xml2
-rw-r--r--plugins/org.eclipse.jet/plugin.xml94
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/DocumentHelper.java102
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/AbstrateDeepIterateStrategy.java115
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/BreadthFirstStrategy.java91
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepContentTag.java54
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateEntry.java96
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateTag.java460
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIteratorStrategyBuilder.java159
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IDeepIterateStrategy.java46
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IteratingTagStatus.java131
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/Messages.java43
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/PreorderStrategy.java95
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/TagFactory.java8
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/messages.properties15
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/java/ImportsLocationTag.java8
19 files changed, 1700 insertions, 10 deletions
diff --git a/doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepContentTag.html b/doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepContentTag.html
new file mode 100644
index 0000000..235b07b
--- /dev/null
+++ b/doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepContentTag.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<META name="GENERATOR" content="IBM Software Development Platform">
+<link rel="STYLESHEET" href="../../../book.css" charset="ISO-8859-1" type="text/css"/>
+<TITLE>&lt;c:deepContent&gt; - Standard JET2 Control Tags</TITLE>
+</HEAD>
+<BODY>
+<TABLE border="0" cellpadding="0" cellspacing="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH align="left">
+ <P>&lt;c:deepContent&gt;</P></TH>
+ <TH align="right"><A href="overview.html">Standard JET2 Control Tags</A></TH>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<P><B>deepContent</B></P>
+<P><P> Designate the insertion point for child content generated from a c:deepIterate tag.<P><P>This tag may only be used if the containing c:deepIterate has no 'filter' and is doing a depth first traversal. Other cases will result in a runtime error.<P> </P>
+<HR>
+<TABLE border="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Tag Summary</TH></TR>
+ <TR>
+ <TD width="10%">required</TD>
+ <TD><SPAN style="font-family: monospace">
+ &lt;c:deepContent/&gt;<br>
+ </SPAN></TD>
+ </TR>
+ <TR>
+ <TD width="10%">full tag</TD>
+ <TD><SPAN style="font-family: monospace">
+ &lt;c:deepContent/&gt;<br>
+ </SPAN></TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Example</TH></TR>
+ <TR>
+ <TD width="10%"></TD>
+ <TD><SPAN style="font-family: monospace">
+<!-- Begin deepContent example -->
+<pre>
+&lt;c:deepIterate select="node" initialContext="/root" indent =" " &gt;
+Node &lt;c:get select="@name"/&gt; {
+ &lt;c:deepContent/&gt;
+} // &lt;c:get select="@name"/&gt;
+&lt;/c:deepIterate&gt;
+</pre>
+<!-- End deepContent example -->
+</SPAN></TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<P></P></BODY>
+</HTML>
diff --git a/doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepIterateTag.html b/doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepIterateTag.html
new file mode 100644
index 0000000..3c73127
--- /dev/null
+++ b/doc/org.eclipse.jet.doc/references/taglibs/controlTags/deepIterateTag.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<META name="GENERATOR" content="IBM Software Development Platform">
+<link rel="STYLESHEET" href="../../../book.css" charset="ISO-8859-1" type="text/css"/>
+<TITLE>&lt;c:deepIterate&gt; - Standard JET2 Control Tags</TITLE>
+</HEAD>
+<BODY>
+<TABLE border="0" cellpadding="0" cellspacing="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH align="left">
+ <P>&lt;c:deepIterate&gt;</P></TH>
+ <TH align="right"><A href="overview.html">Standard JET2 Control Tags</A></TH>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<P><B>deepIterate</B></P>
+<P><P> Do a deep (recusive) iteration by repeated evaluating the 'select' expression on previous results.<P> </P>
+<HR>
+<TABLE border="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Tag Summary</TH></TR>
+ <TR>
+ <TD width="10%">required</TD>
+ <TD><SPAN style="font-family: monospace">
+ &lt;c:deepIterate select=&quot;<I>value</I>&quot;&gt;<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<I>content to be repeated for each iteration</I><BR>
+ &lt;/c:deepIterate&gt;
+ </SPAN></TD>
+ </TR>
+ <TR>
+ <TD width="10%">full tag</TD>
+ <TD><SPAN style="font-family: monospace">
+ &lt;c:deepIterate select=&quot;<I>value</I>&quot; filter=&quot;<I>value</I>&quot; initialContext=&quot;<I>value</I>&quot; var=&quot;<I>value</I>&quot; delimiter=&quot;<I>value</I>&quot; indent=&quot;<I>value</I>&quot; varStatus=&quot;<I>value</I>&quot; allowDuplicates=&quot;<I>value</I>&quot; traversal=&quot;<I>value</I>&quot;&gt;<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<I>content to be repeated for each iteration</I><BR>
+ &lt;/c:deepIterate&gt;
+ </SPAN></TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Required Attributes</TH></TR>
+ <TR>
+ <TD width="10%">select</TD>
+ <TD><BR> The XPath selection that selects nodes. The expression should be relative.<BR> </TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Optional Attributes</TH></TR>
+ <TR>
+ <TR>
+ <TD width="10%">filter</TD>
+ <TD><BR> An optional XPath expression that remove elements after the deep traversal.<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">initialContext</TD>
+ <TD><BR> Optional XPath expression defining the inital context object for the 'select' expression. If omitted, then the current XPath context object is used.<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">var</TD>
+ <TD><BR> Optional XPath variable name that will refer to the XPath context object during traversal, and to the current object during each expansion of the tags contents.<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">delimiter</TD>
+ <TD><BR> Optional string that will be inserted after each expansion of the tag's body, except the last.<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">indent</TD>
+ <TD><BR> Optional string that will used to prefix each line in the expanded tag content. The number of times the text is inserted will depend on the depth of the current item in the traversal. The top most item will have no indent. Each deeper level will have one more insertion of the indent text that is parent level.<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">varStatus</TD>
+ <TD><BR> Optional variable containing information on the current item in the traversal. The status object has the following attributes: <BR><BR>@depth - a one-base index of the items depth in the traversal<BR>@isLeaf - defined if and only if the item is a leaf node. Not defined during evaluation of the 'select' attribute<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">allowDuplicates</TD>
+ <TD><BR> Optional parameter indicating whether the traversal should allow the same node to be traversed more than once (so long as it does not resulting in a loop). Default is 'true'.<BR> </TD>
+ </TR>
+ <TR>
+ <TD width="10%">traversal</TD>
+ <TD><BR> Optional parameter indicating the type of traversal. Possible values are 'depthFirst' and ' breadthFirst'. Default is 'depthFirst'.<BR> </TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Example</TH></TR>
+ <TR>
+ <TD width="10%"></TD>
+ <TD><SPAN style="font-family: monospace">
+<!-- Begin deepIterate example -->
+ &lt;c:deepIterate select=&quot;<I>value</I>&quot;&gt;<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<I>content to be repeated for each iteration</I><BR>
+ &lt;/c:deepIterate&gt;
+<!-- End deepIterate example -->
+</SPAN></TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<P></P></BODY>
+</HTML>
diff --git a/doc/org.eclipse.jet.doc/references/taglibs/controlTags/overview.html b/doc/org.eclipse.jet.doc/references/taglibs/controlTags/overview.html
index 6ac57cf..5705ae5 100644
--- a/doc/org.eclipse.jet.doc/references/taglibs/controlTags/overview.html
+++ b/doc/org.eclipse.jet.doc/references/taglibs/controlTags/overview.html
@@ -60,6 +60,20 @@
</TD>
</TR>
<TR>
+ <TD width="10%"><A href="deepContentTag.html">&lt;c:deepContent&gt;</A></TD>
+ <TD><!-- Begin deepContent description -->
+ <P> Designate the insertion point for child content generated from a c:deepIterate tag
+ <!-- End deepContent description -->
+ </TD>
+ </TR>
+ <TR>
+ <TD width="10%"><A href="deepIterateTag.html">&lt;c:deepIterate&gt;</A></TD>
+ <TD><!-- Begin deepIterate description -->
+ <P> Do a deep (recusive) iteration by repeated evaluating the 'select' expression on previous results
+ <!-- End deepIterate description -->
+ </TD>
+ </TR>
+ <TR>
<TD width="10%"><A href="dumpTag.html">&lt;c:dump&gt;</A></TD>
<TD><!-- Begin dump description -->
Writes out the DOM subtree under a specified model node
diff --git a/doc/org.eclipse.jet.doc/toccontrolTags.xml b/doc/org.eclipse.jet.doc/toccontrolTags.xml
index b10711f..3c5fc1d 100644
--- a/doc/org.eclipse.jet.doc/toccontrolTags.xml
+++ b/doc/org.eclipse.jet.doc/toccontrolTags.xml
@@ -31,5 +31,7 @@
<topic label="c:visitor" href="references/taglibs/controlTags/visitorTag.html"/>
<topic label="c:when" href="references/taglibs/controlTags/whenTag.html"/>
<topic label="c:with" href="references/taglibs/controlTags/withTag.html"/>
+ <topic label="c:deepIterate" href="references/taglibs/controlTags/deepIterateTag.html"/>
+ <topic label="c:deepContent" href="references/taglibs/controlTags/deepContentTag.html"/>
</topic>
</toc>
diff --git a/plugins/org.eclipse.jet/plugin.xml b/plugins/org.eclipse.jet/plugin.xml
index 973ef2d..b4a59b8 100644
--- a/plugins/org.eclipse.jet/plugin.xml
+++ b/plugins/org.eclipse.jet/plugin.xml
@@ -584,6 +584,100 @@ Optionally, an XPath variable may be set by providing a &apos;var&apos; attribut
</description>
</attribute>
</containerTag>
+ <iteratingTag
+ class="org.eclipse.jet.internal.taglib.control.DeepIterateTag"
+ name="deepIterate"
+ processContents="custom"
+ whenContainingLineIsEmpty="remove">
+ <description>
+ Do a deep (recusive) iteration by repeated evaluating the 'select' expression on previous results.
+ </description>
+ <attribute
+ name="select"
+ type="xpath"
+ use="required">
+ <description>
+ The XPath selection that selects nodes. The expression should be relative.
+ </description>
+ </attribute>
+ <attribute
+ name="filter"
+ type="xpath"
+ use="optional">
+ <description>
+ An optional XPath expression that remove elements after the deep traversal.
+ </description>
+ </attribute>
+ <attribute
+ name="initialContext"
+ type="xpath"
+ use="optional">
+ <description>
+ Optional XPath expression defining the inital context object for the 'select' expression. If omitted, then the current XPath context object is used.
+ </description>
+ </attribute>
+ <attribute
+ name="var"
+ type="string"
+ use="optional">
+ <description>
+ Optional XPath variable name that will refer to the XPath context object during traversal, and to the current object during each expansion of the tags contents.
+ </description>
+ </attribute>
+ <attribute
+ name="delimiter"
+ type="string"
+ use="optional">
+ <description>
+ Optional string that will be inserted after each expansion of the tag's body, except the last.
+ </description>
+ </attribute>
+ <attribute
+ name="indent"
+ type="string"
+ use="optional">
+ <description>
+ Optional string that will used to prefix each line in the expanded tag content. The number of times the text is inserted will depend on the depth of the current item in the traversal. The top most item will have no indent. Each deeper level will have one more insertion of the indent text that is parent level.
+ </description>
+ </attribute>
+ <attribute
+ name="varStatus"
+ type="string"
+ use="optional">
+ <description>
+ Optional variable containing information on the current item in the traversal. The status object has the following attributes:
+
+@depth - a one-base index of the items depth in the traversal
+@isLeaf - defined if and only if the item is a leaf node. Not defined during evaluation of the 'select' attribute
+ </description>
+ </attribute>
+ <attribute
+ name="allowDuplicates"
+ type="boolean"
+ use="optional">
+ <description>
+ Optional parameter indicating whether the traversal should allow the same node to be traversed more than once (so long as it does not resulting in a loop). Default is 'true'.
+ </description>
+ </attribute>
+ <attribute
+ name="traversal"
+ type="string"
+ use="optional">
+ <description>
+ Optional parameter indicating the type of traversal. Possible values are 'depthFirst' and ' breadthFirst'. Default is 'depthFirst'.
+ </description>
+ </attribute>
+ </iteratingTag>
+ <emptyTag
+ class="org.eclipse.jet.internal.taglib.control.DeepContentTag"
+ name="deepContent"
+ whenContainingLineIsEmpty="remove">
+ <description>
+ Designate the insertion point for child content generated from a c:deepIterate tag.
+
+This tag may only be used if the containing c:deepIterate has no &apos;filter&apos; and is doing a depth first traversal. Other cases will result in a runtime error.
+ </description>
+ </emptyTag>
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/DocumentHelper.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/DocumentHelper.java
index 23cbb04..f9fc0ef 100644
--- a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/DocumentHelper.java
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/DocumentHelper.java
@@ -1,7 +1,7 @@
/**
* <copyright>
*
- * Copyright (c) 2007 IBM Corporation and others.
+ * Copyright (c) 2007, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -12,18 +12,85 @@
*
* </copyright>
*
- * $Id: DocumentHelper.java,v 1.1 2007/04/30 17:33:08 pelder Exp $
+ * $Id: DocumentHelper.java,v 1.2 2009/03/16 14:42:30 pelder Exp $
*/
package org.eclipse.jet.internal.taglib;
+import org.eclipse.jet.BufferedJET2Writer;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Position;
/**
* Helper class for handling IDocument instances
*/
public class DocumentHelper
{
+ /**
+ * Optional interface on a {@link Position} that allows the position
+ * to control how it is updated rather than a position updater
+ */
+ public interface IPositionUpdaterOverride {
+ /**
+ * Allow the position to update itself
+ * @param changeOffset the offset of the change occuring
+ * @param changeLength the original length of the changed text
+ * @param replacementLength the length of the new text
+ * @return <code>true</code> if the position was updated and the default updater should not be used.
+ */
+ public boolean update(int changeOffset, int changeLenghth, int replacementLength);
+ }
+
+ /**
+ * Implement a zero-length position that stays in front of an insertion at its position.
+ * @see IPositionUpdaterOverride
+ * @see OverrideablePositionUpdater
+ */
+ public static class InsertAfterEmptyPosition extends Position implements IPositionUpdaterOverride {
+
+ public InsertAfterEmptyPosition(int length)
+ {
+ super(length);
+ }
+
+ public boolean update(int changeOffset, int changeLength, int replacementLength)
+ {
+ // if this position is empty, and the changes is inserting text at this position,
+ // then ensure the change is inserted after this position, rather then the default
+ // of shifting this position so that it is after the inserted text.
+ // Note: no fields need to be updated!
+ return changeLength == 0 && getLength() == 0 && changeOffset == getOffset();
+ }
+
+ }
+ /**
+ * An extension of {@link DefaultPositionUpdater} that respects positions that
+ * implement {@link IPositionUpdaterOverride}.
+ */
+ public static class OverrideablePositionUpdater extends DefaultPositionUpdater {
+
+ /**
+ * @see DefaultPositionUpdater#DefaultPositionUpdater(String)
+ */
+ public OverrideablePositionUpdater(String category)
+ {
+ super(category);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.text.DefaultPositionUpdater#adaptToReplace()
+ */
+ protected void adaptToReplace()
+ {
+ if(fPosition instanceof IPositionUpdaterOverride
+ && ((IPositionUpdaterOverride)fPosition).update(fOffset, fLength, fReplaceLength)) {
+ return;
+ }
+ super.adaptToReplace();
+ }
+ }
private DocumentHelper() {
// prevent instantiation.
@@ -31,6 +98,8 @@ public class DocumentHelper
/**
* Install the position category if it is not already installed.
+ * The position category is configured with a {@link OverrideablePositionUpdater}
+ * so that positions may control their updating by implementing {@link IPositionUpdaterOverride}.
* @param document the document into which the category should be installed
* @param category the category.
*/
@@ -38,7 +107,34 @@ public class DocumentHelper
{
if(!document.containsPositionCategory(category)) {
document.addPositionCategory(category);
- document.addPositionUpdater(new DefaultPositionUpdater(category));
+ document.addPositionUpdater(new OverrideablePositionUpdater(category));
+ }
+ }
+
+ public static void indent(BufferedJET2Writer bodyContent, int depth, String indent) {
+ final char[] indentArray = indent.toCharArray();
+ final int indentLength = indentArray.length;
+
+ final char fullIndent[] = new char[indentLength * depth];
+ for(int i = 0; i < depth; i++) {
+ System.arraycopy(indentArray, 0, fullIndent, i * indentLength, indentLength);
+ }
+ final String indentation = new String(fullIndent);
+
+ final IDocument document = (IDocument)bodyContent.getAdapter(IDocument.class);
+ final int numberOfLines = document.getNumberOfLines();
+ for(int line = 0; line < numberOfLines; line++) {
+ try {
+ final IRegion lineInformation = document.getLineInformation(line);
+ if(lineInformation.getLength() > 0 || line < numberOfLines - 1) {
+ document.replace(lineInformation.getOffset(), 0, indentation);
+ }
+ } catch (BadLocationException e) {
+ final RuntimeException rte = new RuntimeException("Unexpected exception: line = " + line ); //$NON-NLS-1$
+ rte.initCause(e);
+ throw rte;
+ }
+
}
}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/AbstrateDeepIterateStrategy.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/AbstrateDeepIterateStrategy.java
new file mode 100644
index 0000000..623b1ad
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/AbstrateDeepIterateStrategy.java
@@ -0,0 +1,115 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: AbstrateDeepIterateStrategy.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.jet.JET2Context;
+import org.eclipse.jet.XPathContextExtender;
+
+
+/**
+ * Abstract implementation of {@link IDeepIterateStrategy}.
+ */
+public abstract class AbstrateDeepIterateStrategy implements IDeepIterateStrategy
+{
+
+ protected final JET2Context context;
+ protected final XPathContextExtender xpc;
+ protected final String select;
+ protected final String filter;
+ protected final String var;
+ protected Set loopDetectionStack;
+ protected Collection entries;
+ protected final boolean allowDuplicates;
+ protected final Object initialContextObject;
+ protected final String deepIterateTagName;
+ protected final String varStatus;
+
+ public AbstrateDeepIterateStrategy(DeepIteratorStrategyBuilder builder)
+ {
+ context = builder.getContext();
+ xpc = XPathContextExtender.getInstance(context);
+ select = builder.getSelect();
+ filter = builder.getFilter();
+ var = builder.getVar();
+ varStatus = builder.getVarStatus();
+ allowDuplicates = builder.isAllowDuplicates();
+ initialContextObject = builder.getContextObject();
+ deepIterateTagName = builder.getTagName();
+ }
+
+ protected boolean satisfiesFilter(DeepIterateEntry entry)
+ {
+ if (filter != null && var != null)
+ {
+ context.setVariable(var, entry.getObject());
+ }
+ if(filter != null && varStatus != null) {
+ context.setVariable(varStatus, new DeepIterateTag.LoopStatus(entry.getDepth(), entry.isLeaf()));
+ }
+ return filter != null ? xpc.resolveTest(entry.getObject(), filter) : true;
+ }
+
+ protected boolean createsRecursiveLoop(DeepIterateEntry entry)
+ {
+ return loopDetectionStack.contains(entry);
+ }
+
+ protected boolean isDuplicateEntry(DeepIterateEntry n)
+ {
+
+ return !allowDuplicates && entries.contains(n);
+ }
+
+ protected Object[] selectNodes(Object contextObject, int depth)
+ {
+ if (var != null)
+ {
+ context.setVariable(var, contextObject);
+ }
+ if(varStatus != null) {
+ context.setVariable(varStatus, new DeepIterateTag.LoopStatus(depth));
+ }
+ return xpc.resolve(contextObject, select);
+ }
+
+ protected abstract void doSearch();
+
+ public final Collection search()
+ {
+ try
+ {
+ // entries = allowDuplicates ? (Collection)new LinkedHashSet() : (Collection)new LinkedList();
+ // for performance on removal during filtering, always use a Linked HashSet()
+ entries = new LinkedHashSet();
+ loopDetectionStack = new HashSet();
+ doSearch();
+ return entries;
+ }
+ finally
+ {
+ // clean up
+ loopDetectionStack = null;
+ entries = null;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/BreadthFirstStrategy.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/BreadthFirstStrategy.java
new file mode 100644
index 0000000..b9a6d77
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/BreadthFirstStrategy.java
@@ -0,0 +1,91 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: BreadthFirstStrategy.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.eclipse.jet.taglib.JET2TagException;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Implement a bread first deep traversal strategy
+ */
+public class BreadthFirstStrategy extends AbstrateDeepIterateStrategy implements IDeepIterateStrategy
+{
+
+ public BreadthFirstStrategy(DeepIteratorStrategyBuilder builder)
+ {
+ super(builder);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.internal.taglib.control.IDeepIterateStrategy#checkDeepContentAllowed()
+ */
+ public void checkDeepContentAllowed() throws JET2TagException
+ {
+ if(!supportsDeepContent()) {
+ throw new JET2TagException(NLS.bind(Messages.BreadthFirstStrategy_DeepContentNotAllowed, deepIterateTagName, "breadthFirst")); //$NON-NLS-1$
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.internal.taglib.control.IDeepIterateStrategy#supportsDeepContent()
+ */
+ public boolean supportsDeepContent()
+ {
+ return false;
+ }
+
+ protected void doSearch()
+ {
+ LinkedList queue = new LinkedList();
+
+ DeepIterateEntry initialEntry = new DeepIterateEntry(initialContextObject, 0);
+ queue.add(initialEntry);
+
+ while(queue.size() > 0) {
+ final DeepIterateEntry entry = (DeepIterateEntry)queue.removeFirst();
+
+ // process the entry...
+ if(entry.getDepth() > 0) {
+ entries.add(entry);
+ }
+
+ boolean hasChildren = false;
+ final Object[] nodes = this.selectNodes(entry.getObject(), entry.getDepth() + 1);
+ for (int i = 0; i < nodes.length; i++)
+ {
+ final DeepIterateEntry child = new DeepIterateEntry(nodes[i], entry.getDepth() + 1);
+ if(!this.isDuplicateEntry(child)) {
+ queue.addLast(child);
+ hasChildren = true;
+ }
+ }
+ entry.setLeaf(!hasChildren);
+ }
+
+ for (Iterator i = entries.iterator(); i.hasNext();)
+ {
+ DeepIterateEntry entry = (DeepIterateEntry)i.next();
+ if(!this.satisfiesFilter(entry)) {
+ i.remove();
+ }
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepContentTag.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepContentTag.java
new file mode 100644
index 0000000..8306b1d
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepContentTag.java
@@ -0,0 +1,54 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: DeepContentTag.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import org.eclipse.jet.BufferedJET2Writer;
+import org.eclipse.jet.JET2Context;
+import org.eclipse.jet.JET2Writer;
+import org.eclipse.jet.taglib.AbstractEmptyTag;
+import org.eclipse.jet.taglib.CustomTag;
+import org.eclipse.jet.taglib.JET2TagException;
+import org.eclipse.jet.taglib.TagInfo;
+
+/**
+ * @author pelder
+ */
+public class DeepContentTag extends AbstractEmptyTag
+{
+
+ /**
+ *
+ */
+ public DeepContentTag()
+ {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.taglib.EmptyTag#doAction(org.eclipse.jet.taglib.TagInfo, org.eclipse.jet.JET2Context, org.eclipse.jet.JET2Writer)
+ */
+ public void doAction(TagInfo td, JET2Context context, JET2Writer out) throws JET2TagException
+ {
+ for(CustomTag parent = getParent(); parent != null; parent = parent.getParent()) {
+ if(parent instanceof DeepIterateTag) {
+ ((DeepIterateTag)parent).markContentInsertionPoint((BufferedJET2Writer)out);
+ return;
+ }
+ }
+ throw new JET2TagException(Messages.DeepContentTag_MustBeContainedByDeepIterate);
+ }
+
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateEntry.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateEntry.java
new file mode 100644
index 0000000..d7a931f
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateEntry.java
@@ -0,0 +1,96 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: DeepIterateEntry.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+public class DeepIterateEntry {
+
+ private final Object object;
+ private final int depth;
+ private boolean leaf;
+
+ public DeepIterateEntry(Object object, int depth) {
+ this.object = object;
+ this.depth = depth;
+ }
+
+ /**
+ * @return Returns the depth.
+ */
+ public int getDepth()
+ {
+ return depth;
+ }
+
+ /**
+ * @return Returns the object.
+ */
+ public Object getObject()
+ {
+ return object;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((object == null) ? 0 : object.hashCode());
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DeepIterateEntry other = (DeepIterateEntry)obj;
+ if (object == null)
+ {
+ if (other.object != null)
+ return false;
+ }
+ else if (!object.equals(other.object))
+ return false;
+ return true;
+ }
+
+ /**
+ * @return Returns the leaf.
+ */
+ final boolean isLeaf()
+ {
+ return leaf;
+ }
+
+ /**
+ * @param leaf The leaf to set.
+ */
+ final void setLeaf(boolean leaf)
+ {
+ this.leaf = leaf;
+ }
+
+
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateTag.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateTag.java
new file mode 100644
index 0000000..f84a66f
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIterateTag.java
@@ -0,0 +1,460 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: DeepIterateTag.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.eclipse.jet.BufferedJET2Writer;
+import org.eclipse.jet.JET2Context;
+import org.eclipse.jet.JET2Writer;
+import org.eclipse.jet.XPathContextExtender;
+import org.eclipse.jet.internal.InternalJET2Platform;
+import org.eclipse.jet.internal.taglib.DocumentHelper;
+import org.eclipse.jet.taglib.AbstractIteratingTag;
+import org.eclipse.jet.taglib.JET2TagException;
+import org.eclipse.jet.taglib.TagInfo;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultPositionUpdater;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.Position;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Implement &lt;c:deepIterate&gt; tag.
+ */
+public class DeepIterateTag extends AbstractIteratingTag
+{
+
+ /**
+ * @author pelder
+ */
+ static class LoopStatus extends DeepIterateTag
+ {
+ private final int depth;
+
+ private boolean leaf;
+
+ public LoopStatus(int depth) {
+ this.depth = depth;
+
+ }
+
+ public LoopStatus(int depth, boolean leaf)
+ {
+ this.depth = depth;
+ this.leaf = leaf;
+ }
+
+ /**
+ * @return Returns the depth.
+ */
+ final int getDepth()
+ {
+ return depth;
+ }
+
+ /**
+ * @param leaf The leaf to set.
+ */
+ public void setLeaf(boolean leaf)
+ {
+ this.leaf = leaf;
+ }
+
+ /**
+ * @return Returns the leaf.
+ */
+ public boolean isLeaf()
+ {
+ return leaf;
+ }
+
+ public Object asInspectableObject() {
+ try
+ {
+ final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ final Element status = document.createElement("status"); //$NON-NLS-1$
+ document.appendChild(status);
+ status.setAttribute("depth", Integer.toString(depth)); //$NON-NLS-1$
+ if(leaf) {
+ status.setAttribute("isLeaf", "true"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ return status;
+ }
+ catch (DOMException e)
+ {
+ // shouldn't happen - all names passed are valid XML names...
+ throw new RuntimeException("Internal Error: An XML name is unexpectedly invalid", e); //$NON-NLS-1$
+ }
+ catch (ParserConfigurationException e)
+ {
+ // shouldn't happen - we're using defaults throughout
+ throw new RuntimeException("Internal Error: DocumentBuilder could not be created.", e); //$NON-NLS-1$
+ }
+ catch (FactoryConfigurationError e)
+ {
+ // shouldn't happen...
+ throw new RuntimeException("Internal Error: DocumentBuilderFactory could not be created.", e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Track location of nested &lt;c:deepContent&gt; tags
+ */
+ private static final class DeepContentPositionTracker {
+
+ private List insertPositions = new ArrayList();
+
+ public DeepContentPositionTracker() {
+ insertPositions.add(null);
+ }
+ /**
+ * @return Returns the currentInsertPosition.
+ */
+ final Position getInsertPosition()
+ {
+ final int size = insertPositions.size();
+ if(size < 2) {
+ throw new IllegalStateException("size = " + size); //$NON-NLS-1$
+ }
+ return (Position)insertPositions.get(size - 2);
+ }
+
+ final void initDepth(int newDepth) {
+ final int oldDepth = insertPositions.size() - 1;
+ if(newDepth < 1 || newDepth > oldDepth + 1) {
+ throw new IllegalArgumentException("newdepth = " + newDepth + ", oldDepth = " + oldDepth); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ // clean-up no longer accessible insert positions...
+ for(int i = oldDepth; i >= newDepth; i--) {
+ insertPositions.remove(i);
+ }
+ insertPositions.add(null); // initially now position at this level...
+ }
+
+ final void setPosition(Position position) {
+ insertPositions.set(insertPositions.size() - 1, position);
+ }
+
+ }
+
+ private static final String INSERTION_POINT_CATEGORY = DeepIterateTag.class.getName() + ".INSERT_POSITION"; //$NON-NLS-1$
+ private String tagName;
+ private String var;
+ private String varStatus;
+ private Iterator iterator;
+ private Object savedVarValue;
+ private Object savedVarStatusValue;
+ private String indent;
+ private DeepIterateEntry currentEntry;
+
+ private DeepContentPositionTracker deepContentPositionTracker;
+
+ private BufferedJET2Writer workingOutput;
+ private IDeepIterateStrategy strategy;
+
+ /**
+ *
+ */
+ public DeepIterateTag()
+ {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.taglib.IteratingTag#doInitializeLoop(org.eclipse.jet.taglib.TagInfo, org.eclipse.jet.JET2Context)
+ */
+ public void doInitializeLoop(TagInfo td, JET2Context context) throws JET2TagException
+ {
+ // get attribute values
+ tagName = td.getTagName();
+ var = getAttribute("var"); //$NON-NLS-1$
+ varStatus = getAttribute("varStatus"); //$NON-NLS-1$
+ indent = getAttribute("indent"); //$NON-NLS-1$
+
+ // save variables we will be setting...
+ savedVarValue = saveVariable(context, var);
+ savedVarStatusValue = saveVariable(context, varStatus);
+
+ // build result set...
+ DeepIteratorStrategyBuilder builder = new DeepIteratorStrategyBuilder(context, tagName, getAttribute("select")) //$NON-NLS-1$
+ .var(var)
+ .traversal(getAttribute("traversal")) //$NON-NLS-1$
+ .varStatus(varStatus)
+ .allowDuplicates(td.hasAttribute("allowDuplicates") ? Boolean.getBoolean(td.getAttribute("allowDuplicates")) : true) //$NON-NLS-1$ //$NON-NLS-2$
+ .filter(getAttribute("filter")); //$NON-NLS-1$
+
+ final String contextSelect = getAttribute("initialContext"); //$NON-NLS-1$
+ if(contextSelect != null) {
+ final XPathContextExtender xpc = XPathContextExtender.getInstance(context);
+ builder.contextObject(xpc.resolveSingle(contextSelect));
+ }
+
+ strategy = builder.build();
+
+ final Collection result = strategy.search();
+
+ // setup for nested deepContent tags...
+ if(strategy.supportsDeepContent()) {
+ deepContentPositionTracker = new DeepContentPositionTracker();
+ }
+
+ // initialize the working output...
+ initWorkingOutput();
+
+ // get a result iterator
+ iterator = result.iterator();
+ }
+
+ /**
+ * @param context
+ * @param varName
+ * @return
+ * @throws JET2TagException
+ */
+ private Object saveVariable(JET2Context context, String varName) throws JET2TagException
+ {
+ Object savedValue = null;
+ if(varName != null) {
+ savedValue = context.hasVariable(varName) ? context.getVariable(varName) : null;
+ }
+ return savedValue;
+ }
+
+ /**
+ *
+ */
+ private void initWorkingOutput()
+ {
+ workingOutput = (BufferedJET2Writer)getOut().newNestedContentWriter();
+ IDocument doc = (IDocument)workingOutput.getAdapter(IDocument.class);
+ doc.addPositionCategory(INSERTION_POINT_CATEGORY);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.taglib.IteratingTag#doEvalLoopCondition(org.eclipse.jet.taglib.TagInfo, org.eclipse.jet.JET2Context)
+ */
+ public boolean doEvalLoopCondition(TagInfo td, JET2Context context) throws JET2TagException
+ {
+ final boolean hasNext = iterator.hasNext();
+ if(!hasNext) {
+ restoreVariable(context, var, savedVarValue);
+ restoreVariable(context, varStatus, savedVarStatusValue);
+ }
+ if(!hasNext) {
+ flushWorkingOutput();
+ }
+ return hasNext;
+ }
+
+ /**
+ * @param context
+ * @param varName
+ * @param savedValue
+ * @throws JET2TagException
+ */
+ private void restoreVariable(JET2Context context, String varName, Object savedValue) throws JET2TagException
+ {
+ if(varName != null) {
+ if(savedValue != null) {
+ context.setVariable(varName, savedValue);
+ } else {
+ context.removeVariable(varName);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private void flushWorkingOutput()
+ {
+ IDocument doc = (IDocument)workingOutput.getAdapter(IDocument.class);
+ try
+ {
+ doc.removePositionCategory(INSERTION_POINT_CATEGORY);
+ }
+ catch (BadPositionCategoryException e)
+ {
+ // shouldn't happen
+ throw new RuntimeException("Internal Error: Position category has not been added: " + INSERTION_POINT_CATEGORY, e); //$NON-NLS-1$
+ }
+ // flush working output...
+ getOut().write(workingOutput);
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.taglib.AbstractIteratingTag#doBeforeBody(org.eclipse.jet.taglib.TagInfo, org.eclipse.jet.JET2Context, org.eclipse.jet.JET2Writer)
+ */
+ public void doBeforeBody(TagInfo td, JET2Context context, JET2Writer out) throws JET2TagException
+ {
+ super.doBeforeBody(td, context, out);
+
+ currentEntry = (DeepIterateEntry)iterator.next();
+
+ // ready insert position tracker, if it exists...
+ if(deepContentPositionTracker != null) {
+ deepContentPositionTracker.initDepth(currentEntry.getDepth());
+ }
+
+ // setup context object
+ final XPathContextExtender xpc = XPathContextExtender.getInstance(context);
+ xpc.pushXPathContextObject(currentEntry.getObject());
+
+ setVariable(context, var, currentEntry.getObject());
+ if(varStatus != null) {
+ LoopStatus status = new LoopStatus(currentEntry.getDepth(), currentEntry.isLeaf());
+ setVariable(context, varStatus, status.asInspectableObject());
+ }
+ }
+
+ /**
+ * @param context
+ * @param varName
+ * @param varValue
+ * @throws JET2TagException
+ */
+ private void setVariable(JET2Context context, String varName, Object varValue) throws JET2TagException
+ {
+ if(varName != null) {
+ context.setVariable(varName, varValue);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.taglib.AbstractIteratingTag#doAfterBody(org.eclipse.jet.taglib.TagInfo, org.eclipse.jet.JET2Context, org.eclipse.jet.JET2Writer)
+ */
+ public void doAfterBody(TagInfo td, JET2Context context, JET2Writer out) throws JET2TagException
+ {
+ super.doAfterBody(td, context, out);
+
+ final XPathContextExtender xpc = XPathContextExtender.getInstance(context);
+ xpc.popXPathContextObject();
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.taglib.AbstractContainerTag#setBodyContent(org.eclipse.jet.JET2Writer)
+ */
+ public void setBodyContent(JET2Writer bodyContent)
+ {
+ if(indent != null) {
+ DocumentHelper.indent((BufferedJET2Writer)bodyContent, currentEntry.getDepth() - 1, indent);
+ }
+ Position insertPosition = deepContentPositionTracker != null
+ ? deepContentPositionTracker.getInsertPosition()
+ : null;
+ if(insertPosition != null) {
+ replaceContent(workingOutput, insertPosition.getOffset(), 0, (BufferedJET2Writer)bodyContent);
+ } else {
+ workingOutput.write(bodyContent);
+ }
+ }
+
+ private void replaceContent(BufferedJET2Writer targetWriter, int offset, int length, BufferedJET2Writer sourceWriter)
+ {
+ final IDocument targetDocument = (IDocument)targetWriter.getAdapter(IDocument.class);
+ targetWriter.replaceContent(offset, length, sourceWriter.getContent());
+ try
+ {
+ // transfer position categories, position updaters and positions
+ final IDocument sourceDocument = (IDocument)sourceWriter.getAdapter(IDocument.class);
+
+ // Position updaters should not be copied, instead, they will be added as part of adding
+ // any new categories.
+ final String categories[] = sourceDocument.getPositionCategories();
+ for (int i = 0; i < categories.length; i++)
+ {
+ // ensure 'document' has the category - no harm in doing this if the category is already there
+ DocumentHelper.installPositionCategory(targetDocument, categories[i]);
+ final Position[] positions = sourceDocument.getPositions(categories[i]);
+ for (int j = 0; j < positions.length; j++)
+ {
+ sourceDocument.removePosition(categories[i], positions[j]);
+ positions[j].setOffset(positions[j].getOffset() + offset);
+ targetDocument.addPosition(categories[i], positions[j]);
+ }
+ }
+ }
+ catch (BadPositionCategoryException e)
+ {
+ // this exception should not occur in this circumstance. We are getting categories
+ // from the document we are applying them against, so we should never encounter problems.
+ InternalJET2Platform.logError("Internal Error", e); //$NON-NLS-1$
+ }
+ catch (BadLocationException e)
+ {
+ // this exception should not occur in this circumstance. If we are creating invalid positions
+ // then this is a sign of programming errors.
+ InternalJET2Platform.logError("Internal Error", e); //$NON-NLS-1$
+ }
+ }
+
+ public void markContentInsertionPoint(BufferedJET2Writer out) throws JET2TagException {
+
+ strategy.checkDeepContentAllowed();
+
+ // get the underlying document, and ensure it has the correct position category...
+ IDocument document = (IDocument)out.getAdapter(IDocument.class);
+ DocumentHelper.installPositionCategory(document, INSERTION_POINT_CATEGORY);
+ if(!document.containsPositionCategory(INSERTION_POINT_CATEGORY)) {
+ document.addPositionCategory(INSERTION_POINT_CATEGORY);
+ document.addPositionUpdater(new DefaultPositionUpdater(INSERTION_POINT_CATEGORY){
+ protected void adaptToReplace()
+ {
+ if(fOffset < fPosition.offset) {
+ fPosition.offset += fReplaceLength;
+ }
+ }
+ });
+ }
+
+ // add a position
+ final Position position = new DocumentHelper.InsertAfterEmptyPosition(document.getLength());
+ try
+ {
+ document.addPosition(INSERTION_POINT_CATEGORY, position);
+ }
+ catch (BadLocationException e)
+ {
+ // shouldn't happen
+ throw new RuntimeException("Internal Error: Unexpected invalid position: " + position, e); //$NON-NLS-1$
+ }
+ catch (BadPositionCategoryException e)
+ {
+ // shouldn't happen
+ throw new RuntimeException("Internal Error: Position category has not been added: " + INSERTION_POINT_CATEGORY, e); //$NON-NLS-1$
+ }
+
+ // remember the insert position
+
+ deepContentPositionTracker.setPosition(position);
+}
+
+
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIteratorStrategyBuilder.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIteratorStrategyBuilder.java
new file mode 100644
index 0000000..e468a4e
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/DeepIteratorStrategyBuilder.java
@@ -0,0 +1,159 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: DeepIteratorStrategyBuilder.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import org.eclipse.jet.JET2Context;
+import org.eclipse.jet.XPathContextExtender;
+import org.eclipse.jet.taglib.JET2TagException;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Factory for {@link IResultSet} implementations
+ */
+public class DeepIteratorStrategyBuilder
+{
+
+ private final JET2Context context;
+ private final String select;
+ private String var;
+ private String filter;
+ private Object contextObject;
+ private boolean allowDuplicates = true;
+ private String varStatus;
+ private final String tagName;
+ private static final int DEPTH_FIRST_TRAVERSAL = 1;
+ private static final int BREADTH_FIRST_TRAVERSAL = 2;
+ private int traversal = DEPTH_FIRST_TRAVERSAL;
+
+ public DeepIteratorStrategyBuilder(JET2Context context, String tagName, String select) {
+ this.context = context;
+ this.tagName = tagName;
+ this.select = select;
+ this.contextObject = XPathContextExtender.getInstance(context).currentXPathContextObject();
+ }
+
+ public DeepIteratorStrategyBuilder var(String var) {
+ this.var = var;
+ return this;
+ }
+
+ public DeepIteratorStrategyBuilder filter(String filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public DeepIteratorStrategyBuilder contextObject(Object contextObject) {
+ this.contextObject = contextObject;
+ return this;
+ }
+
+ public DeepIteratorStrategyBuilder allowDuplicates(boolean allowDuplicates)
+ {
+ this.allowDuplicates = allowDuplicates;
+ return this;
+ }
+
+ public IDeepIterateStrategy build() {
+ if(traversal == DEPTH_FIRST_TRAVERSAL) {
+ return new PreorderStrategy(this);
+ } else {
+ return new BreadthFirstStrategy(this);
+ }
+ }
+
+ /**
+ * @return Returns the context.
+ */
+ final JET2Context getContext()
+ {
+ return context;
+ }
+
+ /**
+ * @return Returns the select.
+ */
+ final String getSelect()
+ {
+ return select;
+ }
+
+ /**
+ * @return Returns the var.
+ */
+ final String getVar()
+ {
+ return var;
+ }
+
+ /**
+ * @return Returns the filter.
+ */
+ final String getFilter()
+ {
+ return filter;
+ }
+
+ /**
+ * @return Returns the contextObject.
+ */
+ final Object getContextObject()
+ {
+ return contextObject;
+ }
+
+ /**
+ * @return Returns the allowDuplicates.
+ */
+ final boolean isAllowDuplicates()
+ {
+ return traversal == DEPTH_FIRST_TRAVERSAL ? allowDuplicates : false;
+ }
+
+ public DeepIteratorStrategyBuilder varStatus(String varStatus)
+ {
+
+ this.varStatus = varStatus;
+ return this;
+ }
+
+ /**
+ * @return Returns the varStatus.
+ */
+ final String getVarStatus()
+ {
+ return varStatus;
+ }
+
+ final String getTagName()
+ {
+ return tagName;
+ }
+
+ public DeepIteratorStrategyBuilder traversal(String traversal) throws JET2TagException
+ {
+ if("depthFirst".equals(traversal)) { //$NON-NLS-1$
+ this.traversal = DEPTH_FIRST_TRAVERSAL;
+ } else if("breadthFirst".equals(traversal)) { //$NON-NLS-1$
+ this.traversal = BREADTH_FIRST_TRAVERSAL;
+ } else if (traversal != null){
+ throw new JET2TagException(NLS.bind(Messages.DeepIteratorStrategyBuilder_UnrecognizedTraversalStrategy,
+ new Object[] {traversal, "depthFirst", "breadthFirst"})); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ return this;
+ }
+
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IDeepIterateStrategy.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IDeepIterateStrategy.java
new file mode 100644
index 0000000..55b065a
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IDeepIterateStrategy.java
@@ -0,0 +1,46 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: IDeepIterateStrategy.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import java.util.Collection;
+
+import org.eclipse.jet.taglib.JET2TagException;
+
+/**
+ * @author pelder
+ */
+public interface IDeepIterateStrategy
+{
+ /**
+ * perform the deep iterate search
+ * @return a collection of {@link DeepIterateEntry} elements
+ */
+ Collection search();
+
+ /**
+ * Indicate whether the strategy supports &lt;c:deepContent&gt; tags.
+ * @return <code>true</code> if such tags are supported, <code>false</code> otherwise
+ */
+ boolean supportsDeepContent();
+
+ /**
+ * Throw an appropriately detailed {@link JET2TagException} if deep content is not allowed, otherwise
+ * return.
+ * @throws JET2TagException if deepContent is not allowed.
+ */
+ void checkDeepContentAllowed() throws JET2TagException;
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IteratingTagStatus.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IteratingTagStatus.java
new file mode 100644
index 0000000..1b51dca
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/IteratingTagStatus.java
@@ -0,0 +1,131 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: IteratingTagStatus.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * Object representing a &lt;c:deepIterate&gt; status object
+ */
+public class IteratingTagStatus
+{
+
+ private final long begin;
+
+ private final long count;
+
+ private final long end;
+
+ private long index;
+
+ private Element status;
+
+ public IteratingTagStatus(long begin, long count) {
+ this.begin = begin;
+ this.count = count;
+ this.end = begin + count - 1;
+ this.index = begin;
+ }
+
+ public IteratingTagStatus(int count) {
+ this(0, count);
+ }
+
+ public final long getIndex()
+ {
+ return index;
+ }
+
+ public final void next()
+ {
+ this.index += 1;
+ }
+
+ public final long getBegin()
+ {
+ return begin;
+ }
+
+ public final long getCount()
+ {
+ return count;
+ }
+
+ public final long getEnd()
+ {
+ return end;
+ }
+
+ public final boolean isFirst()
+ {
+ return index == begin;
+ }
+
+ public final boolean isLast()
+ {
+ return index == end;
+ }
+
+ public Object getStatusObject() {
+ if(status == null) {
+ final Document document = createDocument();
+ status = document.createElement("status"); //$NON-NLS-1$
+ }
+ status.setAttribute("begin", Long.toString(begin)); //$NON-NLS-1$
+ status.setAttribute("end", Long.toString(end)); //$NON-NLS-1$
+ status.setAttribute("index", Long.toString(index)); //$NON-NLS-1$
+ status.setAttribute("count", Long.toString(count)); //$NON-NLS-1$
+ if(isFirst()) {
+ status.setAttribute("isFirst", "true"); //$NON-NLS-1$//$NON-NLS-2$
+ } else {
+ status.removeAttribute("isFirst"); //$NON-NLS-1$
+ }
+ if(isLast()) {
+ status.setAttribute("isLast", "true"); //$NON-NLS-1$//$NON-NLS-2$
+ } else {
+ status.removeAttribute("isLast"); //$NON-NLS-1$
+ }
+ return status;
+ }
+
+ /**
+ * @return
+ * @throws ParserConfigurationException
+ * @throws FactoryConfigurationError
+ */
+ private Document createDocument()
+ {
+ try
+ {
+ return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ }
+ catch (ParserConfigurationException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (FactoryConfigurationError e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/Messages.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/Messages.java
new file mode 100644
index 0000000..94f8350
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/Messages.java
@@ -0,0 +1,43 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: Messages.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+
+import org.eclipse.osgi.util.NLS;
+
+
+public class Messages extends NLS
+{
+ private static final String BUNDLE_NAME = "org.eclipse.jet.internal.taglib.control.messages"; //$NON-NLS-1$
+
+ public static String BreadthFirstStrategy_DeepContentNotAllowed;
+
+ public static String DeepContentTag_MustBeContainedByDeepIterate;
+
+ public static String DeepIteratorStrategyBuilder_UnrecognizedTraversalStrategy;
+
+ public static String PreorderStrategy_DeepContentTagNotAllowed;
+ static
+ {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages()
+ {
+ }
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/PreorderStrategy.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/PreorderStrategy.java
new file mode 100644
index 0000000..ca873c5
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/PreorderStrategy.java
@@ -0,0 +1,95 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: PreorderStrategy.java,v 1.1 2009/03/16 14:42:30 pelder Exp $
+ */
+package org.eclipse.jet.internal.taglib.control;
+
+
+import org.eclipse.jet.taglib.JET2TagException;
+import org.eclipse.osgi.util.NLS;
+
+
+/**
+ * @author pelder
+ */
+public class PreorderStrategy extends AbstrateDeepIterateStrategy implements IDeepIterateStrategy
+{
+
+ public PreorderStrategy(DeepIteratorStrategyBuilder builder)
+ {
+ super(builder);
+ }
+
+ private boolean search(Object contextObject, int depth)
+ {
+ boolean hasChildren = false;
+ Object[] nodes = selectNodes(contextObject, depth);
+ for (int i = 0; i < nodes.length; i++)
+ {
+ final DeepIterateEntry entry = new DeepIterateEntry(nodes[i], depth + 1);
+
+ if (isDuplicateEntry(entry))
+ {
+ continue;
+ }
+ if (createsRecursiveLoop(entry))
+ {
+ continue;
+ }
+ // if we get here, then this is not a leaf...
+ hasChildren = true;
+ // create the entry, and tentatively add it to the result...
+ entries.add(entry);
+
+ // search out child entries...
+ loopDetectionStack.add(entry);
+ boolean nHasChildren = search(entry.getObject(), depth + 1);
+ loopDetectionStack.remove(entry);
+
+ // mark whether node is a leaf...
+ entry.setLeaf(!nHasChildren);
+
+ if(!satisfiesFilter(entry)) {
+ // remove the entry...
+ entries.remove(entry);
+ }
+ }
+ return hasChildren;
+ }
+
+
+ /**
+ * @return
+ */
+ protected void doSearch()
+ {
+ search(initialContextObject, 0);
+ }
+
+ public boolean supportsDeepContent()
+ {
+ // c:deepContent is permitted if there is no filter...
+ return filter == null;
+ }
+
+ public void checkDeepContentAllowed() throws JET2TagException
+ {
+ if(!supportsDeepContent()) {
+ throw new JET2TagException(NLS.bind(Messages.PreorderStrategy_DeepContentTagNotAllowed, deepIterateTagName));
+ }
+
+ }
+
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/TagFactory.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/TagFactory.java
index b18f52b..ad9cdae 100644
--- a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/TagFactory.java
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/TagFactory.java
@@ -32,7 +32,7 @@ public class TagFactory implements TagInstanceFactory
*/
public TagFactory()
{
- tagOrdinalByName = new HashMap(28);
+ tagOrdinalByName = new HashMap(30);
tagOrdinalByName.put("addElement",new Integer(1)); //$NON-NLS-1$
tagOrdinalByName.put("addTextElement",new Integer(2)); //$NON-NLS-1$
@@ -62,6 +62,8 @@ public class TagFactory implements TagInstanceFactory
tagOrdinalByName.put("visitor",new Integer(26)); //$NON-NLS-1$
tagOrdinalByName.put("when",new Integer(27)); //$NON-NLS-1$
tagOrdinalByName.put("with",new Integer(28)); //$NON-NLS-1$
+ tagOrdinalByName.put("deepIterate",new Integer(29)); //$NON-NLS-1$
+ tagOrdinalByName.put("deepContent",new Integer(30)); //$NON-NLS-1$
}
public CustomTag createCustomTag(String name)
@@ -125,6 +127,10 @@ public class TagFactory implements TagInstanceFactory
return new org.eclipse.jet.internal.taglib.control.WhenTag();
case 28: // with
return new org.eclipse.jet.internal.taglib.control.WithTag();
+ case 29: // deepIterate
+ return new org.eclipse.jet.internal.taglib.control.DeepIterateTag();
+ case 30: // deepContent
+ return new org.eclipse.jet.internal.taglib.control.DeepContentTag();
default:
throw new JET2TagException("Unknown Tag: " + name); //$NON-NLS-1$
}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/messages.properties b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/messages.properties
new file mode 100644
index 0000000..5d077c2
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/control/messages.properties
@@ -0,0 +1,15 @@
+#
+# Copyright (c) 2009 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM - Initial API and implementation
+#
+#NLS_MESSAGEFORMAT_VAR
+BreadthFirstStrategy_DeepContentNotAllowed=A {0} tag with a traversal set to {1} cannot contain this tag.
+DeepContentTag_MustBeContainedByDeepIterate=This tag must be contained within a deepIterate tag
+DeepIteratorStrategyBuilder_UnrecognizedTraversalStrategy=Unrecongnized traversal ''{0}''. Valid values are: ''{1}'' or ''{2}''
+PreorderStrategy_DeepContentTagNotAllowed=A {0} tag with a ''filter'' attribute cannot contain this tag.
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/java/ImportsLocationTag.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/java/ImportsLocationTag.java
index 015b801..d148186 100644
--- a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/java/ImportsLocationTag.java
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/taglib/java/ImportsLocationTag.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2005, 2007 IBM Corporation and others.
+ * Copyright (c) 2005, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -33,7 +33,6 @@ import org.eclipse.jet.taglib.TagInfo;
import org.eclipse.jet.taglib.java.JavaActionsUtil;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
-import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
@@ -93,7 +92,7 @@ public class ImportsLocationTag extends AbstractEmptyTag
}
}
- public static final class ImportsPosition extends Position
+ public static final class ImportsPosition extends DocumentHelper.InsertAfterEmptyPosition
{
private final ImportManager importManager;
@@ -142,9 +141,6 @@ public class ImportsLocationTag extends AbstractEmptyTag
String compilationUnitPackage = getAttribute("package"); //$NON-NLS-1$
int importsOffset = bufferedWriter.getContentLength();
- // write something so that the position updater won't treat the position as end of file, and
- // constantly move it.
- out.write(" "); //$NON-NLS-1$
Position importsPosition = new ImportsPosition(importsOffset, compilationUnitPackage, bufferedWriter.getContent());
DocumentHelper.installPositionCategory(document, IMPORTS_POSITION_CATEGORY);

Back to the top