1   package com.atlassian.maven.plugins.amps;
2   
3   import java.util.List;
4   
5   import com.atlassian.fugue.Option;
6   
7   import org.dom4j.Document;
8   import org.dom4j.DocumentException;
9   import org.dom4j.DocumentHelper;
10  import org.dom4j.Element;
11  import org.dom4j.Node;
12  import org.hamcrest.Description;
13  import org.hamcrest.Matcher;
14  import org.hamcrest.TypeSafeDiagnosingMatcher;
15  
16  import static com.atlassian.fugue.Option.some;
17  import static org.hamcrest.Matchers.equalTo;
18  import static org.hamcrest.Matchers.iterableWithSize;
19  import static org.junit.Assert.assertEquals;
20  
21  public class XmlMatchers
22  {
23      private static final String DUMMY_NAMESPACE_PREFIX = "x";
24      
25      public static class XmlWrapper
26      {
27          private final Element root;
28          private final Option<String> defaultNamespacePrefix;
29          
30          private XmlWrapper(Element root)
31          {
32              this(root, Option.none(String.class));
33          }
34          
35          private XmlWrapper(Element root, Option<String> defaultNamespacePrefix)
36          {
37              this.root = root;
38              this.defaultNamespacePrefix = defaultNamespacePrefix;
39          }
40          
41          private String xpath(String path)
42          {
43              for (String ns : defaultNamespacePrefix)
44              {
45                  return path.replaceAll("/(?!/)", "/" + ns + ":");
46              }
47              return path;
48          }
49          
50          private void dump()
51          {
52              System.err.println("Document being matched:");
53              System.err.println(root.asXML());
54          }
55      }
56      
57      public static XmlWrapper xml(Element root)
58      {
59          return new XmlWrapper(root);
60      }
61      
62      public static XmlWrapper xml(Element root, String defaultNamespacePrefix)
63      {
64          return new XmlWrapper(root, some(defaultNamespacePrefix));
65      }
66      
67      public static XmlWrapper xml(String content) throws DocumentException
68      {
69          Document doc = DocumentHelper.parseText(content);
70          String defaultNamespace = doc.getRootElement().getNamespace().getURI();
71          if (defaultNamespace != null)
72          {
73              // this is a workaround for the fact that default namespaces don't really work in dom4j
74              doc.getRootElement().addNamespace(DUMMY_NAMESPACE_PREFIX, defaultNamespace);
75              return xml(doc.getRootElement(), DUMMY_NAMESPACE_PREFIX);
76          }
77          else
78          {
79              return xml(doc.getRootElement());
80          }
81      }
82  
83      public static XmlWrapper xml(String content, String rootElementName) throws DocumentException
84      {
85          XmlWrapper xml = xml(content);
86          assertEquals("Wrong root element type", rootElementName, xml.root.getName());
87          return xml;
88      }
89      
90      public static Matcher<XmlWrapper> node(final String xpath, final Matcher<? super Node> nodeMatcher)
91      {
92          return new TypeSafeDiagnosingMatcher<XmlWrapper>()
93          {
94              protected boolean matchesSafely(XmlWrapper xml, Description mismatchDescription)
95              {
96                  Node node = xml.root.selectSingleNode(xml.xpath(xpath));
97                  if (!nodeMatcher.matches(node))
98                  {
99                      nodeMatcher.describeMismatch(node, mismatchDescription);
100                     System.err.println("Document being matched:");
101                     System.err.println(xml.root.asXML());
102                     return false;
103                 }
104                 return true;
105             }
106 
107             public void describeTo(Description description)
108             {
109                 description.appendText("node at [" + xpath + "] ");
110                 nodeMatcher.describeTo(description);
111             }
112         };
113     }
114 
115     public static Matcher<XmlWrapper> nodes(final String xpath, final Matcher<Iterable<Node>> nodesMatcher)
116     {
117         return new TypeSafeDiagnosingMatcher<XmlWrapper>()
118         {
119             @SuppressWarnings("unchecked")
120             protected boolean matchesSafely(XmlWrapper xml, Description mismatchDescription)
121             {
122                 List<Node> nodes = (List<Node>) xml.root.selectNodes(xml.xpath(xpath));
123                 if (!nodesMatcher.matches(nodes))
124                 {
125                     nodesMatcher.describeMismatch(nodes, mismatchDescription);
126                     xml.dump();
127                     return false;
128                 }
129                 return true;
130             }
131 
132             public void describeTo(Description description)
133             {
134                 description.appendText("nodes at [" + xpath + "] ");
135                 nodesMatcher.describeTo(description);
136             }
137         };
138     }
139     
140     public static Matcher<Node> nodeText(final Matcher<? super String> textMatcher)
141     {
142         return new TypeSafeDiagnosingMatcher<Node>()
143         {
144             protected boolean matchesSafely(Node node, Description mismatchDescription)
145             {
146                 if (node == null)
147                 {
148                     mismatchDescription.appendText("node did not exist");
149                     return false;
150                 }
151                 else if (!textMatcher.matches(node.getText().trim()))
152                 {
153                     textMatcher.describeMismatch(node.getText().trim(), mismatchDescription);
154                     return false;
155                 }
156                 return true;
157             }
158 
159             public void describeTo(Description description)
160             {
161                 description.appendText("with text ");
162                 textMatcher.describeTo(description);
163             }
164         };
165     }
166     
167     public static Matcher<Node> nodeTextEquals(final String text)
168     {
169         return nodeText(equalTo(text));
170     }
171     
172     public static Matcher<Iterable<Node>> nodeCount(final int count)
173     {
174         return iterableWithSize(count);
175     }
176 }