View Javadoc
1   /**
2    * Copyright (C) 2014-2017 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.ext.CommonsArrayList;
30  import com.helger.commons.collection.ext.CommonsLinkedHashMap;
31  import com.helger.commons.collection.ext.ICommonsList;
32  import com.helger.commons.collection.ext.ICommonsOrderedMap;
33  import com.helger.commons.string.StringHelper;
34  import com.helger.commons.string.ToStringGenerator;
35  import com.helger.schematron.CSchematron;
36  import com.helger.schematron.CSchematronXML;
37  import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
38  import com.helger.xml.microdom.IMicroElement;
39  import com.helger.xml.microdom.MicroElement;
40  
41  /**
42   * A single Schematron diagnostic-element.<br>
43   * A natural-language message giving more specific details concerning a failed
44   * assertion, such as found versus expected values and repair hints.<br>
45   * NOTE: Diagnostics in multiple languages may be supported by using a different
46   * diagnostic element for each language, with the appropriate xml:lang language
47   * attribute, and referencing all the unique identifiers of the diagnostic
48   * elements in the diagnostics attribute of the assertion. Annex G gives a
49   * simple example of a multi-lingual schema.<br>
50   * An implementation is not required to make use of this element.
51   *
52   * @author Philip Helger
53   */
54  @NotThreadSafe
55  public class PSDiagnostic implements
56                            IPSClonableElement <PSDiagnostic>,
57                            IPSOptionalElement,
58                            IPSHasID,
59                            IPSHasForeignElements,
60                            IPSHasMixedContent,
61                            IPSHasRichGroup
62  {
63    private String m_sID;
64    private PSRichGroup m_aRich;
65    private final ICommonsList <Object> m_aContent = new CommonsArrayList <> ();
66    private ICommonsOrderedMap <String, String> m_aForeignAttrs;
67    private ICommonsList <IMicroElement> m_aForeignElements;
68  
69    public PSDiagnostic ()
70    {}
71  
72    public boolean isValid (@Nonnull final IPSErrorHandler aErrorHandler)
73    {
74      for (final Object aContent : m_aContent)
75        if (aContent instanceof IPSElement)
76          if (!((IPSElement) aContent).isValid (aErrorHandler))
77            return false;
78      if (StringHelper.hasNoText (m_sID))
79      {
80        aErrorHandler.error (this, "<diagnostic> has no 'id'");
81        return false;
82      }
83      return true;
84    }
85  
86    public void validateCompletely (@Nonnull final IPSErrorHandler aErrorHandler)
87    {
88      for (final Object aContent : m_aContent)
89        if (aContent instanceof IPSElement)
90          ((IPSElement) aContent).validateCompletely (aErrorHandler);
91      if (StringHelper.hasNoText (m_sID))
92        aErrorHandler.error (this, "<diagnostic> has no 'id'");
93    }
94  
95    public boolean isMinimal ()
96    {
97      return false;
98    }
99  
100   public void addForeignElement (@Nonnull final IMicroElement aForeignElement)
101   {
102     ValueEnforcer.notNull (aForeignElement, "ForeignElement");
103     if (aForeignElement.hasParent ())
104       throw new IllegalArgumentException ("ForeignElement already has a parent!");
105     if (m_aForeignElements == null)
106       m_aForeignElements = new CommonsArrayList <> ();
107     m_aForeignElements.add (aForeignElement);
108   }
109 
110   public boolean hasForeignElements ()
111   {
112     return m_aForeignElements != null && m_aForeignElements.isNotEmpty ();
113   }
114 
115   @Nonnull
116   @ReturnsMutableCopy
117   public ICommonsList <IMicroElement> getAllForeignElements ()
118   {
119     return new CommonsArrayList <> (m_aForeignElements);
120   }
121 
122   public void addForeignAttribute (@Nonnull final String sAttrName, @Nonnull final String sAttrValue)
123   {
124     ValueEnforcer.notNull (sAttrName, "AttrName");
125     ValueEnforcer.notNull (sAttrValue, "AttrValue");
126     if (m_aForeignAttrs == null)
127       m_aForeignAttrs = new CommonsLinkedHashMap <> ();
128     m_aForeignAttrs.put (sAttrName, sAttrValue);
129   }
130 
131   public boolean hasForeignAttributes ()
132   {
133     return m_aForeignAttrs != null && m_aForeignAttrs.isNotEmpty ();
134   }
135 
136   @Nonnull
137   @ReturnsMutableCopy
138   public ICommonsOrderedMap <String, String> getAllForeignAttributes ()
139   {
140     return new CommonsLinkedHashMap <> (m_aForeignAttrs);
141   }
142 
143   public void setID (@Nullable final String sID)
144   {
145     m_sID = sID;
146   }
147 
148   @Nullable
149   public String getID ()
150   {
151     return m_sID;
152   }
153 
154   public void setRich (@Nullable final PSRichGroup aRich)
155   {
156     m_aRich = aRich;
157   }
158 
159   @Nullable
160   public PSRichGroup getRich ()
161   {
162     return m_aRich;
163   }
164 
165   public void addText (@Nonnull @Nonempty final String sText)
166   {
167     ValueEnforcer.notEmpty (sText, "Text");
168     m_aContent.add (sText);
169   }
170 
171   public boolean hasAnyText ()
172   {
173     return m_aContent.containsAny (e -> e instanceof String);
174   }
175 
176   @Nonnull
177   @ReturnsMutableCopy
178   public ICommonsList <String> getAllTexts ()
179   {
180     return m_aContent.getAllInstanceOf (String.class);
181   }
182 
183   public void addValueOf (@Nonnull final PSValueOf aValueOf)
184   {
185     ValueEnforcer.notNull (aValueOf, "ValueOf");
186     m_aContent.add (aValueOf);
187   }
188 
189   @Nonnull
190   @ReturnsMutableCopy
191   public ICommonsList <PSValueOf> getAllValueOfs ()
192   {
193     return m_aContent.getAllInstanceOf (PSValueOf.class);
194   }
195 
196   public void addEmph (@Nonnull final PSEmph aEmph)
197   {
198     ValueEnforcer.notNull (aEmph, "Emph");
199     m_aContent.add (aEmph);
200   }
201 
202   @Nonnull
203   @ReturnsMutableCopy
204   public ICommonsList <PSEmph> getAllEmphs ()
205   {
206     return m_aContent.getAllInstanceOf (PSEmph.class);
207   }
208 
209   public void addDir (@Nonnull final PSDir aDir)
210   {
211     ValueEnforcer.notNull (aDir, "Dir");
212     m_aContent.add (aDir);
213   }
214 
215   @Nonnull
216   @ReturnsMutableCopy
217   public ICommonsList <PSDir> getAllDirs ()
218   {
219     return m_aContent.getAllInstanceOf (PSDir.class);
220   }
221 
222   public void addSpan (@Nonnull final PSSpan aSpan)
223   {
224     ValueEnforcer.notNull (aSpan, "Span");
225     m_aContent.add (aSpan);
226   }
227 
228   @Nonnull
229   @ReturnsMutableCopy
230   public ICommonsList <PSSpan> getAllSpans ()
231   {
232     return m_aContent.getAllInstanceOf (PSSpan.class);
233   }
234 
235   /**
236    * @return A list of {@link String}, {@link PSValueOf}, {@link PSEmph},
237    *         {@link PSDir} and {@link PSSpan} elements.
238    */
239   @Nonnull
240   @ReturnsMutableCopy
241   public ICommonsList <Object> getAllContentElements ()
242   {
243     return m_aContent.getClone ();
244   }
245 
246   @Nonnull
247   public IMicroElement getAsMicroElement ()
248   {
249     final IMicroElement ret = new MicroElement (CSchematron.NAMESPACE_SCHEMATRON, CSchematronXML.ELEMENT_DIAGNOSTIC);
250     ret.setAttribute (CSchematronXML.ATTR_ID, m_sID);
251     if (m_aRich != null)
252       m_aRich.fillMicroElement (ret);
253     if (m_aForeignElements != null)
254       for (final IMicroElement aForeignElement : m_aForeignElements)
255         ret.appendChild (aForeignElement.getClone ());
256     for (final Object aContent : m_aContent)
257       if (aContent instanceof String)
258         ret.appendText ((String) aContent);
259       else
260         ret.appendChild (((IPSElement) aContent).getAsMicroElement ());
261     if (m_aForeignAttrs != null)
262       for (final Map.Entry <String, String> aEntry : m_aForeignAttrs.entrySet ())
263         ret.setAttribute (aEntry.getKey (), aEntry.getValue ());
264     return ret;
265   }
266 
267   @Nonnull
268   public PSDiagnostic getClone ()
269   {
270     final PSDiagnostic ret = new PSDiagnostic ();
271     ret.setID (m_sID);
272     ret.setRich (getRichClone ());
273     for (final Object aContent : m_aContent)
274     {
275       if (aContent instanceof String)
276         ret.addText ((String) aContent);
277       else
278         if (aContent instanceof PSValueOf)
279           ret.addValueOf (((PSValueOf) aContent).getClone ());
280         else
281           if (aContent instanceof PSEmph)
282             ret.addEmph (((PSEmph) aContent).getClone ());
283           else
284             if (aContent instanceof PSDir)
285               ret.addDir (((PSDir) aContent).getClone ());
286             else
287               if (aContent instanceof PSSpan)
288                 ret.addSpan (((PSSpan) aContent).getClone ());
289               else
290                 throw new IllegalStateException ("Unexpected content element: " + aContent);
291     }
292     if (hasForeignElements ())
293       ret.addForeignElements (m_aForeignElements);
294     if (hasForeignAttributes ())
295       ret.addForeignAttributes (m_aForeignAttrs);
296     return ret;
297   }
298 
299   @Override
300   public String toString ()
301   {
302     return new ToStringGenerator (this).appendIfNotNull ("id", m_sID)
303                                        .appendIfNotNull ("rich", m_aRich)
304                                        .appendIf ("content", m_aContent, CollectionHelper::isNotEmpty)
305                                        .appendIf ("foreignAttrs", m_aForeignAttrs, CollectionHelper::isNotEmpty)
306                                        .appendIf ("foreignElements", m_aForeignElements, CollectionHelper::isNotEmpty)
307                                        .getToString ();
308   }
309 }