changeset 1410:279dc6b31ebd

RT-22076: reinsert patch
author David Grieve<david.grieve@oracle.com>
date Tue, 03 Jul 2012 20:22:41 -0400
parents 53c9087d14fb
children 907808cece4e
files javafx-ui-common/src/com/sun/javafx/css/CompoundSelector.java javafx-ui-common/src/com/sun/javafx/css/Rule.java javafx-ui-common/src/com/sun/javafx/css/Selector.java javafx-ui-common/src/com/sun/javafx/css/SimpleSelector.java javafx-ui-common/src/com/sun/javafx/css/StyleManager.java javafx-ui-common/src/javafx/scene/Node.java javafx-ui-common/test/unit/com/sun/javafx/css/RuleTest.java
diffstat 7 files changed, 161 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-common/src/com/sun/javafx/css/CompoundSelector.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/css/CompoundSelector.java	Tue Jul 03 20:22:41 2012 -0400
@@ -211,7 +211,7 @@
     }
 
     @Override
-    boolean mightApply(final String className, final String id, final List<String> styleClasses) {
+    boolean mightApply(final String className, final String id, final long[] styleClasses) {
         return selectors.get(selectors.size()-1).mightApply(className, id, styleClasses);
     }
 
--- a/javafx-ui-common/src/com/sun/javafx/css/Rule.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/css/Rule.java	Tue Jul 03 20:22:41 2012 -0400
@@ -122,7 +122,7 @@
         return matches;
     }
 
-    public boolean mightApply(String className, String id, List<String> styleClasses) {
+    public boolean mightApply(String className, String id, long[] styleClasses) {
         for (int i = 0; i < selectors.size(); i++) {
             Selector sel = selectors.get(i);
             if (sel.mightApply(className, id, styleClasses)) return true;
--- a/javafx-ui-common/src/com/sun/javafx/css/Selector.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/css/Selector.java	Tue Jul 03 20:22:41 2012 -0400
@@ -81,7 +81,8 @@
     abstract Match matches(Scene scene);
     // same as the matches method expect return true/false rather than a match
     public abstract boolean applies(Node node);
-    abstract boolean mightApply(String className, String id, List<String> styleClasses);
+    abstract boolean mightApply(String className, String id, long[] styleClasses);
+    
     /**
      * Determines whether the current state of the node and its parents
      * matches the pseudoclasses defined (if any) for this selector.
--- a/javafx-ui-common/src/com/sun/javafx/css/SimpleSelector.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/css/SimpleSelector.java	Tue Jul 03 20:22:41 2012 -0400
@@ -59,6 +59,105 @@
 final public class SimpleSelector extends Selector {
     static final private Object MAX_CLASS_DEPTH = 255;
     
+    //
+    // The Long value is a bit mask. The upper 4 bits of the mask are used to
+    // hold the index of the mask within the long[] and the remaining bits are
+    // used to hold the mask value. If, for example, "foo" is the 96th entry in
+    // styleClassMask, the upper 4 bits will be 0x01 (foo will be at mask[1]) 
+    // and the remaining bits will have the 36th bit set. 
+    //
+    // When creating the long[] bit set, you get the value from styleClassMask,
+    // mask and shift the upper 4 bits to get the index of the style class in 
+    // the long[], then or the value from styleClassMask with the mask[index]. 
+    // In our example, "foo" will always be at mask[1]
+    //
+    private static final Map<String,Long> styleClassMask = new HashMap<String,Long>();
+    
+    
+    // 4 is arbitrary but allows for 960 style classes. Well, actually 
+    // less since the first bit of each mask[index>0] is wasted since
+    // 61 % 60 is 1, even though the 61st entry is the zeroth value in mask[1]. 
+    // So, 945 style classes.
+    private static final int VALUE_BITS = Long.SIZE-4;
+    
+    // 0x0fffffffffffffff
+    private static final long VALUE_MASK = ~(0xfL << VALUE_BITS);
+        
+    /** 
+     * Convert styleClass string to a bit mask
+     * @param styleClass
+     * @return The upper 4 bits is an index into the long[] mask representation 
+     * of styleClasses. The remaining bits are the bit mask for this styleClass
+     * within the mask[index]
+     */
+    public static long getStyleClassMask(String styleClass) {
+        Long mask = styleClassMask.get(styleClass);
+        if (mask == null) {
+            final int size = styleClassMask.size();
+            final long element = size / VALUE_BITS; // use top bits for element
+            final int exp = size % VALUE_BITS; // remaining bits for value
+            mask = Long.valueOf(
+                (element << VALUE_BITS) | (1L << exp) // same as Math.pow(2,exp)
+            ); 
+            styleClassMask.put(styleClass, mask);
+        }
+        return mask.longValue();
+    }
+
+    /**
+     * Convert a list of style class strings to an array of bit masks.
+     * @param styleClasses
+     * @return The upper 4 bits of each element is an index into the long[] mask
+     * representation of styleClasses. The remaining bits of each element are 
+     * the bit mask for this styleClass within the mask[index]
+     */
+    public static long[] getStyleClassMasks(List<String> styleClasses) {
+        
+        long[] mask = new long[0]; // return zero length if styleClasses is null
+        
+        final int max = styleClasses != null ? styleClasses.size() : -1;
+        for (int n=0; n<max; n++) {
+            final String styleClass = styleClasses.get(n);
+            final long m = getStyleClassMask(styleClass);
+            final long element = (m & ~VALUE_MASK);
+            final int  index = (int)(element >> VALUE_BITS);
+            // need to grow?
+            if (index >= mask.length) {
+                final long[] temp = new long[index+1];
+                System.arraycopy(mask, 0, temp, 0, mask.length);
+                mask = temp;
+            }
+            mask[index] = mask[index] | m;
+        }
+        return mask;
+    }
+
+    public static List<String> getStyleClassStrings(long[] mask) {
+        
+        if (mask == null || mask.length == 0) return Collections.EMPTY_LIST;
+
+        final Map<Long,String> stringMap = new HashMap<Long,String>();
+        for (Map.Entry<String,Long> entry : styleClassMask.entrySet()) {
+            stringMap.put(entry.getValue(), entry.getKey());
+        }
+        final List<String> strings = new ArrayList<String>();
+        for(int index=0; index<mask.length; index++) {
+            final long m = mask[index];
+            final long element = (m & ~VALUE_MASK);
+            for (int exp=0; exp < VALUE_BITS; exp++) {
+                final long key = element | ((1L << exp) & m);
+                if (key != 0) {
+                    final String value = stringMap.get(key);
+                    if (value != null) strings.add(value);
+                }
+            }
+        }
+        // even though the list returned could be modified without causing 
+        // harm, returning an unmodifiableList is consistent with 
+        // SimpleSelector.getStyleClasses()         
+        return Collections.unmodifiableList(strings);
+    }    
+    
     /**
      * If specified in the CSS file, the name of the java class to which
      * this selector is applied. For example, if the CSS file had:
@@ -75,15 +174,16 @@
         return name;
     }
     
-    final private List<String> styleClasses;
-    
     /**
      * @return Immutable List&lt;String&gt; of style-classes of the selector
      */
     public List<String> getStyleClasses() {
-        return styleClasses;
+        return getStyleClassStrings(styleClassMasks);
     }
 
+    /** styleClasses converted to a set of bit masks */
+    final private long[] styleClassMasks;
+    
     final private List<String> pseudoclasses;
     /**
      * @return Immutable List&lt;String&gt; of pseudo-classes of the selector
@@ -122,11 +222,11 @@
         // then match needs to check name
         this.matchOnName = (name != null && !("".equals(name)) && !("*".equals(name)));
 
-        this.styleClasses = 
-                (styleClasses != null) 
-                ? Collections.unmodifiableList(styleClasses)  
-                : Collections.EMPTY_LIST;
-        this.matchOnStyleClass = (this.styleClasses.size() > 0);
+        this.styleClassMasks = 
+                (styleClasses != null && styleClasses.isEmpty() == false)
+                ? getStyleClassMasks(styleClasses)
+                : new long[0];
+        this.matchOnStyleClass = (this.styleClassMasks.length > 0);
 
         this.pseudoclasses = 
                 (pseudoclasses != null) 
@@ -152,7 +252,7 @@
     Match matches(final Node node) {
         if (applies(node)) {
             final int idCount = (matchOnId) ? 1 : 0;
-            return new Match(this, pseudoclasses, idCount, styleClasses.size());
+            return new Match(this, pseudoclasses, idCount, styleClassMasks.length);
         }
         return null;
     }
@@ -176,7 +276,7 @@
     }
 
     @Override 
-    public boolean applies(final Node node) {
+    public boolean applies(Node node) {
         // if the selector has an id,
         // then bail if it doesn't match the node's id
         // (do this first since it is potentially the cheapest check)
@@ -195,14 +295,14 @@
         }
 
         if (matchOnStyleClass) {
-            boolean styleClassMatch = matchStyleClasses(node.getStyleClass());
+            boolean styleClassMatch = matchStyleClasses(node.impl_cssGetStyleClassBits());
             if (!styleClassMatch) return false;
         }
         return true;
     }
 
     @Override
-    boolean mightApply(final String className, final String id, final List<String> styleClasses) {
+    boolean mightApply(final String className, final String id, final long[] styleClasses) {
         if (matchOnName && nameMatchesAtEnd(className)) return true;
         if (matchOnId   && this.id.equals(id)) return true;
         if (matchOnStyleClass) return matchStyleClasses(styleClasses);
@@ -246,8 +346,8 @@
     //
     // This rule matches when class="pastoral blue aqua marine" but does not
     // match for class="pastoral blue".
-    private boolean matchStyleClasses(final List<String> nodeStyleClasses) {
-        return isSubsetOf(styleClasses, nodeStyleClasses);
+    private boolean matchStyleClasses(long[] nodeStyleClasses) {
+        return isSubsetOf(styleClassMasks, nodeStyleClasses);
     }
 
     /**
@@ -260,36 +360,20 @@
       * return true if seq1 is a subset of seq2. That is, all the strings
       * in seq1 are contained in seq2
       */
-    boolean isSubsetOf(final List<String> seq1, final List<String> seq2) {
+    boolean isSubsetOf(long[] seq1, long[] seq2) {
+        
         // if one or the other is null, then they are a subset if both are null
         if (seq1 == null || seq2 == null) return seq1 == null && seq2 == null;
         
         // they are a subset if both are empty
-        if (seq1.isEmpty() && seq2.isEmpty()) return true;
+        if (seq1.length == 0 && seq2.length == 0) return true;
 
         // [foo bar] cannot be a subset of [foo]
-        if (seq1.size() > seq2.size()) return false;
-
-        // is [foo] a subset of [foo bar bang]?
-        // Just need to find the first string in seq2 that equals seq1[0]
-        if (seq1.size() == 1) {
-            final String otherString = seq1.get(0);
-            if (otherString == null) return false;
-
-            for (int n=0, max=seq2.size(); n<max; n++) {
-                String item = seq2.get(n);
-                if (item == null) continue;
-                if (item.equals(otherString)) return true;
-            }
-            return false;
-        }
+        if (seq1.length > seq2.length) return false;
 
         // is [foo bar] a subset of [foo bar bang]?
-        // Check if each string in seq1 is in seq2
-        strSet.clear();
-        for (int n=0, max=seq2.size(); n<max; n++) strSet.add(seq2.get(n));
-        for (int n=0, max=seq1.size(); n<max; n++) {
-            if (! strSet.contains(seq1.get(n))) return false;
+        for (int n=0, max=seq1.length; n<max; n++) {
+            if ((seq1[n] & seq2[n]) != seq1[n]) return false;
         }
         return true;
 
@@ -313,7 +397,7 @@
         if ((this.id == null) ? (other.id != null) : !this.id.equals(other.id)) {
             return false;
         }
-        if (this.styleClasses != other.styleClasses && (this.styleClasses == null || !this.styleClasses.equals(other.styleClasses))) {
+        if (this.styleClassMasks != other.styleClassMasks && (this.styleClassMasks == null || !Arrays.equals(this.styleClassMasks, other.styleClassMasks))) {
             return false;
         }
         return true;
@@ -325,7 +409,7 @@
     @Override public int hashCode() {
         if (hash == -1) {
             hash = name.hashCode();
-            hash = 31 * (hash + styleClasses.hashCode());
+            hash = 31 * (hash + (styleClassMasks != null ? Arrays.hashCode(styleClassMasks) : 37));
             hash = 31 * (hash + (id != null ? id.hashCode() : 1229));
             hash = 31 * (int)(pclassMask ^ (pclassMask >>> 32));
         }
@@ -337,9 +421,12 @@
         StringBuilder sbuf = new StringBuilder();
         if (name != null && name.isEmpty() == false) sbuf.append(name);
         else sbuf.append("*");
-        for (int n=0; n<styleClasses.size(); n++) {
-            sbuf.append('.');
-            sbuf.append(styleClasses.get(n));
+        if (styleClassMasks != null && styleClassMasks.length > 0) {
+            List<String> strings = getStyleClassStrings(styleClassMasks);
+            for(String styleClass : strings) {
+                sbuf.append('.');
+                sbuf.append(styleClass);
+            }
         }
         if (id != null && id.isEmpty() == false) {
             sbuf.append('#');
@@ -357,6 +444,7 @@
     {
         super.writeBinary(os, stringStore);
         os.writeShort(stringStore.addString(name));
+        final List<String> styleClasses = getStyleClasses();
         os.writeShort(styleClasses.size());
         for (String sc  : styleClasses) os.writeShort(stringStore.addString(sc));
         os.writeShort(stringStore.addString(id));
--- a/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Tue Jul 03 20:22:41 2012 -0400
@@ -881,7 +881,10 @@
                 if (value != null) strings.add(value);
             }
         }
-        return strings;
+        // even though the list returned could be modified without causing 
+        // harm, returning an unmodifiableList is consistent with 
+        // SimpleSelector.getStyleClasses()
+        return Collections.unmodifiableList(strings);
     }
 
     /*
@@ -1142,7 +1145,7 @@
             // of the node and lookup the associated Cache in the cacheMap
             final String className = node.getClass().getName();
             final String id = node.getId();
-            final List<String> styleClass = node.getStyleClass();
+            final long[] styleClass = node.impl_cssGetStyleClassBits();
             
             final int[] indicesOfParentsWithStylesheets =  
                 getIndicesOfParentsWithStylesheets(
@@ -1325,12 +1328,7 @@
                 final Key newKey = new Key();
                 newKey.className = className;
                 newKey.id = id;
-                // Copy the list.
-                // If the contents of the Node's styleClass changes,
-                // the cacheMap lookup should miss.
-                final int nElements = styleClass.size();
-                newKey.styleClass = new ArrayList<String>(nElements);
-                for(int n=0; n<nElements; n++) newKey.styleClass.add(styleClass.get(n));
+                newKey.styleClass = styleClass;
                 newKey.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
                 
                 cache = new Cache(this, rules, pseudoclassStateMask, impactsChildren);
@@ -1545,7 +1543,7 @@
         // necessary.
         String className;
         String id;
-        List<String> styleClass;
+        long[] styleClass;
         
         // this will be initialized if a Parent has a stylesheet and will 
         // hold the indices of those Parents with stylesheets (the Parent's
@@ -1573,7 +1571,7 @@
                         || (id != null && id.equals(other.id))
                        )
                     && (   (styleClass == null && other.styleClass == null)
-                        || (styleClass != null && styleClass.containsAll(other.styleClass))
+                        || (styleClass != null && Arrays.equals(styleClass, other.styleClass))
                        );
                 
                 if (eq && indices != null) {
@@ -1600,7 +1598,7 @@
         public int hashCode() {
             int hash = className.hashCode();
             hash = 31 * (hash + ((id == null || id.isEmpty()) ? 1231 : id.hashCode()));
-            hash = 31 * (hash + ((styleClass == null || styleClass.isEmpty()) ? 1237 : styleClass.hashCode()));
+            hash = 31 * (hash + ((styleClass == null) ? 1237 : Arrays.hashCode(styleClass)));
             if (indices != null) hash = 31 * (hash + Arrays.hashCode(indices));
             return hash;
         }
--- a/javafx-ui-common/src/javafx/scene/Node.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Tue Jul 03 20:22:41 2012 -0400
@@ -793,6 +793,9 @@
     private ObservableList<String> styleClass = new TrackableObservableList<String>() {
         @Override
         protected void onChanged(Change<String> c) {
+            // setting styleClassBits to null will cause the bits to get
+            // recalculated on the next call to impl_cssGetStyleClassBits
+            styleClassBits = null;
             impl_reapplyCSS();
         }
 
@@ -815,6 +818,20 @@
         }
     };
     
+    private long[] styleClassBits = new long[0];
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    public final long[] impl_cssGetStyleClassBits() {
+        if (styleClassBits == null) {
+            styleClassBits = com.sun.javafx.css.SimpleSelector.getStyleClassMasks(styleClass);
+        }
+        // return a copy so caller can't alter styleClassBits itself.
+        return Arrays.copyOf(styleClassBits, styleClassBits.length);
+    }
+    
     public final ObservableList<String> getStyleClass() { 
         return styleClass; 
     }
--- a/javafx-ui-common/test/unit/com/sun/javafx/css/RuleTest.java	Tue Jul 03 13:10:38 2012 -0700
+++ b/javafx-ui-common/test/unit/com/sun/javafx/css/RuleTest.java	Tue Jul 03 20:22:41 2012 -0400
@@ -151,9 +151,10 @@
         String className = "";
         String id = "";
         List<String> styleClasses = null;
+        long[] styleClassBits = null;
         Rule instance = null;
         boolean expResult = false;
-        boolean result = instance.mightApply(className, id, styleClasses);
+        boolean result = instance.mightApply(className, id, styleClassBits);
         assertEquals(expResult, result);
         fail("The test case is a prototype.");
     }