001package Torello.HTML.NodeSearch;
002
003import java.util.regex.*;
004import java.util.stream.Stream;
005
006import java.util.function.Predicate;
007
008import Torello.HTML.TagNode;
009
010/**
011 * An exception used for problems generated by either HTML-Tag <B>in-line</B> CSS
012 * <CODE>STYLE='&#46;&#46;&#46;'</CODE> attributes, or by explicitly declared CSS
013 * <CODE>&lt;STYLE TYPE='text/css'&gt;</CODE> <B>style-blocks</B>.
014 * 
015 * <BR /><BR />
016 * This class does some passed-parameter checking for the HTML-Search routines.  If one is
017 * using the {@code class TextComparitor}, it is important to remember that it usually works in 
018 * coordination with Java's var-args syntax which allows anywhere from {@code '0'} to
019 * {@code 'n' String's}.  This class of exception will be thrown if any one of the
020 * {@code String's} passed to the var-args parameter with the {@code TextComparitor} them has an
021 * invalid CSS Token according to the definition of valid CSS Naming-Convention Tokens.
022 *
023 * <BR /><BR /><B CLASS=JDDescLabel>NOTE:</B>
024 * 
025 * <BR />This exception test is only performed when a {@link TextComparitor} is used with one of
026 * the {@link TagNode} CSS {@code 'class'} getter and setter methods, for example:
027 * {@link TagNode#setCSSClasses​(SD, boolean, String[])}
028 *
029 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=EXPM>
030 */
031public class CSSStrException extends TCCompareStrException
032{
033    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDEX>  */
034    public static final long serialVersionUID = 1;
035
036    /**
037     * This regular-expression can be used to identify valid versus invalid CSS Class-Names, and
038     * also CSS ID-Names. In English, this regular-expression says: A valid name should start with
039     * an underscore (_), a hyphen (-) or a letter (a-z) which is followed by any numbers, hyphens,
040     * underscores, letters. A name should be at least two characters long. Cannot start with a
041     * digit, two hyphens or a hyphen followed by a number.
042     */
043    public static final Pattern VALID_CSS_CLASS_OR_NAME_TOKEN = 
044        Pattern.compile("^[_\\-a-zA-Z]+[_\\-a-zA-Z0-9]*$");
045
046    /**
047     * Regular-Expression as {@code Predicate<String>}
048     * @see #VALID_CSS_CLASS_OR_NAME_TOKEN
049     */
050    public static final Predicate<String> VALID_CSS_CLASS_OR_NAME_TOKEN_PRED =
051        VALID_CSS_CLASS_OR_NAME_TOKEN.asPredicate();
052
053    /**
054     * This constructor just calls the line: {@code super(message, compareStr, i);}
055     * 
056     * @param message the detail message.
057     * 
058     * @param compareStr This <I>SHOULD BE</I> the list of compare-strings that were passed to a
059     * TextComparitor that may have contained a null value, or was empty.  It is important not to
060     * pass null for this parameter, because checking the input to an exception's constructor is
061     * not very wise... "Exceptions about Exceptions" is usually unintelligible to a programmer.
062     * 
063     * @param i This is the index into the compareStr var-args parameter list that cause the
064     * exception to throw.
065     */
066    public CSSStrException(String message, String[] compareStr, int i)
067    { super(message, compareStr, i); }
068
069    /**
070     * This constructor just calls the line: {@code super(message, cause, compareStr, i);}
071     * 
072     * <BR /><BR /><DIV CLASS=JDHint>
073     * <B STYLE='color:red;'>Note:</B> The detail message associated with cause is not
074     * automatically incorporated into this exception's detail message.
075     * </DIV>
076     * 
077     * @param message The detail message (which is saved for later retrieval by the
078     * {@code Throwable.getMessage()} method).
079     * 
080     * @param cause This is sometimes used to "chain" exceptions in multi-threaded or heavily I/O
081     * related code.
082     * 
083     * @param compareStr This <I>SHOULD BE</I> the list of compare-strings that were passed to a
084     * TextComparitor that may have contained a null value, or was empty.  It is important not to
085     * pass null for this parameter, because checking the input to an exception's constructor is
086     * not very wise... "Exceptions about Exceptions" is usually unintelligible to a programmer.
087     * 
088     * @param i This is the index into the compareStr var-args parameter list that cause the
089     * exception to throw.
090     */
091    public CSSStrException(String message, Throwable cause, String[] compareStr, int i)
092    { super(message, cause, compareStr, i); }
093
094    /**
095     * This will do a simple test of the compare-{@code String's} to make sure none of them are
096     * null, and that there are at least one {@code String} in the array.
097     *
098     * <!-- This Explanation has been Copied from Torello.Java.StrCmprException -->
099     * <BR /><BR /><B CLASS=JDDescLabel>Explanation:</B>
100     * 
101     * <BR />It is a subtle issue, but likely better to throw exceptions when one of the Varargs to
102     * a {@code String} comparison is {@code 'null'}.  Since there is likely no general-convention
103     * or agreement on what {@code null} in the presence of {@code logical AND, OR, NOT, XOR, NAND}
104     * really means, throwing a {@code StrCmprException} <I>when even one var-args string-parameter
105     * is {@code 'null'}</I> actually makes the most sense.
106     * 
107     * @param compareStr This should be the var-args parameter that was passed to a search method
108     * 
109     * @throws CSSStrException If any of the elements of Var-Args parameter {@code 'compareStr'}
110     * is null, or if the array is zero-length.
111     * 
112     * @see #VALID_CSS_CLASS_OR_NAME_TOKEN
113     * @see TCCompareStrException#check(String[])
114     */
115    public static void check(String... compareStr)
116    {
117        // Repackages the Exception into one with the name CSSStrException
118        try
119            { TCCompareStrException.check(compareStr); }
120        catch (TCCompareStrException e)
121        {
122            String[] tempV = new String[compareStr.length];
123            throw new CSSStrException(
124                e.getMessage() + "\nSee getCause() for details.",
125                e, e.compareStrVec.toArray(tempV), e.i
126            );
127        }
128
129        for (int i=0; i < compareStr.length; i++)
130
131            if (! VALID_CSS_CLASS_OR_NAME_TOKEN.matcher(compareStr[i]).find())
132
133                throw new CSSStrException(
134                    "One of the compare-strings passed to a search-method's var-args String " +
135                    "parameter 'compareStr': [" + compareStr[i] + "]\n" +
136                    "Did not pass the CSS Token-Naming Testing Regular-Expression: " +
137                    "[" + VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "]\n" +
138                    "And this means it has been identified as an invalid CSS Token.  " +
139                    "This is not allowed here.\n" +
140                    "If you are using TagNode.cssClasses(), switch to TagNode.AV('class').\n" +
141                    "If you are using TextComparitor.CONTAINS_CSS_CLASS*, switch to " +
142                    "TextComparitor.EQ_CI_TRM",
143                    compareStr, i
144                );
145    }
146
147    /**
148     * This will do a simple test of the compare-{@code String's} to make sure none of them are
149     * null, and that there are at least one {@code String} in the array.
150     *
151     * <!-- This Explanation has been Copied from Torello.Java.StrCmprException -->
152     * <BR /><BR /><B CLASS=JDDescLabel>Explanation:</B>
153     * 
154     * <BR />It is a subtle issue, but likely better to throw exceptions when one of the Varargs to
155     * a {@code String} comparison is {@code 'null'}.  Since there is likely no general-convention
156     * or agreement on what {@code null} in the presence of {@code logical AND, OR, NOT, XOR, NAND}
157     * really means, throwing a {@code StrCmprException} <I>when even one var-args string-parameter
158     * is {@code 'null'}</I> actually makes the most sense.
159     * 
160     * @param compareStrs This should be the any instance of {@code Stream<String>}
161     * 
162     * @throws CSSStrException If any of the elements of the {@code compareStrs} stream are null,
163     * or if the stream is empty.
164     * 
165     * @see #VALID_CSS_CLASS_OR_NAME_TOKEN
166     * @see TCCompareStrException#check(String[])
167     */
168    public static Stream<String> check(Stream<String> compareStrs)
169    {
170        // Repackages the Exception into one with the name CSSStrException
171        String[] compareStr = compareStrs.toArray(String[]::new);
172
173        try
174            { TCCompareStrException.check(compareStr); }
175        catch (TCCompareStrException e)
176        {
177            String[] tempV = new String[compareStr.length];
178
179            throw new CSSStrException(
180                e.getMessage() + "\nSee getCause() for details.",
181                e, e.compareStrVec.toArray(tempV), e.i
182            );
183        }
184
185        for (int i=0; i < compareStr.length; i++) 
186
187            if (! VALID_CSS_CLASS_OR_NAME_TOKEN.matcher(compareStr[i]).find())
188
189                throw new CSSStrException(
190                    "One of the compare-strings passed to a search-method's var-args String " +
191                    "parameter 'compareStr': [" + compareStr[i] + "]\n" +
192                    "Did not pass the CSS Token-Naming Testing Regular-Expression: " +
193                    "[" + VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "]\n" +
194                    "And this means it has been identified as an invalid CSS Token.  " +
195                    "This is not allowed here.\n" +
196                    "If you are using TagNode.cssClasses(), switch to TagNode.AV('class').\n" +
197                    "If you are using TextComparitor.CONTAINS_CSS_CLASS*, switch to " +
198                    "TextComparitor.EQ_CI_TRM",
199                    compareStr, i
200                );
201
202        return Stream.of(compareStr);
203        // The original stream is now empty!  We must re-build it!
204    }
205}