TRINIDAD-1679 Tag doc to list relationship between tags
Thanks to Maria Kaval for the patch
diff --git a/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/ComponentBean.java b/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/ComponentBean.java
index 80042fb..c67a6ed 100644
--- a/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/ComponentBean.java
+++ b/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/ComponentBean.java
@@ -20,9 +20,12 @@
 
 import java.lang.reflect.Modifier;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.logging.Logger;
 
@@ -716,6 +719,147 @@
   }
 
   /**
+   * Parses the string of satisfied contracts into a String array
+   * using space as the separator between values.
+   * In the component metadata file, the satisfied contracts are noted
+   * with satisfied-contracts markup.  As an example, af:popup 
+   * (oracle.adf.view.rich.component.rich.RichPopup) supports
+   * oracle-adf-richmenu-holder, oracle-adf-richdialog-holder, 
+   * oracle-adf-richnotewindow-holder, and oracle-adf-richpanelwindow-holder
+   * contracts.  The satisfied contracts of a given component are matched
+   * with the required ancestor contracts of other components to determine
+   * if a component hierarchy is legally assembled.
+   *
+   * @param satisfiedContracts  a space delimited string of satisifed contracts
+   */
+  public void parseSatisfiedContracts(
+    String satisfiedContracts)
+  {
+    setSatisfiedContracts(satisfiedContracts.split(" "));
+  }
+
+  /**
+   * Sets the possible values for this property.
+   * In the component metadata file, the satisfied contracts are noted
+   * with satisfied-contracts markup.  As an example, af:popup 
+   * (oracle.adf.view.rich.component.rich.RichPopup) supports
+   * oracle-adf-richmenu-holder, oracle-adf-richdialog-holder, 
+   * oracle-adf-richnotewindow-holder, and oracle-adf-richpanelwindow-holder
+   * contracts.  The satisfied contracts of a given component are matched
+   * with the required ancestor contracts of other components to determine
+   * if a component hierarchy is legally assembled.
+   * 
+   * @param satisfiedContracts  a string array of the satisfied contracts
+   */
+  public void setSatisfiedContracts(
+    String[] satisfiedContracts)
+  {
+    _satisfiedContracts = Arrays.asList(satisfiedContracts);
+  }
+
+  /**
+   * Returns an iterator of the satisfied contracts for this component.
+   * In the component metadata file, the satisfied contracts are noted
+   * with satisfied-contracts markup.  As an example, af:popup 
+   * (oracle.adf.view.rich.component.rich.RichPopup) supports
+   * oracle-adf-richmenu-holder, oracle-adf-richdialog-holder, 
+   * oracle-adf-richnotewindow-holder, and oracle-adf-richpanelwindow-holder
+   * contracts.  The satisfied contracts of a given component are matched
+   * with the required ancestor contracts of other components to determine
+   * if a component hierarchy is legally assembled.
+   * 
+   * @return  a java.util.Iterator of Strings, where each string is the name of a 
+   *          satisfied contract
+   */
+  public Iterator<String> satisfiedContracts()
+  {
+    return _satisfiedContracts.iterator();
+  }  
+  
+  /**
+   * Returns true if this component has any satisfied contracts.
+   *
+   * @return  true   if this component has any satisfied contracts,
+   *          false  otherwise
+   */
+  public boolean hasSatisfiedContracts()
+  {
+    return (!_satisfiedContracts.isEmpty());
+  }
+  
+  /**
+   * Parses the string of required ancestor contracts into a String array
+   * using space as the separator between values.
+   * In the component metadata file, the required ancestors are noted
+   * with required-ancestor-contracts markup. This indicates that an
+   * ancestor (e.g. parent or grandparent) tag must be have satisfied-contracts 
+   * metadata matching the required-ancestor-contracts metadata of this tag.
+   * As an example, af:dialog
+   * (oracle.adf.view.rich.component.rich.RichDialog) lists
+   * oracle-adf-richdialog-holder as a required ancestor contract, and 
+   * af:popup (oracle.adf.view.rich.component.rich.RichPopup) lists
+   * oracle-adf-richdialog-holder as a satisified contract.
+   *
+   * @param requiredAncestorContracts  a space delimited string of required ancestor contracts
+   */
+  public void parseRequiredAncestorContracts(
+    String requiredAncestorContracts)
+  {
+    setRequiredAncestorContracts(requiredAncestorContracts.split(" "));
+  }
+
+  /**
+   * Sets the possible values for this property.
+   * In the component metadata file, the required ancestors are noted
+   * with required-ancestor-contracts markup. This indicates that an
+   * ancestor (e.g. parent or grandparent) tag must be have satisfied-contracts 
+   * metadata matching the required-ancestor-contracts metadata of this tag.
+   * As an example, af:dialog
+   * (oracle.adf.view.rich.component.rich.RichDialog) lists
+   * oracle-adf-richdialog-holder as a required ancestor contract, and 
+   * af:popup (oracle.adf.view.rich.component.rich.RichPopup) lists
+   * oracle-adf-richdialog-holder as a satisified contract.
+   * 
+   * @param requiredAncestorContracts  a string array of the required ancestor contracts
+   */
+  public void setRequiredAncestorContracts(
+    String[] requiredAncestorContracts)
+  {
+    _requiredAncestorContracts = Arrays.asList(requiredAncestorContracts);
+  }
+
+  /**
+   * Returns the required ancestor contracts for this component.
+   * In the component metadata file, the required ancestors are noted
+   * with required-ancestor-contracts markup. This indicates that an
+   * ancestor (e.g. parent or grandparent) tag must be have satisfied-contracts 
+   * metadata matching the required-ancestor-contracts metadata of this tag.
+   * As an example, af:dialog
+   * (oracle.adf.view.rich.component.rich.RichDialog) lists
+   * oracle-adf-richdialog-holder as a required ancestor contract, and 
+   * af:popup (oracle.adf.view.rich.component.rich.RichPopup) lists
+   * oracle-adf-richdialog-holder as a satisified contract.
+   * 
+   * @return  a java.util.Iterator of strings, where each string is the name
+   *          of a required ancestor contract
+   */
+  public Iterator<String> requiredAncestorContracts()
+  {
+    return _requiredAncestorContracts.iterator();
+  }  
+
+  /**
+   * Returns true if this component has any required ancestor contracts.
+   *
+   * @return  true   if this component has any required ancestor contracts,
+   *          false  otherwise
+   */
+  public boolean hasRequiredAncestorContracts()
+  {
+    return (!_requiredAncestorContracts.isEmpty());
+  }
+  
+  /**
    * Adds a Java Language class modifier to the tag class.
    *
    * @param modifier  the modifier to be added
@@ -1043,6 +1187,8 @@
   private int     _componentClassModifiers;
   private int     _tagClassModifiers;
   private String[] _unsupportedAgents = new String[0];
+  private List<String> _requiredAncestorContracts = new ArrayList<String>();
+  private List<String> _satisfiedContracts = new ArrayList<String>();
 
   static private final String _TRINIDAD_COMPONENT_BASE =
                          "org.apache.myfaces.trinidad.component.UIXComponentBase";
diff --git a/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacesConfigParser.java b/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacesConfigParser.java
index f06a272..720f5f2 100644
--- a/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacesConfigParser.java
+++ b/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacesConfigParser.java
@@ -224,6 +224,21 @@
     digester.addBeanPropertySetter("faces-config/component/property/property-extension/property-metadata/required");
     digester.addBeanPropertySetter("faces-config/component/property/property-extension/property-metadata/value-expression", "valueExpression");
 
+    // faces-config/component/facet/facet-extension/facet-metadata/allowed-child-components
+    digester.addCallMethod("faces-config/component/facet/facet-extension/facet-metadata/allowed-child-components",
+                           "parseAllowedChildComponents", 1);
+    digester.addCallParam("faces-config/component/facet/facet-extension/facet-metadata/allowed-child-components", 0);
+
+    // faces-config/component/component-extension/component-metadata/required-ancestor-contracts
+    digester.addCallMethod("faces-config/component/component-extension/component-metadata/required-ancestor-contracts",
+                           "parseRequiredAncestorContracts", 1);
+    digester.addCallParam("faces-config/component/component-extension/component-metadata/required-ancestor-contracts", 0);
+
+    // faces-config/component/component-extension/component-metadata/satisfied-contracts
+    digester.addCallMethod("faces-config/component/component-extension/component-metadata/satisfied-contracts",
+                           "parseSatisfiedContracts", 1);
+    digester.addCallParam("faces-config/component/component-extension/component-metadata/satisfied-contracts", 0);
+
     // XInclude rules
     digester.setRuleNamespaceURI(XIncludeFilter.XINCLUDE_NAMESPACE);
     digester.addFactoryCreate("faces-config/component/include",
diff --git a/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacetBean.java b/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacetBean.java
index feb41ed..b01fc48 100644
--- a/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacetBean.java
+++ b/maven-faces-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/faces/parse/FacetBean.java
@@ -19,6 +19,7 @@
 package org.apache.myfaces.trinidadbuild.plugin.faces.parse;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
@@ -143,9 +144,75 @@
     return _accessibilityGuidelines.iterator();
   }
 
+  /**
+   * Parses the string of allowed child class names into a String array
+   * using space as the separator between values.
+   * In the component metadata file, the allowed child components
+   * are denoted with allowed-child-components markup.  As an example,
+   * oracle.adf.RichMenu is an allowed child component of 
+   * af:panelCollection's (oracle.adf.view.rich.component.rich.output.RichPanelCollection) 
+   * menu facet. The allowed child components of one component are matched with the
+   * component type attribute of other components to determine which child tags
+   * are allowed in the component hierarchy.
+   *
+   * @param allowedChildComponents  a space delimited string of allowed child component class names
+   */
+  public void parseAllowedChildComponents(
+    String allowedChildComponents)
+  {
+    setAllowedChildComponents(allowedChildComponents.split(" "));
+  }
+
+  /**
+   * Sets the possible values for this property.
+   * In the component metadata file, the allowed child components
+   * are denoted with allowed-child-components markup.  As an example,
+   * oracle.adf.RichMenu is an allowed child component of 
+   * af:panelCollection's (oracle.adf.view.rich.component.rich.output.RichPanelCollection)
+   * menu facet. The allowed child components of one component are matched with the
+   * component type attribute of other components to determine which child tags
+   * are allowed in the component hierarchy.
+   *
+   * @param allowedChildComponents  a string array of the allowed child component class names
+   */
+  public void setAllowedChildComponents(
+    String[] allowedChildComponents)
+  {
+    _allowedChildComponents = Arrays.asList(allowedChildComponents);
+  }
+
+  /**
+   * Returns the allowed child components for this facet.
+   * In the component metadata file, the allowed child components
+   * are denoted with allowed-child-components markup.  As an example,
+   * oracle.adf.RichMenu is an allowed child component of 
+   * af:panelCollection's (oracle.adf.view.rich.component.rich.output.RichPanelCollection)
+   * menu facet. The allowed child components of one component are matched with the
+   * component type attribute of other components to determine which child tags
+   * are allowed in the component hierarchy.
+   * 
+   * @return  an iterator of allowed child component class name strings
+   */
+  public Iterator<String> allowedChildComponents()
+  {
+    return (_allowedChildComponents.iterator());
+  }  
+
+  /**
+   * Returns true if this component has any allowed child components.
+   *
+   * @return  true   if this component has any allowed child components,
+   *          false  otherwise
+   */
+  public boolean hasAllowedChildComponents()
+  {
+    return (!_allowedChildComponents.isEmpty());
+  }
+
   private String       _description;
   private String       _facetName;
   private boolean      _required;
   private boolean      _hidden;
   private List<String> _accessibilityGuidelines = new ArrayList<String>();
+  private List<String> _allowedChildComponents = new ArrayList<String>();
 }
diff --git a/maven-tagdoc-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/tagdoc/TagdocReport.java b/maven-tagdoc-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/tagdoc/TagdocReport.java
index 1d2b9e5..421349b 100644
--- a/maven-tagdoc-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/tagdoc/TagdocReport.java
+++ b/maven-tagdoc-plugin/src/main/java/org/apache/myfaces/trinidadbuild/plugin/tagdoc/TagdocReport.java
@@ -31,6 +31,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -115,16 +116,77 @@
       return;
     }
 
-
-    Iterator components = facesConfig.components();
+    // Need to cycle through the components two times, hence need two iterators.
+    // components Iterator will be used when actually writing out the tag doc
+    // compIter Iterator will be used when creating the maps of component relationships
+    Iterator<ComponentBean> components = facesConfig.components();
     components = new FilteredIterator(components, new SkipFilter());
     components = new FilteredIterator(components, new ComponentTagFilter());
     components = new FilteredIterator(components, new ComponentNamespaceFilter());
     
+    Iterator<ComponentBean> compIter = facesConfig.components();
+    compIter = new FilteredIterator(compIter, new SkipFilter());
+    compIter = new FilteredIterator(compIter, new ComponentTagFilter());
+    compIter = new FilteredIterator(compIter, new ComponentNamespaceFilter());
+    
+    // compTypeMap holds a map of compononent types to tag names that implement that component type
+    // The map is built using getComponentType method on the component bean to determine the 
+    // component type of a given tag name
+    Map<String, List<QName>>  compTypeMap = new HashMap<String, List<QName>> ();
+    // contractMap holds a map of contract name to tag names that satisify that contract.
+    // The map is built using the getSatisfiedContracts method API on the component bean to determine 
+    // which contracts are satisfied for a given tagname
+    Map<String, List<QName>> contractMap = new HashMap<String, List<QName>>();
+    while (compIter.hasNext())
+    {
+      ComponentBean compBean = compIter.next();
+      List<QName> tagNames;
+      String compType = compBean.getComponentType();
+      if (compType != null &&
+          compTypeMap.containsKey (compType) && 
+          compBean.getTagName() != null) 
+      {
+        // the component type map already contains an entry for this component type
+        tagNames = compTypeMap.get(compType);
+      } 
+      else 
+      {
+        // the component type map does not contain an entry for this component type
+        // so create a new ArrayList that will be used to store the tag names of 
+        // component that have this component type
+        tagNames = new ArrayList<QName>();
+      }
+      tagNames.add(compBean.getTagName());
+      compTypeMap.put (compType, tagNames);      
+
+      if (compBean.hasSatisfiedContracts())
+      {
+        Iterator<String> satContractsIter = compBean.satisfiedContracts();
+        while (satContractsIter.hasNext())
+        {
+          String satContract = satContractsIter.next();
+          if (contractMap.containsKey (satContract))
+          {
+            // the contract map already contains an entry for this contract 
+            tagNames = contractMap.get(satContract);
+          }
+          else
+          {
+            // the contract map does not contain an entry for this contract, so 
+            // create a new ArrayList which will be used to store the tag names of
+            // components that satisfy this contract
+            tagNames = new ArrayList<QName>();
+          }
+          tagNames.add(compBean.getTagName());
+          contractMap.put (satContract, tagNames);
+        }
+      }
+    }
+
     Iterator validators = facesConfig.validators();
     validators = new FilteredIterator(validators, new ValidatorTagFilter());
     validators = new FilteredIterator(validators, new ValidatorNamespaceFilter());
-    
+
     Iterator converters = facesConfig.converters();
     converters = new FilteredIterator(converters, new ConverterTagFilter());
     converters = new FilteredIterator(converters, new ConverterNamespaceFilter());
@@ -147,7 +209,7 @@
     int count = 0;
     while (components.hasNext())
     {
-      String pageName = _generateComponentDoc((ComponentBean)components.next());
+      String pageName = _generateComponentDoc((ComponentBean)components.next(), compTypeMap, contractMap);
       if (pageName != null)
       {
         componentPages.add(pageName);
@@ -246,6 +308,53 @@
     return set;
   }
 
+  private String _formatTagList(
+    Iterator<String> strIter,
+    Map <String, List<QName>> pMap,
+    String header)
+  {
+    String formatted = null;
+    
+    // Don't know how long this will be, but 300 should be plenty.
+    StringBuffer sb = new StringBuffer(300);
+    sb.append("\n");
+    sb.append("<b>");
+    sb.append(header);
+    sb.append(":</b> ");
+
+    boolean gotOne = false;    
+    while (strIter.hasNext())
+    {
+      List<QName> tagNameList = pMap.get(strIter.next());
+      if (tagNameList != null && !tagNameList.isEmpty())
+      {
+        Iterator<QName> tagNameIter  = tagNameList.iterator();
+
+        while (tagNameIter.hasNext())
+        {
+          QName tagName = tagNameIter.next();
+
+          String tagdocURL = _platformAgnosticPath("../tagdoc/" +
+            _toPageName(tagName) + ".html");
+          sb.append("<a href=\"" + tagdocURL + "\">");
+          sb.append(_getQualifiedName(tagName));
+          sb.append("</a>");
+          if (gotOne)
+          {
+            sb.append(", ");
+          }
+          gotOne = true;
+        }
+      }
+      if (gotOne)
+      {
+        sb.append("<br/>\n");
+        formatted = sb.toString();
+      }
+    }
+    return formatted;
+  }
+
   private String _formatPropList(
     String[] pList, 
     String   header)
@@ -372,7 +481,7 @@
     return "unknown";
   }
 
-  private String _generateComponentDoc(ComponentBean component)
+  private String _generateComponentDoc(ComponentBean component, Map<String, List<QName>> compTypeMap, Map <String, List<QName>> contractMap)
     throws Exception
   {
     if (component.getTagName() == null)
@@ -400,7 +509,7 @@
 
       out.write(" <section name=\"Summary\">\n");
       out.write(" <p>\n");
-      _writeComponentSummary(out, component);
+      _writeComponentSummary(out, component, contractMap);
       out.write(" </p>\n");
       out.write(" </section>\n");
 
@@ -423,7 +532,7 @@
       {
         out.write(" <section name=\"Supported Facets\">\n");
         out.write(" <p>\n");
-        _writeComponentFacets(out, component);
+        _writeComponentFacets(out, component, compTypeMap);
         out.write(" </p>\n");
         out.write(" </section>\n");
       }
@@ -546,7 +655,7 @@
   }
 
 
-  private void _writeComponentSummary(Writer out, ComponentBean bean) throws IOException
+  private void _writeComponentSummary(Writer out, ComponentBean bean, Map <String, List<QName>> contractMap) throws IOException
   {
     out.write("   <b>Tag name:</b> &lt;" +
               _getQualifiedName(bean.getTagName()) + "&gt;\n");
@@ -565,6 +674,14 @@
     out.write("   <b>Component type:</b> " + bean.getComponentType() +  "\n");
     out.write("   <br/>\n");
 
+    if (bean.hasRequiredAncestorContracts())
+    {
+      String formattedAncestors = _formatTagList ( bean.requiredAncestorContracts(), 
+                                                   contractMap, 
+                                                   "Required Ancestor Tag");
+      out.write (formattedAncestors);
+    }
+
     if (_isNamingContainer(bean))
     {
       out.write("   <p><b>Naming container:</b>  Yes.  When referring to children of this " +
@@ -1041,14 +1158,14 @@
   }
 
 
-  private void _writeComponentFacets(Writer out, ComponentBean bean) throws IOException
+  private void _writeComponentFacets(Writer out, ComponentBean bean, Map<String, List<QName>> compTypeMap) throws IOException
   {
     // Sort the facets
     TreeSet facetNames = new TreeSet();
-    Iterator iter = bean.facets(true);
+    Iterator<FacetBean> iter = bean.facets(true);
     while (iter.hasNext())
     {
-      FacetBean facetBean = (FacetBean)iter.next();
+      FacetBean facetBean = iter.next();
       if (!facetBean.isHidden())
       {
         facetNames.add(facetBean.getFacetName());
@@ -1061,14 +1178,22 @@
     out.write("<th>Description</th>\n");
     out.write("</tr>\n");
 
-    Iterator nameIter = facetNames.iterator();
+    Iterator<String> nameIter = facetNames.iterator();
     while (nameIter.hasNext())
     {
-      String name = (String) nameIter.next();
+      String name = nameIter.next();
       FacetBean facet = bean.findFacet(name, true);
       out.write("<tr>\n");
       out.write("<td>" + facet.getFacetName() + "</td>");
       out.write("<td>");
+
+      if (facet.hasAllowedChildComponents())
+      {
+        String formattedChildComps = _formatTagList (facet.allowedChildComponents(), compTypeMap, "Allowed Child Components");
+        out.write (formattedChildComps);
+        out.write("<br/>");
+      }
+
       out.write(facet.getDescription());
       out.write("</td>\n");
       out.write("</tr>\n");