]> granicus.if.org Git - icu/commitdiff
ICU-6591 make ccc=x work for all integers 0..255
authorMarkus Scherer <markus.icu@gmail.com>
Fri, 11 Aug 2017 23:42:02 +0000 (23:42 +0000)
committerMarkus Scherer <markus.icu@gmail.com>
Fri, 11 Aug 2017 23:42:02 +0000 (23:42 +0000)
X-SVN-Rev: 40328

icu4c/source/common/uniset_props.cpp
icu4c/source/test/intltest/usettest.cpp
icu4c/source/test/intltest/usettest.h
icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeSet.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/lang/UnicodeSetTest.java

index f87be3d2ed9d2d767d59c926a60bd0646f42886d..9f6480b54281a0aa4ff6bc75e35acb45b8e3cc84 100644 (file)
@@ -987,7 +987,7 @@ UnicodeSet::applyPropertyAlias(const UnicodeString& prop,
 
     UProperty p;
     int32_t v;
-    UBool mustNotBeEmpty = FALSE, invert = FALSE;
+    UBool invert = FALSE;
 
     if (value.length() > 0) {
         p = u_getPropertyEnum(pname.data());
@@ -1009,15 +1009,13 @@ UnicodeSet::applyPropertyAlias(const UnicodeString& prop,
                     p == UCHAR_LEAD_CANONICAL_COMBINING_CLASS) {
                     char* end;
                     double value = uprv_strtod(vname.data(), &end);
+                    // Anything between 0 and 255 is valid even if unused.
                     // Cast double->int only after range check.
                     if (*end != 0 || value < 0 || 255 < value ||
                             (v = (int32_t)value) != value) {
                         // non-integral value or outside 0..255, or trailing junk
                         FAIL(ec);
                     }
-                    // If the resultant set is empty then the numeric value
-                    // was invalid.
-                    mustNotBeEmpty = TRUE;
                 } else {
                     FAIL(ec);
                 }
@@ -1116,12 +1114,6 @@ UnicodeSet::applyPropertyAlias(const UnicodeString& prop,
         complement();
     }
 
-    if (U_SUCCESS(ec) && (mustNotBeEmpty && isEmpty())) {
-        // mustNotBeEmpty is set to true if an empty set indicates
-        // invalid input.
-        ec = U_ILLEGAL_ARGUMENT_ERROR;
-    }
-
     if (isBogus() && U_SUCCESS(ec)) {
         // We likely ran out of memory. AHHH!
         ec = U_MEMORY_ALLOCATION_ERROR;
index ed6419a0ebde476ee53f2465745d665410759af4..a6ba3e86253f1e0c98db47814a4c72a0d8de1f9e 100644 (file)
@@ -97,6 +97,7 @@ UnicodeSetTest::runIndexedTest(int32_t index, UBool exec,
     TESTCASE_AUTO(TestStringSpan);
     TESTCASE_AUTO(TestUCAUnsafeBackwards);
     TESTCASE_AUTO(TestIntOverflow);
+    TESTCASE_AUTO(TestUnusedCcc);
     TESTCASE_AUTO_END;
 }
 
@@ -3934,5 +3935,34 @@ void UnicodeSetTest::TestIntOverflow() {
     IcuTestErrorCode errorCode(*this, "TestIntOverflow");
     UnicodeSet set(u"[:ccc=2222222222222222222:]", errorCode);
     assertTrue("[:ccc=int_overflow:] -> empty set", set.isEmpty());
-    assertEquals("[:ccc=int_overflow:] -> illegal argument", U_ILLEGAL_ARGUMENT_ERROR, errorCode.reset());
+    assertEquals("[:ccc=int_overflow:] -> illegal argument",
+                 U_ILLEGAL_ARGUMENT_ERROR, errorCode.reset());
+}
+
+void UnicodeSetTest::TestUnusedCcc() {
+    // All numeric ccc values 0..255 are valid, but many are unused.
+    IcuTestErrorCode errorCode(*this, "TestUnusedCcc");
+    UnicodeSet ccc2(u"[:ccc=2:]", errorCode);
+    assertSuccess("[:ccc=2:]", errorCode);
+    assertTrue("[:ccc=2:] -> empty set", ccc2.isEmpty());
+
+    UnicodeSet ccc255(u"[:ccc=255:]", errorCode);
+    assertSuccess("[:ccc=255:]", errorCode);
+    assertTrue("[:ccc=255:] -> empty set", ccc255.isEmpty());
+
+    // Non-integer values and values outside 0..255 are invalid.
+    UnicodeSet ccc_1(u"[:ccc=-1:]", errorCode);
+    assertEquals("[:ccc=-1:] -> illegal argument",
+                 U_ILLEGAL_ARGUMENT_ERROR, errorCode.reset());
+    assertTrue("[:ccc=-1:] -> empty set", ccc_1.isEmpty());
+
+    UnicodeSet ccc256(u"[:ccc=256:]", errorCode);
+    assertEquals("[:ccc=256:] -> illegal argument",
+                 U_ILLEGAL_ARGUMENT_ERROR, errorCode.reset());
+    assertTrue("[:ccc=256:] -> empty set", ccc256.isEmpty());
+
+    UnicodeSet ccc1_1(u"[:ccc=1.1:]", errorCode);
+    assertEquals("[:ccc=1.1:] -> illegal argument",
+                 U_ILLEGAL_ARGUMENT_ERROR, errorCode.reset());
+    assertTrue("[:ccc=1.1:] -> empty set", ccc1_1.isEmpty());
 }
index f0fe250114f3ec28d71c9dc4e80b035cb7883d12..b34728ae6316bdb3bffeedd18eea44876d9358f6 100644 (file)
@@ -92,6 +92,7 @@ private:
 
     void TestUCAUnsafeBackwards();
     void TestIntOverflow();
+    void TestUnusedCcc();
 
 private:
 
index 92fb31592748e25984ac4e7ad3ba23dc17674c72..3270541e19cbc3d122227b47d8b4deb8f440141f 100644 (file)
@@ -3443,7 +3443,7 @@ public class UnicodeSet extends UnicodeFilter implements Iterable<String>, Compa
         checkFrozen();
         int p;
         int v;
-        boolean mustNotBeEmpty = false, invert = false;
+        boolean invert = false;
 
         if (symbols != null
                 && (symbols instanceof XSymbolTable)
@@ -3476,10 +3476,7 @@ public class UnicodeSet extends UnicodeFilter implements Iterable<String>, Compa
                             p == UProperty.LEAD_CANONICAL_COMBINING_CLASS ||
                             p == UProperty.TRAIL_CANONICAL_COMBINING_CLASS) {
                         v = Integer.parseInt(PatternProps.trimWhiteSpace(valueAlias));
-                        // If the resultant set is empty then the numeric value
-                        // was invalid.
-                        //mustNotBeEmpty = true;
-                        // old code was wrong; anything between 0 and 255 is valid even if unused.
+                        // Anything between 0 and 255 is valid even if unused.
                         if (v < 0 || v > 255) throw e;
                     } else {
                         throw e;
@@ -3580,12 +3577,6 @@ public class UnicodeSet extends UnicodeFilter implements Iterable<String>, Compa
             complement();
         }
 
-        if (mustNotBeEmpty && isEmpty()) {
-            // mustNotBeEmpty is set to true if an empty set indicates
-            // invalid input.
-            throw new IllegalArgumentException("Invalid property value");
-        }
-
         return this;
     }
 
index 6472e735e5041b166eb69c72e95757bb8a8c2941..c5f92953a0b06de15aeba98a7a022e309c42241c 100644 (file)
@@ -84,7 +84,7 @@ public class UnicodeSetTest extends TestFmwk {
 
     @Test
     public void TestPropertyAccess() {
-        int count = 0; 
+        int count = 0;
         // test to see that all of the names work
         for (int propNum = UProperty.BINARY_START; propNum < UProperty.INT_LIMIT; ++propNum) {
             count++;
@@ -130,7 +130,7 @@ public class UnicodeSetTest extends TestFmwk {
                         }
                     } catch (RuntimeException e1) {
                         errln("Can't get property value name for: "
-                                + "Property (" + propNum + "): " + propName + ", " 
+                                + "Property (" + propNum + "): " + propName + ", "
                                 + "Value (" + valueNum + ") "
                                 + ", NameChoice: " + nameChoice + ", "
                                 + e1.getClass().getName());
@@ -142,7 +142,7 @@ public class UnicodeSetTest extends TestFmwk {
                         testSet = new UnicodeSet("[:" + propName + "=" + valueName + ":]");
                     } catch (RuntimeException e) {
                         errln("Can't create UnicodeSet for: "
-                                + "Property (" + propNum + "): " + propName + ", " 
+                                + "Property (" + propNum + "): " + propName + ", "
                                 + "Value (" + valueNum + "): " + valueName + ", "
                                 + e.getClass().getName());
                         continue;
@@ -155,13 +155,13 @@ public class UnicodeSetTest extends TestFmwk {
                         }
                     }
                     if (collectedErrors.size() != 0) {
-                        errln("Property Value Differs: " 
-                                + "Property (" + propNum + "): " + propName + ", " 
+                        errln("Property Value Differs: "
+                                + "Property (" + propNum + "): " + propName + ", "
                                 + "Value (" + valueNum + "): " + valueName + ", "
                                 + "Differing values: " + collectedErrors.toPattern(true));
                     }
                 }
-            } 
+            }
         }
     }
 
@@ -183,7 +183,7 @@ public class UnicodeSetTest extends TestFmwk {
                 if (!toPatternAux(0, i)) continue;
                 if (!toPatternAux(i, 0xFFFF)) continue;
             }
-        } 
+        }
 
         // Test pattern behavior of multicharacter strings.
         UnicodeSet s = new UnicodeSet("[a-z {aa} {ab}]");
@@ -211,7 +211,7 @@ public class UnicodeSetTest extends TestFmwk {
                 new String[] {"abc", NOT, "ab"});
 
         // JB#3400: For 2 character ranges prefer [ab] to [a-b]
-        s.clear(); 
+        s.clear();
         s.add('a', 'b');
         expectToPattern(s, "[ab]", null);
 
@@ -244,7 +244,7 @@ public class UnicodeSetTest extends TestFmwk {
     }
 
     static String[] OTHER_TOPATTERN_TESTS = {
-        "[[:latin:]&[:greek:]]", 
+        "[[:latin:]&[:greek:]]",
         "[[:latin:]-[:greek:]]",
         "[:nonspacing mark:]"
     };
@@ -456,7 +456,7 @@ public class UnicodeSetTest extends TestFmwk {
         for (int i=0; i<0x200; ++i) {
             boolean l = UCharacter.isLetter(i);
             if (l != set.contains((char)i)) {
-                errln("FAIL: L contains " + (char)i + " = " + 
+                errln("FAIL: L contains " + (char)i + " = " +
                         set.contains((char)i));
                 if (++failures == 10) break;
             }
@@ -466,7 +466,7 @@ public class UnicodeSetTest extends TestFmwk {
         for (int i=0; i<0x200; ++i) {
             boolean lu = (UCharacter.getType(i) == ECharacterCategory.UPPERCASE_LETTER);
             if (lu != set.contains((char)i)) {
-                errln("FAIL: Lu contains " + (char)i + " = " + 
+                errln("FAIL: Lu contains " + (char)i + " = " +
                         set.contains((char)i));
                 if (++failures == 20) break;
             }
@@ -653,7 +653,7 @@ public class UnicodeSetTest extends TestFmwk {
             logln("bitsToSet(setToBits(c)): " + c);
         } else {
             errln("FAIL: bitsToSet(setToBits(c)) = " + c + ", expect " + exp);
-        } 
+        }
 
         // Additional tests for coverage JB#2118
         //UnicodeSet::complement(class UnicodeString const &)
@@ -744,10 +744,10 @@ public class UnicodeSetTest extends TestFmwk {
         }
 
         {
-            //Cover addAll(Collection) and addAllTo(Collection)  
+            //Cover addAll(Collection) and addAllTo(Collection)
             //  Seems that there is a bug in addAll(Collection) operation
             //    Ram also add a similar test to UtilityTest.java
-            logln("Testing addAll(Collection) ... ");  
+            logln("Testing addAll(Collection) ... ");
             String[] array = {"a", "b", "c", "de"};
             List list = Arrays.asList(array);
             Set aset = new HashSet(list);
@@ -783,20 +783,20 @@ public class UnicodeSetTest extends TestFmwk {
         //  Object[][] testList = {
         //  {I_EQUALS,  UnicodeSet.fromAll("abc"),
         //  new UnicodeSet("[a-c]")},
-        //  
+        //
         //  {I_EQUALS,  UnicodeSet.from("ch").add('a','z').add("ll"),
         //  new UnicodeSet("[{ll}{ch}a-z]")},
-        //  
-        //  {I_EQUALS,  UnicodeSet.from("ab}c"),  
+        //
+        //  {I_EQUALS,  UnicodeSet.from("ab}c"),
         //  new UnicodeSet("[{ab\\}c}]")},
-        //  
-        //  {I_EQUALS,  new UnicodeSet('a','z').add('A', 'Z').retain('M','m').complement('X'), 
+        //
+        //  {I_EQUALS,  new UnicodeSet('a','z').add('A', 'Z').retain('M','m').complement('X'),
         //  new UnicodeSet("[[a-zA-Z]&[M-m]-[X]]")},
         //  };
-        //  
+        //
         //  for (int i = 0; i < testList.length; ++i) {
         //  expectRelation(testList[i][0], testList[i][1], testList[i][2], "(" + i + ")");
-        //  }        
+        //  }
 
         UnicodeSet[][] testList = {
                 {UnicodeSet.fromAll("abc"),
@@ -805,10 +805,10 @@ public class UnicodeSetTest extends TestFmwk {
                     {UnicodeSet.from("ch").add('a','z').add("ll"),
                         new UnicodeSet("[{ll}{ch}a-z]")},
 
-                        {UnicodeSet.from("ab}c"),  
+                        {UnicodeSet.from("ab}c"),
                             new UnicodeSet("[{ab\\}c}]")},
 
-                            {new UnicodeSet('a','z').add('A', 'Z').retain('M','m').complement('X'), 
+                            {new UnicodeSet('a','z').add('A', 'Z').retain('M','m').complement('X'),
                                 new UnicodeSet("[[a-zA-Z]&[M-m]-[X]]")},
         };
 
@@ -816,10 +816,10 @@ public class UnicodeSetTest extends TestFmwk {
             if (!testList[i][0].equals(testList[i][1])) {
                 errln("FAIL: sets unequal; see source code (" + i + ")");
             }
-        }        
+        }
     }
 
-    static final Integer 
+    static final Integer
     I_ANY = new Integer(SortedSetRelation.ANY),
     I_CONTAINS = new Integer(SortedSetRelation.CONTAINS),
     I_DISJOINT = new Integer(SortedSetRelation.DISJOINT),
@@ -875,12 +875,12 @@ public class UnicodeSetTest extends TestFmwk {
 
         iset.add(new Integer(size + 1));    // add odd value in middle
 
-        CheckSpeed(iset, jset, "when a contains b", iterations);        
+        CheckSpeed(iset, jset, "when a contains b", iterations);
         CheckSpeed(jset, iset, "when b contains a", iterations);
 
         jset.add(new Integer(size - 1));    // add different odd value in middle
 
-        CheckSpeed(jset, iset, "when a, b are disjoint", iterations);        
+        CheckSpeed(jset, iset, "when a, b are disjoint", iterations);
     }
 
     void CheckSpeed(SortedSet iset, SortedSet jset, String message, int iterations) {
@@ -952,28 +952,28 @@ public class UnicodeSetTest extends TestFmwk {
 
     public static final String[] RELATION_NAME = {
         "both-are-null",
-        "a-is-null", 
-        "equals", 
+        "a-is-null",
+        "equals",
         "is-contained-in",
         "b-is-null",
         "is-disjoint_with",
-        "contains", 
+        "contains",
         "any", };
 
     boolean dumbHasRelation(Collection A, int filter, Collection B) {
         Collection ab = new TreeSet(A);
         ab.retainAll(B);
-        if (ab.size() > 0 && (filter & SortedSetRelation.A_AND_B) == 0) return false; 
+        if (ab.size() > 0 && (filter & SortedSetRelation.A_AND_B) == 0) return false;
 
         // A - B size == A.size - A&B.size
-        if (A.size() > ab.size() && (filter & SortedSetRelation.A_NOT_B) == 0) return false; 
+        if (A.size() > ab.size() && (filter & SortedSetRelation.A_NOT_B) == 0) return false;
 
         // B - A size == B.size - A&B.size
-        if (B.size() > ab.size() && (filter & SortedSetRelation.B_NOT_A) == 0) return false; 
+        if (B.size() > ab.size() && (filter & SortedSetRelation.B_NOT_A) == 0) return false;
 
 
         return true;
-    }    
+    }
 
     void checkSetRelation(SortedSet a, SortedSet b, String message) {
         for (int i = 0; i < 8; ++i) {
@@ -984,7 +984,7 @@ public class UnicodeSetTest extends TestFmwk {
             logln(message + " " + hasRelation + ":\t" + a + "\t" + RELATION_NAME[i] + "\t" + b);
 
             if (hasRelation != dumbHasRelation) {
-                errln("FAIL: " + 
+                errln("FAIL: " +
                         message + " " + dumbHasRelation + ":\t" + a + "\t" + RELATION_NAME[i] + "\t" + b);
             }
         }
@@ -1077,9 +1077,9 @@ public class UnicodeSetTest extends TestFmwk {
                 "\u03D6", // 1.1
                 "\u03D8\u03D9", // 3.2
 
-                "[:Age=3.1:]", 
-                "\\u1800\\u3400\\U0002f800", 
-                "\\u0220\\u034f\\u30ff\\u33ff\\ufe73\\U00010000\\U00050000", 
+                "[:Age=3.1:]",
+                "\\u1800\\u3400\\U0002f800",
+                "\\u0220\\u034f\\u30ff\\u33ff\\ufe73\\U00010000\\U00050000",
 
                 // JB#2350: Case_Sensitive
                 "[:Case Sensitive:]",
@@ -1168,7 +1168,7 @@ public class UnicodeSetTest extends TestFmwk {
                 "\\uFDF2"
         };
 
-        for (int i=0; i<DATA.length; i+=3) {  
+        for (int i=0; i<DATA.length; i+=3) {
             expectContainment(DATA[i], DATA[i+1], DATA[i+2]);
         }
     }
@@ -1319,7 +1319,7 @@ public class UnicodeSetTest extends TestFmwk {
 
                 CASE,
                 "[{F\uFB01}]",
-                "[\uFB03{ffi}]",            
+                "[\uFB03{ffi}]",
 
                 CASE,
                 "[a-z]","[A-Za-z\u017F\u212A]",
@@ -1615,7 +1615,8 @@ public class UnicodeSetTest extends TestFmwk {
         assertEquals("compareTo-shorter-first", goalShortest, sorted);
 
         TreeSet<UnicodeSet> sorted1 = new TreeSet<UnicodeSet>(new Comparator<UnicodeSet>(){
-            public int compare(UnicodeSet o1, UnicodeSet o2) {
+            @Override
+      public int compare(UnicodeSet o1, UnicodeSet o2) {
                 // TODO Auto-generated method stub
                 return o1.compareTo(o2, ComparisonStyle.LONGER_FIRST);
             }});
@@ -1625,7 +1626,8 @@ public class UnicodeSetTest extends TestFmwk {
         assertEquals("compareTo-longer-first", goalLongest, sorted);
 
         sorted1 = new TreeSet<UnicodeSet>(new Comparator<UnicodeSet>(){
-            public int compare(UnicodeSet o1, UnicodeSet o2) {
+            @Override
+      public int compare(UnicodeSet o1, UnicodeSet o2) {
                 // TODO Auto-generated method stub
                 return o1.compareTo(o2, ComparisonStyle.LEXICOGRAPHIC);
             }});
@@ -1931,7 +1933,8 @@ public class UnicodeSetTest extends TestFmwk {
         /* (non-Javadoc)
          * @see com.ibm.icu.text.SymbolTable#lookup(java.lang.String)
          */
-        public char[] lookup(String s) {
+        @Override
+    public char[] lookup(String s) {
             logln("TokenSymbolTable: lookup \"" + s + "\" => \"" +
                     new String((char[]) contents.get(s)) + "\"");
             return (char[])contents.get(s);
@@ -1940,7 +1943,8 @@ public class UnicodeSetTest extends TestFmwk {
         /* (non-Javadoc)
          * @see com.ibm.icu.text.SymbolTable#lookupMatcher(int)
          */
-        public UnicodeMatcher lookupMatcher(int ch) {
+        @Override
+    public UnicodeMatcher lookupMatcher(int ch) {
             return null;
         }
 
@@ -1948,7 +1952,8 @@ public class UnicodeSetTest extends TestFmwk {
          * @see com.ibm.icu.text.SymbolTable#parseReference(java.lang.String,
      java.text.ParsePosition, int)
          */
-        public String parseReference(String text, ParsePosition pos, int
+        @Override
+    public String parseReference(String text, ParsePosition pos, int
                 limit) {
             int cp;
             int start = pos.getIndex();
@@ -1982,7 +1987,7 @@ public class UnicodeSetTest extends TestFmwk {
                     CharsToUnicodeString("abc\\U00010000"),
                     "\uD800;\uDC00"); // split apart surrogate-pair
             if (set.size() != 4) {
-                errln(Utility.escape("FAIL: " + DATA[i] + ".size() == " + 
+                errln(Utility.escape("FAIL: " + DATA[i] + ".size() == " +
                         set.size() + ", expected 4"));
             }
         }
@@ -2385,11 +2390,11 @@ public class UnicodeSetTest extends TestFmwk {
             }
             boolean contained = set.contains(expStrings[i]);
             if (contained == in) {
-                logln("Ok: " + expPat + 
+                logln("Ok: " + expPat +
                         (contained ? " contains {" : " does not contain {") +
                         Utility.escape(expStrings[i]) + "}");
             } else {
-                errln("FAIL: " + expPat + 
+                errln("FAIL: " + expPat +
                         (contained ? " contains {" : " does not contain {") +
                         Utility.escape(expStrings[i]) + "}");
             }
@@ -2442,10 +2447,10 @@ public class UnicodeSetTest extends TestFmwk {
         assertEquals("", "M, a-c", CollectionUtilities.join(us1.ranges(), ", "));
 
         // Sample code
-        for (@SuppressWarnings("unused") EntryRange range : us1.ranges()) { 
-            // do something with code points between range.codepointEnd and range.codepointEnd; 
+        for (@SuppressWarnings("unused") EntryRange range : us1.ranges()) {
+            // do something with code points between range.codepointEnd and range.codepointEnd;
         }
-        for (@SuppressWarnings("unused") String s : us1.strings()) { 
+        for (@SuppressWarnings("unused") String s : us1.strings()) {
             // do something with each string;
         }
 
@@ -2479,7 +2484,7 @@ public class UnicodeSetTest extends TestFmwk {
         UnicodeSetSpanner m;
 
         m = new UnicodeSetSpanner(new UnicodeSet("[._]"));
-        assertEquals("", "abc", m.deleteFrom("_._a_._b_._c_._"));        
+        assertEquals("", "abc", m.deleteFrom("_._a_._b_._c_._"));
         assertEquals("", "_.__.__.__._", m.deleteFrom("_._a_._b_._c_._", SpanCondition.NOT_CONTAINED));
 
         assertEquals("", "a_._b_._c", m.trim("_._a_._b_._c_._"));
@@ -2511,11 +2516,11 @@ public class UnicodeSetTest extends TestFmwk {
         checkCodePoints("👦", "👧", CountMethod.MIN_ELEMENTS, SpanCondition.SIMPLE, null, 1);
     }
 
-    private void checkCodePoints(String a, String b, CountMethod quantifier, SpanCondition spanCondition, 
+    private void checkCodePoints(String a, String b, CountMethod quantifier, SpanCondition spanCondition,
             String expectedReplaced, int expectedCount) {
         final String ab = a+b;
         UnicodeSetSpanner m = new UnicodeSetSpanner(new UnicodeSet("[{" + a + "}]"));
-        assertEquals("new UnicodeSetSpanner(\"[{" + a + "}]\").countIn(\"" + ab + "\")", 
+        assertEquals("new UnicodeSetSpanner(\"[{" + a + "}]\").countIn(\"" + ab + "\")",
                 expectedCount,
                 callCountIn(m, ab, quantifier, spanCondition)
                 );
@@ -2523,7 +2528,7 @@ public class UnicodeSetTest extends TestFmwk {
         if (expectedReplaced == null) {
             expectedReplaced = "-" + b;
         }
-        assertEquals("new UnicodeSetSpanner(\"[{" + a + "}]\").replaceFrom(\"" + ab + "\", \"-\")", 
+        assertEquals("new UnicodeSetSpanner(\"[{" + a + "}]\").replaceFrom(\"" + ab + "\", \"-\")",
                 expectedReplaced, m.replaceFrom(ab, "-", quantifier));
     }
 
@@ -2657,7 +2662,7 @@ public class UnicodeSetTest extends TestFmwk {
         assertEquals("CharSequence complementAll", new UnicodeSet("[ABbc]"), new UnicodeSet("[a-cA]").complementAll(new StringBuilder("aB")) );
 
         // containment
-        assertEquals("CharSequence contains", true, new UnicodeSet("[a-cA{ab}]"). contains(new StringBuilder("ab")) ); 
+        assertEquals("CharSequence contains", true, new UnicodeSet("[a-cA{ab}]"). contains(new StringBuilder("ab")) );
         assertEquals("CharSequence containsNone", false, new UnicodeSet("[a-cA]"). containsNone(new StringBuilder("ab"))  );
         assertEquals("CharSequence containsSome", true, new UnicodeSet("[a-cA{ab}]"). containsSome(new StringBuilder("ab"))  );
 
@@ -2726,7 +2731,7 @@ public class UnicodeSetTest extends TestFmwk {
                 0, UnicodeSet.fromAll("a").compareTo(Collections.singleton("a")));
 
         // Longer is bigger
-        assertTrue("UnicodeSet is empty", 
+        assertTrue("UnicodeSet is empty",
                 UnicodeSet.ALL_CODE_POINTS.compareTo(test_set) > 0);
         assertTrue("UnicodeSet not empty",
                 UnicodeSet.EMPTY.compareTo(Collections.singleton("a")) < 0);
@@ -2739,4 +2744,33 @@ public class UnicodeSetTest extends TestFmwk {
         assertTrue("UnicodeSet comparison wrong",
                 UnicodeSet.fromAll("b").compareTo(Collections.singleton("a")) > 0);
     }
+
+    @Test
+    public void TestUnusedCcc() {
+        // All numeric ccc values 0..255 are valid, but many are unused.
+        UnicodeSet ccc2 = new UnicodeSet("[:ccc=2:]");
+        assertTrue("[:ccc=2:] -> empty set", ccc2.isEmpty());
+
+        UnicodeSet ccc255 = new UnicodeSet("[:ccc=255:]");
+        assertTrue("[:ccc=255:] -> empty set", ccc255.isEmpty());
+
+        // Non-integer values and values outside 0..255 are invalid.
+        try {
+            new UnicodeSet("[:ccc=-1:]");
+            fail("[:ccc=-1:] -> illegal argument");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            new UnicodeSet("[:ccc=256:]");
+            fail("[:ccc=256:] -> illegal argument");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            new UnicodeSet("[:ccc=1.1:]");
+            fail("[:ccc=1.1:] -> illegal argument");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
 }