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