View Javadoc
1   /**
2    * Copyright (C) 2014-2015 Philip Helger (www.helger.com)
3    * philip[at]helger[dot]com
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *         http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.helger.schematron.pure.model;
18  
19  import java.util.ArrayList;
20  import java.util.LinkedHashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.annotation.Nonnull;
25  import javax.annotation.Nullable;
26  import javax.annotation.concurrent.NotThreadSafe;
27  
28  import com.helger.commons.ValueEnforcer;
29  import com.helger.commons.annotation.Nonempty;
30  import com.helger.commons.annotation.ReturnsMutableCopy;
31  import com.helger.commons.collection.CollectionHelper;
32  import com.helger.commons.microdom.IMicroElement;
33  import com.helger.commons.microdom.MicroElement;
34  import com.helger.commons.regex.RegExHelper;
35  import com.helger.commons.string.StringHelper;
36  import com.helger.commons.string.ToStringGenerator;
37  import com.helger.schematron.CSchematron;
38  import com.helger.schematron.CSchematronXML;
39  import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
40  
41  /**
42   * A single Schematron assert- or report-element.<br>
43   * An assertion made about the context nodes. The data content is a
44   * natural-language assertion. The required test attribute is an assertion test
45   * evaluated in the current context. If the test evaluates positive, the
46   * assertion succeeds. The optional diagnostics attribute is a reference to
47   * further diagnostic information.<br>
48   * The natural-language assertion shall be a positive statement of a constraint.
49   * NOTE: The natural-language assertion may contain information about actual
50   * values in addition to expected values and may contain diagnostic information.
51   * Users should note, however, that the diagnostic element is provided for such
52   * information to encourage clear statement of the natural-language assertion.<br>
53   * The icon, see and fpi attributes allow rich interfaces and documentation.<br>
54   * The flag attribute allows more detailed outcomes.<br>
55   * The role and subject attributes allow explicit identification of some part of
56   * a pattern.<br>
57   *
58   * @author Philip Helger
59   */
60  @NotThreadSafe
61  public class PSAssertReport implements IPSElement, IPSHasFlag, IPSHasForeignElements, IPSHasMixedContent, IPSHasID, IPSHasRichGroup, IPSHasLinkableGroup
62  {
63    private final boolean m_bIsAssert;
64    private String m_sTest;
65    private String m_sFlag;
66    private String m_sID;
67    private List <String> m_aDiagnostics;
68    private PSRichGroup m_aRich;
69    private PSLinkableGroup m_aLinkable;
70    private final List <Object> m_aContent = new ArrayList <Object> ();
71    private Map <String, String> m_aForeignAttrs;
72    private List <IMicroElement> m_aForeignElements;
73  
74    public PSAssertReport (final boolean bIsAssert)
75    {
76      m_bIsAssert = bIsAssert;
77    }
78  
79    public boolean isValid (@Nonnull final IPSErrorHandler aErrorHandler)
80    {
81      for (final Object aContent : m_aContent)
82        if (aContent instanceof IPSElement)
83          if (!((IPSElement) aContent).isValid (aErrorHandler))
84            return false;
85      if (StringHelper.hasNoText (m_sTest))
86      {
87        aErrorHandler.error (this, (m_bIsAssert ? "<assert>" : "<report>") + " has no 'test'");
88        return false;
89      }
90      return true;
91    }
92  
93    public void validateCompletely (@Nonnull final IPSErrorHandler aErrorHandler)
94    {
95      for (final Object aContent : m_aContent)
96        if (aContent instanceof IPSElement)
97          ((IPSElement) aContent).validateCompletely (aErrorHandler);
98      if (StringHelper.hasNoText (m_sTest))
99        aErrorHandler.error (this, (m_bIsAssert ? "<assert>" : "<report>") + " has no 'test'");
100   }
101 
102   public boolean isMinimal ()
103   {
104     if (!m_bIsAssert)
105       return false;
106     for (final Object aContent : m_aContent)
107       if (aContent instanceof IPSElement)
108         if (!((IPSElement) aContent).isMinimal ())
109           return false;
110     return true;
111   }
112 
113   public void addForeignElement (@Nonnull final IMicroElement aForeignElement)
114   {
115     ValueEnforcer.notNull (aForeignElement, "ForeignElement");
116     if (aForeignElement.hasParent ())
117       throw new IllegalArgumentException ("ForeignElement already has a parent!");
118     if (m_aForeignElements == null)
119       m_aForeignElements = new ArrayList <IMicroElement> ();
120     m_aForeignElements.add (aForeignElement);
121   }
122 
123   public void addForeignElements (@Nonnull final List <IMicroElement> aForeignElements)
124   {
125     ValueEnforcer.notNull (aForeignElements, "ForeignElements");
126     for (final IMicroElement aForeignElement : aForeignElements)
127       addForeignElement (aForeignElement);
128   }
129 
130   public boolean hasForeignElements ()
131   {
132     return m_aForeignElements != null && !m_aForeignElements.isEmpty ();
133   }
134 
135   @Nonnull
136   @ReturnsMutableCopy
137   public List <IMicroElement> getAllForeignElements ()
138   {
139     return CollectionHelper.newList (m_aForeignElements);
140   }
141 
142   public void addForeignAttribute (@Nonnull final String sAttrName, @Nonnull final String sAttrValue)
143   {
144     ValueEnforcer.notNull (sAttrName, "AttrName");
145     ValueEnforcer.notNull (sAttrValue, "AttrValue");
146     if (m_aForeignAttrs == null)
147       m_aForeignAttrs = new LinkedHashMap <String, String> ();
148     m_aForeignAttrs.put (sAttrName, sAttrValue);
149   }
150 
151   public void addForeignAttributes (@Nonnull final Map <String, String> aForeignAttrs)
152   {
153     ValueEnforcer.notNull (aForeignAttrs, "ForeignAttrs");
154     for (final Map.Entry <String, String> aEntry : aForeignAttrs.entrySet ())
155       addForeignAttribute (aEntry.getKey (), aEntry.getValue ());
156   }
157 
158   public boolean hasForeignAttributes ()
159   {
160     return m_aForeignAttrs != null && !m_aForeignAttrs.isEmpty ();
161   }
162 
163   @Nonnull
164   @ReturnsMutableCopy
165   public Map <String, String> getAllForeignAttributes ()
166   {
167     return CollectionHelper.newOrderedMap (m_aForeignAttrs);
168   }
169 
170   public boolean isAssert ()
171   {
172     return m_bIsAssert;
173   }
174 
175   public boolean isReport ()
176   {
177     return !m_bIsAssert;
178   }
179 
180   public void setTest (@Nullable final String sTest)
181   {
182     m_sTest = sTest;
183   }
184 
185   @Nullable
186   public String getTest ()
187   {
188     return m_sTest;
189   }
190 
191   public void setFlag (@Nullable final String sFlag)
192   {
193     m_sFlag = sFlag;
194   }
195 
196   @Nullable
197   public String getFlag ()
198   {
199     return m_sFlag;
200   }
201 
202   public void setID (@Nullable final String sID)
203   {
204     m_sID = sID;
205   }
206 
207   public boolean hasID ()
208   {
209     return m_sID != null;
210   }
211 
212   @Nullable
213   public String getID ()
214   {
215     return m_sID;
216   }
217 
218   /**
219    * Set the diagnostics, as an IDREFS value (multiple IDREF values separated by
220    * spaces)
221    *
222    * @param sDiagnostics
223    *        The value to be set. May be <code>null</code>.
224    */
225   public void setDiagnostics (@Nullable final String sDiagnostics)
226   {
227     if (StringHelper.hasText (sDiagnostics))
228       setDiagnostics (RegExHelper.getSplitToList (sDiagnostics.trim (), "\\s+"));
229     else
230       m_aDiagnostics = null;
231   }
232 
233   /**
234    * Set the diagnostics, as an IDREFS value (multiple IDREF values separated by
235    * spaces)
236    *
237    * @param aDiagnostics
238    *        The value to be set. May be <code>null</code>.
239    */
240   public void setDiagnostics (@Nullable final List <String> aDiagnostics)
241   {
242     m_aDiagnostics = aDiagnostics;
243   }
244 
245   /**
246    * @return List of references to {@link PSDiagnostic} elements.
247    */
248   @Nonnull
249   @ReturnsMutableCopy
250   public List <String> getAllDiagnostics ()
251   {
252     return CollectionHelper.newList (m_aDiagnostics);
253   }
254 
255   public void setRich (@Nullable final PSRichGroup aRich)
256   {
257     m_aRich = aRich;
258   }
259 
260   public boolean hasRich ()
261   {
262     return m_aRich != null;
263   }
264 
265   @Nullable
266   public PSRichGroup getRich ()
267   {
268     return m_aRich;
269   }
270 
271   @Nullable
272   public PSRichGroup getRichClone ()
273   {
274     return m_aRich == null ? null : m_aRich.getClone ();
275   }
276 
277   public void setLinkable (@Nullable final PSLinkableGroup aLinkable)
278   {
279     m_aLinkable = aLinkable;
280   }
281 
282   public boolean hasLinkable ()
283   {
284     return m_aLinkable != null;
285   }
286 
287   @Nullable
288   public PSLinkableGroup getLinkable ()
289   {
290     return m_aLinkable;
291   }
292 
293   @Nullable
294   public PSLinkableGroup getLinkableClone ()
295   {
296     return m_aLinkable == null ? null : m_aLinkable.getClone ();
297   }
298 
299   public void addText (@Nonnull @Nonempty final String sText)
300   {
301     ValueEnforcer.notEmpty (sText, "Text");
302     m_aContent.add (sText);
303   }
304 
305   public boolean hasAnyText ()
306   {
307     for (final Object aElement : m_aContent)
308       if (aElement instanceof String)
309         return true;
310     return false;
311   }
312 
313   @Nonnull
314   @ReturnsMutableCopy
315   public List <String> getAllTexts ()
316   {
317     final List <String> ret = new ArrayList <String> ();
318     for (final Object aElement : m_aContent)
319       if (aElement instanceof String)
320         ret.add ((String) aElement);
321     return ret;
322   }
323 
324   public void addName (@Nonnull final PSName aName)
325   {
326     ValueEnforcer.notNull (aName, "Name");
327     m_aContent.add (aName);
328   }
329 
330   @Nonnull
331   @ReturnsMutableCopy
332   public List <PSName> getAllNames ()
333   {
334     final List <PSName> ret = new ArrayList <PSName> ();
335     for (final Object aElement : m_aContent)
336       if (aElement instanceof PSName)
337         ret.add ((PSName) aElement);
338     return ret;
339   }
340 
341   public void addValueOf (@Nonnull final PSValueOf aValueOf)
342   {
343     ValueEnforcer.notNull (aValueOf, "ValueOf");
344     m_aContent.add (aValueOf);
345   }
346 
347   @Nonnull
348   @ReturnsMutableCopy
349   public List <PSValueOf> getAllValueOfs ()
350   {
351     final List <PSValueOf> ret = new ArrayList <PSValueOf> ();
352     for (final Object aElement : m_aContent)
353       if (aElement instanceof PSValueOf)
354         ret.add ((PSValueOf) aElement);
355     return ret;
356   }
357 
358   public void addEmph (@Nonnull final PSEmph aEmph)
359   {
360     ValueEnforcer.notNull (aEmph, "Emph");
361     m_aContent.add (aEmph);
362   }
363 
364   @Nonnull
365   @ReturnsMutableCopy
366   public List <PSEmph> getAllEmphs ()
367   {
368     final List <PSEmph> ret = new ArrayList <PSEmph> ();
369     for (final Object aElement : m_aContent)
370       if (aElement instanceof PSEmph)
371         ret.add ((PSEmph) aElement);
372     return ret;
373   }
374 
375   public void addDir (@Nonnull final PSDir aDir)
376   {
377     ValueEnforcer.notNull (aDir, "Dir");
378     m_aContent.add (aDir);
379   }
380 
381   @Nonnull
382   @ReturnsMutableCopy
383   public List <PSDir> getAllDirs ()
384   {
385     final List <PSDir> ret = new ArrayList <PSDir> ();
386     for (final Object aElement : m_aContent)
387       if (aElement instanceof PSDir)
388         ret.add ((PSDir) aElement);
389     return ret;
390   }
391 
392   public void addSpan (@Nonnull final PSSpan aSpan)
393   {
394     ValueEnforcer.notNull (aSpan, "Span");
395     m_aContent.add (aSpan);
396   }
397 
398   @Nonnull
399   @ReturnsMutableCopy
400   public List <PSSpan> getAllSpans ()
401   {
402     final List <PSSpan> ret = new ArrayList <PSSpan> ();
403     for (final Object aElement : m_aContent)
404       if (aElement instanceof PSSpan)
405         ret.add ((PSSpan) aElement);
406     return ret;
407   }
408 
409   /**
410    * @return A list of {@link String}, {@link PSName}, {@link PSValueOf},
411    *         {@link PSEmph}, {@link PSDir} and {@link PSSpan} elements.
412    */
413   @Nonnull
414   @ReturnsMutableCopy
415   public List <Object> getAllContentElements ()
416   {
417     return CollectionHelper.newList (m_aContent);
418   }
419 
420   @Nonnull
421   public IMicroElement getAsMicroElement ()
422   {
423     final IMicroElement ret = new MicroElement (CSchematron.NAMESPACE_SCHEMATRON,
424                                                 m_bIsAssert ? CSchematronXML.ELEMENT_ASSERT
425                                                            : CSchematronXML.ELEMENT_REPORT);
426     ret.setAttribute (CSchematronXML.ATTR_ID, m_sID);
427     ret.setAttribute (CSchematronXML.ATTR_FLAG, m_sFlag);
428     ret.setAttribute (CSchematronXML.ATTR_TEST, m_sTest);
429     if (CollectionHelper.isNotEmpty (m_aDiagnostics))
430       ret.setAttribute (CSchematronXML.ATTR_DIAGNOSTICS, StringHelper.getImploded (' ', m_aDiagnostics));
431     if (m_aRich != null)
432       m_aRich.fillMicroElement (ret);
433     if (m_aLinkable != null)
434       m_aLinkable.fillMicroElement (ret);
435     if (m_aForeignElements != null)
436       for (final IMicroElement aForeignElement : m_aForeignElements)
437         ret.appendChild (aForeignElement.getClone ());
438     for (final Object aContent : m_aContent)
439       if (aContent instanceof String)
440         ret.appendText ((String) aContent);
441       else
442         ret.appendChild (((IPSElement) aContent).getAsMicroElement ());
443     if (m_aForeignAttrs != null)
444       for (final Map.Entry <String, String> aEntry : m_aForeignAttrs.entrySet ())
445         ret.setAttribute (aEntry.getKey (), aEntry.getValue ());
446     return ret;
447   }
448 
449   @Override
450   public String toString ()
451   {
452     return new ToStringGenerator (this).append ("isAssert", m_bIsAssert)
453                                        .appendIfNotNull ("test", m_sTest)
454                                        .appendIfNotNull ("flag", m_sFlag)
455                                        .appendIfNotNull ("id", m_sID)
456                                        .appendIfNotNull ("diagnostics", m_aDiagnostics)
457                                        .appendIfNotNull ("rich", m_aRich)
458                                        .appendIfNotNull ("linkable", m_aLinkable)
459                                        .appendIfNotEmpty ("content", m_aContent)
460                                        .appendIfNotEmpty ("foreignAttrs", m_aForeignAttrs)
461                                        .appendIfNotEmpty ("foreignElements", m_aForeignElements)
462                                        .toString ();
463   }
464 }