You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by jw...@apache.org on 2010/07/22 21:29:21 UTC

svn commit: r966817 - in /myfaces/trinidad/trunk: trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/skin/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/...

Author: jwaldman
Date: Thu Jul 22 19:29:20 2010
New Revision: 966817

URL: http://svn.apache.org/viewvc?rev=966817&view=rev
Log:
TRINIDAD-636 Skinning an icon does not pick up base skin's non-overridden properties

Do not create the Icon Object in SkinStyleSheetParserUtils.java, like we did before.
Add StyleNode object to IconNode, so that an IconNode can have Styles.
Then use the reconciling code that we have in place in StyleSheetDocument for for StyleNodes to also work for IconNodes.
Test this out in purple skin.

Modified:
    myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleBigFontSkin.css
    myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleSkin.css
    myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/skin/SkinStyleSheetParserUtils.java
    myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/cache/FileSystemStyleCache.java
    myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/IconNode.java
    myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/StyleSheetDocument.java

Modified: myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleBigFontSkin.css
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleBigFontSkin.css?rev=966817&r1=966816&r2=966817&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleBigFontSkin.css (original)
+++ myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleBigFontSkin.css Thu Jul 22 19:29:20 2010
@@ -22,4 +22,12 @@
 
 .AFDefaultFont:alias {
 	font-size: 18pt;
-}
\ No newline at end of file
+}
+
+/* This tests that the other css properties are picked up from the base skin, in this case purple */
+/* This tests that the -tr-rule-ref does not yet work for icons. See TRINIDAD-17 */
+af|selectOrderShuttle::reorder-up-icon {
+  content: url(/skins/purple/images/prev.png);
+  -tr-rule-ref: selector(".AFDefaultFont:alias");
+   border: 2px solid purple;
+}

Modified: myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleSkin.css
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleSkin.css?rev=966817&r1=966816&r2=966817&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleSkin.css (original)
+++ myfaces/trinidad/trunk/trinidad-examples/trinidad-demo/src/main/webapp/skins/purple/purpleSkin.css Thu Jul 22 19:29:20 2010
@@ -914,7 +914,7 @@ af|navigationPane::tabs-inactive af|navi
 }
 
 .AFFooIcon:alias {content:url(/skins/purple/images/btns.gif); width:7px; height:18px}
-.AFBarIcon {content:url(/skins/purple/images/btns.gif); width:7px; height:18px}
+.AFBarIcon:alias {content:url(/skins/purple/images/btns.gif); width:7px; height:18px}
 
 @agent ie
 {
@@ -947,3 +947,14 @@ af|tree {
   af|selectBooleanCheckbox::label {color: fuschia}
 }
 
+/* This tests the ordering of includes */
+/* includes happen first no matter where they are, then the local properties get applied. 
+.second and .third resolve both resolve to color:red; background-coor:green} */
+.first {color: blue; background-color: green}
+.second {-tr-rule-ref:selector(".first"); color: red}
+.third {color: red; -tr-rule-ref:selector(".first");}
+
+
+
+
+

Modified: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/skin/SkinStyleSheetParserUtils.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/skin/SkinStyleSheetParserUtils.java?rev=966817&r1=966816&r2=966817&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/skin/SkinStyleSheetParserUtils.java (original)
+++ myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/skin/SkinStyleSheetParserUtils.java Thu Jul 22 19:29:20 2010
@@ -37,17 +37,11 @@ import org.apache.myfaces.trinidad.loggi
 import org.apache.myfaces.trinidad.share.io.InputStreamProvider;
 import org.apache.myfaces.trinidad.share.io.NameResolver;
 
-import org.apache.myfaces.trinidad.skin.Icon;
 import org.apache.myfaces.trinidad.util.URLUtils;
 import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinProperties;
 import org.apache.myfaces.trinidadinternal.share.expl.Coercions;
 import org.apache.myfaces.trinidadinternal.share.xml.ParseContext;
 import org.apache.myfaces.trinidadinternal.share.xml.XMLUtils;
-import org.apache.myfaces.trinidadinternal.skin.icon.ContextImageIcon;
-import org.apache.myfaces.trinidadinternal.skin.icon.NullIcon;
-import org.apache.myfaces.trinidadinternal.skin.icon.TextIcon;
-import org.apache.myfaces.trinidadinternal.skin.icon.URIImageIcon;
-import org.apache.myfaces.trinidadinternal.style.CSSStyle;
 import org.apache.myfaces.trinidadinternal.style.util.CSSUtils;
 import org.apache.myfaces.trinidadinternal.style.util.StyleUtils;
 import org.apache.myfaces.trinidadinternal.style.xml.parse.IconNode;
@@ -164,7 +158,7 @@ class SkinStyleSheetParserUtils
    * A StyleSheetEntry is an object that contains:
    * styleSheetName, StyleSheetDocument
    * A StyleSheetDocument contains StyleSheetNodes. A StyleSheetNode contains
-   * a list style selectors and their properties and additional info like
+   * StyleNodes, IconNodes, and SkinPropertyNodes and additional info like
    * the direction, locale, etc. for this list of selectors.
    * @param context
    * @param sourceName
@@ -177,8 +171,6 @@ class SkinStyleSheetParserUtils
     List <SkinStyleSheetNode> skinSSNodeList
     )
   {
-
-
     // Get each SkinStyleSheetNode, and for each SkinStyleSheetNode get a
     // styleNodeList. Also, build one iconNodeList and one skinPropertyNodeList.
 
@@ -231,13 +223,20 @@ class SkinStyleSheetParserUtils
           if (direction == LocaleUtils.DIRECTION_RIGHTTOLEFT)
             selectorName = selectorName.concat(StyleUtils.RTL_CSS_SUFFIX);
 
-          // create an IconNode object and add it ot the iconNodeList
-          boolean addStyleNode = _addIconNode(sourceName,
+          // create an IconNode object and add it to the iconNodeList
+          // This method returns hasContentProperty=false if there isn't a content attribute.
+          boolean hasContentProperty = _addIconNode(sourceName,
                                               baseSourceURI,
                                               selectorName,
                                               noTrPropertyList,
+                                              resolvedProperties.getTrRuleRefList(),
                                               iconNodeList);
-          if (addStyleNode)
+          // TODO
+          // Log a warning that you should not have your style
+          // selectors end in 'icon' or 'Icon:alias" because in the skinning framework this denotes
+          // icons. We really should have used an @icon {} or somehow change icons... put it in a 
+          // different file or something.
+          if (!hasContentProperty)
           {
             _addStyleNode(selectorName,
                           noTrPropertyList,
@@ -250,7 +249,7 @@ class SkinStyleSheetParserUtils
         }
         else
         {
-          // create a StyleNode object and add it to the styleNodeList.
+
           _addStyleNode(selectorName,
                         noTrPropertyList,
                         resolvedProperties.getTrRuleRefList(),
@@ -404,16 +403,22 @@ class SkinStyleSheetParserUtils
    * @param sourceName
    * @param baseSourceURI
    * @param selectorName
-   * @param noTrPropertyNodeList
-   * @param iconNodeList
-   * @return boolean true if this "icon" does not contain an image url or text icon as the
-   * property value of 'content:'. That means it is only css styles.
+   * @param trRuleRefList -> This is -tr-rule-ref: selector(). 
+   * Currently not supported for icons. See https://issues.apache.org/jira/browse/TRINIDAD-17
+   * @param noTrPropertyNodeList -> these are properties, like width: 100px.
+   * @param iconNodeList Once the IconNode is created, it is added to the iconNodeList to be
+   * used outside this method.
+   * @return boolean Returns true if an IconNode was created and added to iconNodeList. 
+   * If false, then it means that the properties did not contain 'content', 
+   * so it only had css styles. It could have a -tr-rule-ref that includes a 'content'.
+   * TODO what to do about that???
    */
   private static boolean _addIconNode(
     String             sourceName,
     String             baseSourceURI,
     String             selectorName,
     List<PropertyNode> noTrPropertyNodeList,
+    List<String>       trRuleRefList,
     List<IconNode>     iconNodeList)
   {
 
@@ -438,162 +443,104 @@ class SkinStyleSheetParserUtils
     // depending upon the DIRECTION that is set on the context.
     // The current Icon classes code will not have to change.
 
+    boolean hasContentProperty = false;
+    
+    // Create the propertyNode array now.
+    PropertyNode[] propertyNodeArray = new PropertyNode[noTrPropertyNodeList.size()];
+    int i = 0;
 
-    Integer width = null;
-    String  widthValue = null;
-    Integer height = null;
-    String  heightValue = null;
-    //String  styleClass = null;
-    String  uri = null;
-    String  text = null;
-    boolean isNullIcon = false;
-    boolean createStyleNode = false;
-    // append all the styles that are not content, width or height into
-    // inline style
-    CSSStyle inlineStyle = null;
-
+    // Loop through each property until we find the 'content' property, then change the url to
+    // something we can use in StyleSheetDocument.
     for(PropertyNode propertyNode : noTrPropertyNodeList)
     {
       String propertyName = propertyNode.getName();
       String propertyValue = propertyNode.getValue();
-      if (propertyName.equals("width"))
+      
+      // fix up the url
+      if (propertyName.equals("content") && propertyValue != null)
       {
-        // save original width value
-        // strip off px from the string and return an Integer
-        if (_INTEGER_PATTERN.matcher(propertyValue).matches())
-        {
-          widthValue = propertyValue;
-          width = _convertPxDimensionStringToInteger(widthValue);
-        }
-        else
+        hasContentProperty = true;
+        // is it a text or uri
+        if (_isURLValue(propertyValue))
         {
-          widthValue = null;
-          // use inlineStyle for non-integer width values;
-          if (inlineStyle == null)
+          
+          // get the string that is inside of the 'url()'
+          String uriString = _getURIString(propertyValue);
+          
+          // a leading / indicates context-relative
+          //      (auto-prefix the servlet context)
+          // a leading // indicates server-relative
+          //      (don't auto-prefix the servlet context).
+
+          boolean startsWithTwoSlashes = uriString.startsWith("//");
+          if (!startsWithTwoSlashes && uriString.startsWith("/"))
           {
-            inlineStyle = new CSSStyle();
+            uriString = uriString.substring(1);
           }
-          inlineStyle.setProperty(propertyName, propertyValue);
-        }
-      }
-      else if (propertyName.equals("height"))
-      {
-        // save original height value
-        // strip off px from the string and return an Integer
-        if (_INTEGER_PATTERN.matcher(propertyValue).matches())
-        {
-          heightValue = propertyValue;
-          height = _convertPxDimensionStringToInteger(heightValue);
-        }
-        else
-        {
-          // use inlineStyle for non-integer height values;
-          heightValue = null;
-          if (inlineStyle == null)
+          else
           {
-            inlineStyle = new CSSStyle();
+            // a. if it has two slashes, strip off one.
+            // b. if it starts with http: don't do anything to the uri
+            // c. if it an absolute url, then it should be relative to
+            // the skin file since they wrote the absolute url in the skin file.
+            if (startsWithTwoSlashes)
+              uriString = uriString.substring(1);
+            else if (!(uriString.startsWith("http:")))
+              uriString = CSSUtils.getAbsoluteURIValue(sourceName, baseSourceURI, uriString);
+
           }
-          inlineStyle.setProperty(propertyName, propertyValue);
-        }
-      }
-      else if (propertyName.equals("content"))
-      {
-        // is it a text or uri
-        if (_isURLValue(propertyValue))
-        {
-          uri = _getURIString(propertyValue);
-        }
+          // At this point, URIImageIcons start with '/' or 'http:',
+          // whereas ContextImageIcons uri do not. This is how we will know which type of 
+          // Icon to create in StyleSheetDocument. Wrap back up with the 'url()' string so that
+          // we will know this is not a TextIcon.
+          propertyNodeArray[i] = new PropertyNode(propertyName, _wrapWithURLString(uriString));
+        }      
         else if (propertyValue.startsWith("inhibit"))
         {
-          isNullIcon = true;
+          propertyNodeArray[i] = new PropertyNode(propertyName, propertyValue);
         }
         else
         {
-          text = trimQuotes(propertyValue);
+          String text = trimQuotes(propertyValue);
+          propertyNodeArray[i] = new PropertyNode(propertyName, text);
         }
+        
 
-      }
+      } // end if 'content'
       else
-      {
-        // create an inlineStyle with all the extraneous style properties
-        if (inlineStyle == null)
-          inlineStyle = new CSSStyle();
-        inlineStyle.setProperty(propertyName, propertyValue);
-      }
-
+        propertyNodeArray[i] = new PropertyNode(propertyName, propertyValue);
+      i++;
     }
-    // now I need to create the icon.
-    // do not create an icon if isNullIcon is true.
-    Icon icon = null;
-
-    if (!isNullIcon)
-    {
-      if (text != null)
-      {
-        // don't allow styleClass from the css parsing file. We can handle
-        // this when we have style includes
-        // put back the width/height properties if there were some
-        if ((heightValue != null || widthValue != null) && inlineStyle == null)
-          inlineStyle = new CSSStyle();
-        if (heightValue != null)
-          inlineStyle.setProperty("height", heightValue);
-        if (widthValue != null)
-          inlineStyle.setProperty("width", widthValue);
-        icon = new TextIcon(text, text, null, inlineStyle);
-      }
-      else if (uri != null)
-      {
-
-
-        // a leading / indicates context-relative
-        //      (auto-prefix the servlet context)
-        // a leading // indicates server-relative
-        //      (don't auto-prefix the servlet context).
-
-        boolean startsWithTwoSlashes = uri.startsWith("//");
-        if (!startsWithTwoSlashes && uri.startsWith("/"))
-        {
-
-          uri = uri.substring(1);
-
-          icon =
-            new ContextImageIcon(uri, uri, width, height, null, inlineStyle);
-        }
-        else
-        {
-          // a. if it has two slashes, strip off one.
-          // b. if it starts with http: don't do anything to the uri
-          // c. if it an absolute url, then it should be relative to
-          // the skin file since they wrote the absolute url in the skin file.
-          if (startsWithTwoSlashes)
-            uri = uri.substring(1);
-          else if (!(uri.startsWith("http:")))
-            uri = CSSUtils.getAbsoluteURIValue(sourceName, baseSourceURI, uri);
-          icon =
-            new URIImageIcon(uri, uri, width, height, null, inlineStyle);
-        }
-      }
-      else
-      {
-        /// neither text or image icon.
-        if (inlineStyle != null)
-        {
-          // create a styleNode, too with the inlineStyles.
-          createStyleNode = true;
-        }
-      }
-    }
-    else
+ 
+    // TODO - Add -tr-rule-ref capability for icons.
+    // I purposely do not include the -tr-rule-ref at this time.
+    // See TRINIDAD-17 for details why. There is a hitch. Even though we treat selectors 
+    // with -icon as Icon objects, if there is no content attribute, we also treat them as styles.
+    // Well, if they use -tr-rule-ref to import 'content', then we will think it is a style, 
+    // and we'll have extra styles. Yet we don't want to resolve selectors just to see if it is
+    // really an icon. But we don't want to hurt the person that didn't abide by the -icon rule
+    // because this wasn't an enforced rule.
+    //
+    if (selectorName != null)
     {
-      icon = NullIcon.sharedInstance();
+      // Create a styleNode that we will add to the IconNode.
+      StyleNode styleNode =
+        new StyleNode(null,
+                      selectorName,
+                      propertyNodeArray,
+                      null, // TODO includeStyleNodes.toArray(new IncludeStyleNode[0]), TRINIDAD-17
+                      null, //TODO jmw includePropertyNodes
+                      null // TODO jmw inhibitedProperties
+                      );
+      
+      // Create a new IconNode.
+      // don't bother creating the Icon object now (the second property to new IconNode()); 
+      // we create the Icon object when we resolve IconNodes in StyleSheetDocument
+      // See StyleSheetDocument#getIcons(StyleContext context).      
+      iconNodeList.add(new IconNode(selectorName, null, styleNode));
     }
 
-    // if icon is not null, create an IconNode
-
-    if (icon != null)
-      iconNodeList.add(new IconNode(selectorName, icon));
-
-    return createStyleNode;
+    return hasContentProperty;
 
   }
 
@@ -602,7 +549,12 @@ class SkinStyleSheetParserUtils
    * @param selectorName
    * @param propertyNodeList
    * @param trRuleRefList
-   * @param styleNodeList
+   * @param includePropertyNodes
+   * @param inhibitedProperties
+   * @param trTextAntialias
+   * @param styleNodeList Once the StyleNode is created, it is added to the iconNodeList to be
+   * used outside this method.
+   *
    */
   private static void _addStyleNode(
     String                    selectorName,
@@ -614,6 +566,22 @@ class SkinStyleSheetParserUtils
     List<StyleNode>           styleNodeList)
   {
 
+    StyleNode styleNode = _createStyleNode(selectorName, propertyNodeList, trRuleRefList, 
+                                           includePropertyNodes, inhibitedProperties, 
+                                           trTextAntialias);
+
+    styleNodeList.add(styleNode);
+
+  }
+
+  private static StyleNode _createStyleNode(
+    String                    selectorName,
+    List<PropertyNode>        propertyNodeList,
+    List<String>              trRuleRefList,
+    List<IncludePropertyNode> includePropertyNodes,
+    Set<String>               inhibitedProperties,
+    boolean                   trTextAntialias)
+  {
     // these are the styles.
     // At this point I have a selector name and the properties.
     // create a StyleNode based on this information.
@@ -666,11 +634,10 @@ class SkinStyleSheetParserUtils
                     selector,
                     propertyArray,
                     includeStyleNodes.toArray(new IncludeStyleNode[0]),
-                    includePropertyNodes.isEmpty() ? null : includePropertyNodes.toArray(new IncludePropertyNode[0]),
+                    includePropertyNodes.isEmpty() ? 
+                    null : includePropertyNodes.toArray(new IncludePropertyNode[0]),
                     inhibitedProperties);
-
-    styleNodeList.add(styleNode);
-
+    return styleNode;
   }
 
   /**
@@ -766,7 +733,7 @@ class SkinStyleSheetParserUtils
 
     if (value != null)
     {
-      // parse string and create styleNode for each selector value
+      // parse string and create IncludeStyleNode for each selector value.
       // the string will be of this form:
       // selector(".AFBaseFont:alias") selector(".MyDarkBackground")
       // or a single selector:
@@ -775,24 +742,11 @@ class SkinStyleSheetParserUtils
 
       List<String> selectors = new ArrayList<String>();
 
-      String[] test = _SELECTOR_PATTERN.split(value);
-      for (int i=0; i < test.length; i++)
-      {
-        int endIndex = test[i].indexOf(")");
-        if (endIndex > -1)
-        {
-          String selectorValue = test[i].substring(0, endIndex);
-          selectorValue = trimQuotes(selectorValue);
-          selectors.add(selectorValue);
-        }
-      }
+      _parseValueIntoSelectors(value, selectors);
 
       // now take the selector List and convert it to IncludeStyleNodes.
-      int size = selectors.size();
-
-      for (int i=0; i < size; i++)
+      for (String includeStyle : selectors)
       {
-        String includeStyle = selectors.get(i);
         // if it has :alias at the end it is a named style
         if (includeStyle.endsWith(":alias"))
         {
@@ -813,6 +767,30 @@ class SkinStyleSheetParserUtils
     }
   }
 
+  /**
+   * Parse the value into a List<String> of selector names.
+   * @param value - String of the form: selector(".AFBaseFont:alias") selector(".MyDarkBackground")
+   * or a single selector: selector(".AFBaseFont:alias")
+   * @param selectors a List<String> of selector names. This will be filled in during this method
+   * call.
+   */
+  private static void _parseValueIntoSelectors(
+    String value, 
+    List<String> selectors)
+  {
+    String[] test = _SELECTOR_PATTERN.split(value);
+    for (int i=0; i < test.length; i++)
+    {
+      int endIndex = test[i].indexOf(")");
+      if (endIndex > -1)
+      {
+        String selectorValue = test[i].substring(0, endIndex);
+        selectorValue = trimQuotes(selectorValue);
+        selectors.add(selectorValue);
+      }
+    }
+  }
+
   private static StyleSheetDocument _createStyleSheetDocument(
     ParseContext context,
     List<StyleSheetNode> ssNodeList)
@@ -914,11 +892,23 @@ class SkinStyleSheetParserUtils
 
     return trimQuotes(uri);
   }
-
+  
+  // wraps the uri in 'url( )' and returns the new String.
+  private static String _wrapWithURLString(String uri)
+  {
+    StringBuilder builder = new StringBuilder(5 + uri.length());
+    builder.append("url(");
+    builder.append(uri);
+    builder.append(")");
+    return builder.toString();
+    
+  }
 
   // returns true if the selectorName indicates that it is an icon.
   private static boolean _isIcon(String selectorName)
   {
+    if (selectorName == null)
+      return false;
     // =-=jmw There is no good way to tell if this is an icon.
     // for now, I look at the selector name.
     // we do have some styles that have -icon- in the name, but it's
@@ -932,24 +922,7 @@ class SkinStyleSheetParserUtils
             (selectorName.indexOf("-icon:") > -1) ||
             selectorName.indexOf("Icon:alias") > -1);
   }
-
-  /**
-   * Given a String that denotes a width or height css style
-   * property, return an Integer. This will strip off 'px' from
-   * the string if there is one.
-   * e.g., if propertyValue is '7px', the Integer 7 will be returned.
-   * @param propertyValue - this is a string that indicates width
-   * or height.
-   * @return Integer
-   */
-  private static Integer _convertPxDimensionStringToInteger(
-    String propertyValue)
-  {
-    int pxPosition = propertyValue.indexOf("px");
-    if (pxPosition > -1)
-      propertyValue = propertyValue.substring(0, pxPosition);
-    return Integer.valueOf(propertyValue);
-  }
+  
   private static class ResolvedSkinProperties
   {
 
@@ -1018,7 +991,6 @@ class SkinStyleSheetParserUtils
   private static final String _PROPERTY_INHIBIT = "inhibit";
   private static final String _INCLUDE_PROPERTY = "-tr-property-ref";
   private static final String _PROPERTY_TEXT_ANTIALIAS = "text-antialias";
-  private static final Pattern _INTEGER_PATTERN = Pattern.compile("\\d+(px)?");
 
   private static final Pattern _SPACE_PATTERN = Pattern.compile("\\s");
   private static final Pattern _SELECTOR_PATTERN = Pattern.compile("selector\\(");

Modified: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/cache/FileSystemStyleCache.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/cache/FileSystemStyleCache.java?rev=966817&r1=966816&r2=966817&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/cache/FileSystemStyleCache.java (original)
+++ myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/cache/FileSystemStyleCache.java Thu Jul 22 19:29:20 2010
@@ -630,31 +630,24 @@ public class FileSystemStyleCache implem
 
   /**
    * Returns a Map of icon names to Icons for the specified
-   * styleSheetNodes that have been filtered from the StyleContext and StyleSheetDocument.
+   * styleSheetNodes that have been filtered from the StyleContext and StyleSheetDocument
+   * and everything merged together.
    */
   private ConcurrentMap<String, Icon> _getStyleContextResolvedIcons(
     StyleContext       context,
     StyleSheetDocument document
     )
   {
-    Iterator<StyleSheetNode> styleSheetNodes = document.getStyleSheets(context);
 
-    ConcurrentMap<String, Icon> icons = new ConcurrentHashMap<String, Icon>();
-    while (styleSheetNodes.hasNext())
+    Iterator<IconNode> iconNodeIterator = document.getIcons(context);
+    ConcurrentMap<String, Icon> iconMap = new ConcurrentHashMap<String, Icon>();
+    while (iconNodeIterator.hasNext())
     {
-      StyleSheetNode styleSheetNode = styleSheetNodes.next();
-      Collection<IconNode> iconNodes = styleSheetNode.getIcons();
-
-      if (iconNodes != null)
-      {
-        for (IconNode iconNode : iconNodes)
-        {
-          icons.put(iconNode.getIconName(), iconNode.getIcon());
-        }
-      }
+      IconNode iconNode = iconNodeIterator.next();
+      iconMap.put(iconNode.getIconName(), iconNode.getIcon());
     }
 
-    return icons;
+    return iconMap;
   }
 
   /**

Modified: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/IconNode.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/IconNode.java?rev=966817&r1=966816&r2=966817&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/IconNode.java (original)
+++ myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/IconNode.java Thu Jul 22 19:29:20 2010
@@ -30,13 +30,13 @@ public class IconNode
 {
   /**
    * Creates a IconNode
-   * @param namespace The namespace of the icon
    * @param name The name of the icon
    * @param icon The Icon instance
    */
   public IconNode(
-    String name,
-    Icon   icon
+    String            name,
+    Icon              icon,
+    StyleNode         styleNode 
     )
   {
     if (name == null)
@@ -47,8 +47,25 @@ public class IconNode
 
     _name = name;
     _icon = icon;
+    _styleNode = styleNode;
+    
   }
 
+  /**
+   * Creates a IconNode
+   * @param name The name of the icon
+   * @param icon The Icon instance
+   */
+  public IconNode(
+    String            name,
+    Icon              icon    
+    )
+  {
+    this (name, icon, null);
+    
+  }
+
+
 
   /**
    * Returns the name of the icon that is defined
@@ -66,9 +83,16 @@ public class IconNode
   {
     return _icon;
   }
+  
+  public StyleNode getStyleNode()
+  {
+    return _styleNode;
+  }
 
   private String      _name;
   private Icon        _icon;
+  private StyleNode   _styleNode;
   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
     IconNode.class);
+
 }

Modified: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/StyleSheetDocument.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/StyleSheetDocument.java?rev=966817&r1=966816&r2=966817&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/StyleSheetDocument.java (original)
+++ myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/style/xml/parse/StyleSheetDocument.java Thu Jul 22 19:29:20 2010
@@ -22,6 +22,7 @@ import java.awt.Color;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -30,16 +31,23 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.Stack;
 
+import java.util.regex.Pattern;
+
 import org.apache.myfaces.trinidad.context.AccessibilityProfile;
 import org.apache.myfaces.trinidad.context.LocaleContext;
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
+import org.apache.myfaces.trinidad.skin.Icon;
 import org.apache.myfaces.trinidad.util.IntegerUtils;
 
 import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
+import org.apache.myfaces.trinidadinternal.skin.icon.ContextImageIcon;
+import org.apache.myfaces.trinidadinternal.skin.icon.NullIcon;
+import org.apache.myfaces.trinidadinternal.skin.icon.TextIcon;
+import org.apache.myfaces.trinidadinternal.skin.icon.URIImageIcon;
+import org.apache.myfaces.trinidadinternal.style.CSSStyle;
 import org.apache.myfaces.trinidadinternal.style.PropertyParseException;
 import org.apache.myfaces.trinidadinternal.style.StyleContext;
 import org.apache.myfaces.trinidadinternal.style.util.CSSUtils;
@@ -50,7 +58,7 @@ import org.apache.myfaces.trinidadintern
 
 /**
  * Parsed representation of a Trinidad style sheet document.
- * 
+ *
  * The StyleSheetDocument provides access to both style as well as icons
  * information, but not to skin properties.
  *
@@ -181,6 +189,238 @@ public class StyleSheetDocument
     StyleSheetList styleSheets = _getStyleSheets(context);
     return styleSheets.styleSheets().iterator();
   }
+  
+  /**
+   * Returns an Iterator of StyleNode objects for the specified context.
+   */
+   @SuppressWarnings("unchecked")
+  public Iterator<IconNode> getIcons(StyleContext context)
+  {
+    // document.getStyleSheets sorts by lowest to highest precedence
+    StyleSheetList styleSheets = _getStyleSheets(context);
+    if (styleSheets.isEmpty())
+    {
+      List<IconNode> emptyList = Collections.emptyList();
+      return emptyList.iterator();
+    }
+    
+    // We also need to provide a Map for storing selector-based
+    // styles and another for storing name-based styles, used by
+    // _resolveStyle() to store results
+    HashMap<String, StyleNode> resolvedStyles = 
+      new HashMap<String, StyleNode>();
+    HashMap<String, StyleNode> resolvedNamedStyles = 
+      new HashMap<String, StyleNode>();
+
+    // Keep track of all selectors and names that we've already
+    // found, so that we don't bother re-resolving them
+    // These differ from the above Maps in that the Maps are
+    // mutated by _resolveStyle - so an included selector, for
+    // instance, might already be present in _resolveStyle but
+    // not yet have been encountered by this top loop.  We
+    // could eliminate the need for this if we were willing
+    // to give up the ordering of the list
+    Set<String> foundNames = new HashSet<String>();
+    List<IconNode> iconNodes = new ArrayList<IconNode>();
+
+    // We want to cache IDs in this use of StyleSheetList. We'll use the cache
+    // when we resolve styles.
+    styleSheets.cacheIconIds();
+    styleSheets.cacheStyleIds();
+    
+    for (StyleSheetNode styleSheet : styleSheets.styleSheets())
+    {
+      Iterable<IconNode> iconNodeList = styleSheet.getIcons();
+      // iterate through each one and add to the list.
+      for (IconNode iconNodeFromStyleSheet : iconNodeList)
+      {
+            String id;
+            boolean isFound;
+
+
+            id = iconNodeFromStyleSheet.getIconName();
+            isFound = foundNames.contains(id);
+            
+
+            // If we've already seen that node/selector, no need to look
+            // for it again
+            if (!isFound)
+            {
+              StyleNode resolvedNode = _resolveStyleNode(context, true,
+                                                         styleSheets,
+                                                         resolvedStyles,
+                                                         resolvedNamedStyles,
+                                                         null,
+                                                         null,
+                                                         id,
+                                                         false);
+
+              // Create the Icon
+              
+              if (resolvedNode != null)        
+              {
+ 
+                Icon icon = _createIconFromNode(resolvedNode);
+ 
+                if (icon != null)
+                {
+                  iconNodes.add(new IconNode(id, icon, resolvedNode));
+
+                  foundNames.add(id);
+                }
+
+              }
+       
+
+        }
+      }
+    }
+
+    return iconNodes.iterator();
+  
+  }
+
+  /**
+   * From each property in the resolvedNode, create an Icon object.
+   * @param resolvedNode StyleNode that contains all the resolved css properties
+   * @return an Icon object that was created from the information in the resolvedNode.
+   */
+  private Icon _createIconFromNode(StyleNode resolvedNode)
+  {    
+    Integer width = null;
+    String  widthValue = null;
+    Integer height = null;
+    String  heightValue = null;
+    String  uri = null;
+    String  text = null;
+    boolean isNullIcon = false;
+    CSSStyle  inlineStyle = null;
+
+    // loop through each property in the StyleNode.
+    // If 'content', then get the url and the type of icon: 
+    // Context, Image, Null, or Text
+    // If 'width', then get the width
+    // If 'height', then get the height
+    // Build up all the rest of the properties as a CSSStyle object.
+    // Then create an Icon object. 
+    Collection<PropertyNode> properties = resolvedNode.getProperties();
+    for (PropertyNode propertyNode : properties)
+    {
+      String propertyName = propertyNode.getName();
+      String propertyValue = propertyNode.getValue();
+     
+      
+      if (propertyName != null)
+      {
+        if (propertyName.equals("width"))
+        {
+          // save the original propertyValue for the 'width' property in widthValue.
+          // Then strip off px from the string and return an Integer and store in width.
+          if (_INTEGER_PATTERN.matcher(propertyValue).matches())
+          {
+            widthValue = propertyValue;
+            width = _convertPxDimensionStringToInteger(widthValue);
+          }
+          else
+          {
+            widthValue = null;
+            // use inlineStyle for non-integer width values;
+            if (inlineStyle == null)
+              inlineStyle = new CSSStyle();
+            inlineStyle.setProperty(propertyName, propertyValue);
+          }
+        }
+        else if (propertyName.equals("height"))
+        {
+          // save original height value
+          // strip off px from the string and return an Integer
+          if (_INTEGER_PATTERN.matcher(propertyValue).matches())
+          {
+            heightValue = propertyValue;
+            height = _convertPxDimensionStringToInteger(heightValue);
+          }
+          else
+          {
+            // use inlineStyle for non-integer height values;
+            heightValue = null;
+            if (inlineStyle == null)
+              inlineStyle = new CSSStyle();
+            inlineStyle.setProperty(propertyName, propertyValue);
+          }
+        }
+        else if (propertyName.equals("content"))
+        {
+          // is it a text or uri
+          if (_isURLValue(propertyValue))
+          {
+            uri = _getURIString(propertyValue);
+          }
+          else if (propertyValue.startsWith("inhibit"))
+          {
+            isNullIcon = true;
+          }
+          else
+          {
+            text = _trimQuotes(propertyValue);
+          }
+        }
+        else
+        {
+          // create an inlineStyle with all the extraneous style properties
+          if (inlineStyle == null)
+            inlineStyle = new CSSStyle();
+          inlineStyle.setProperty(propertyName, propertyValue);
+        }
+      }
+    }
+     
+
+    // now I need to create the icon.
+    // do not create an icon if isNullIcon is true.
+    Icon icon = null;
+
+    if (!isNullIcon)
+    {
+     if (text != null)
+     {
+       // don't allow styleClass from the css parsing file. We can handle
+       // this when we have style includes
+       // put back the width/height properties if there were some
+       if ((heightValue != null || widthValue != null) && inlineStyle == null)
+         inlineStyle = new CSSStyle();
+       if (heightValue != null)
+         inlineStyle.setProperty("height", heightValue);
+       if (widthValue != null)
+         inlineStyle.setProperty("width", widthValue);
+       icon = new TextIcon(text, text, null, inlineStyle);
+     }
+     else if (uri != null)
+     {
+       // A URIImageIcon url starts with '/' or 'http:',
+       // whereas a ContextImageIcons uri does not. 
+       boolean startsWithASlash = uri.startsWith("/");
+       if (!startsWithASlash)
+       {
+         icon =
+           new ContextImageIcon(uri, uri, width, height, null, inlineStyle);
+       }
+       else
+       {
+         icon =
+           new URIImageIcon(uri, uri, width, height, null, inlineStyle);  
+
+
+       }
+      }
+    }
+    else
+    {
+      icon = NullIcon.sharedInstance();
+    }
+
+    
+    return icon;
+  }
 
   /**
    * Returns an Iterator of StyleNode objects for the specified context.
@@ -222,8 +462,9 @@ public class StyleSheetDocument
 
     // Now, loop through all StyleNodes in all StyleSheetNodes
     
-    // We want to cache IDs in this use of StyleSheetList
-    styleSheets.cacheIds();
+    // We want to cache IDs in this use of StyleSheetList. We'll use the cache
+    // when we resolve styles.
+    styleSheets.cacheStyleIds();
 
     for (StyleSheetNode styleSheet : styleSheets.styleSheets())
     {
@@ -253,7 +494,7 @@ public class StyleSheetDocument
         // for it again
         if (!isFound)
         {
-          StyleNode resolvedNode = _resolveStyle(context,
+          StyleNode resolvedNode = _resolveStyleNode(context, false,
                                                  styleSheets,
                                                  resolvedStyles,
                                                  resolvedNamedStyles,
@@ -357,7 +598,7 @@ public class StyleSheetDocument
     if (styleSheets.isEmpty())
       return null;
 
-    return _resolveStyle(context,
+    return _resolveStyleNode(context, false,
                          styleSheets,
                          new HashMap<String, StyleNode>(19),  // Resolved styles
                          new HashMap<String, StyleNode>(19),  // Resolved named styles
@@ -370,6 +611,7 @@ public class StyleSheetDocument
   /**
    * Resolves the (named or selector-based) style with the specified id.
    * @param context The StyleContext
+   * @param forIconNode if you are resolving the styles for an IconNode, this is true.
    * @param styleSheets The StyleSheetNodes to use for resolving the style,
    *          sorted from lowest to highest precedence.
    * @param resolvedStyles The set of already resolved styles, hashed by
@@ -387,8 +629,9 @@ public class StyleSheetDocument
    *           resolved style is stored in the appropriate resolved style
    *           Map.
    */
-  private StyleNode _resolveStyle(
+  private StyleNode _resolveStyleNode(
     StyleContext           context,
+    boolean                forIconNode,
     StyleSheetList         styleSheets,
     Map<String, StyleNode> resolvedStyles,
     Map<String, StyleNode> resolvedNamedStyles,
@@ -466,7 +709,92 @@ public class StyleSheetDocument
       includesStack.push(id);
     }
 
+    // styleSheets.styleNodes(id, isNamed) returns a List of StyleNodes that match the StyleContext
+    // and have the same selector name. For example, if the css files contains 
+    // .someStyle {color: red} .someStyle {font-size: 11px}
+    // you will get two StyleNodes, and the properties will get merged together.
     List<StyleNode> nodeList = styleSheets.styleNodes(id, isNamed);
+    // get the StyleNodes from each iconNodeList and add it to the StyleNode list
+    if (forIconNode)
+    {
+      List<IconNode> iconNodeList = styleSheets.iconNodes(id);
+
+      // protect against null - 
+      // iconNodeList could be null if in SkinStyleSheetParserUtils 
+      // we thought a selector was an icon because it ended in -icon and we created an IconNode.
+      // But we also saw that it had no 'content', so we created a StyleNode.
+      // Really this is a mis-named style selector (should have ended with -icon-style),
+      // and this resolving work is wasted cycles.
+      if (iconNodeList != null)
+      {
+        for (IconNode iconNode: iconNodeList)
+        {
+          StyleNode sNode = iconNode.getStyleNode();
+
+          if (sNode != null)
+          {
+            if (nodeList == null)
+              nodeList = new ArrayList<StyleNode>(iconNodeList.size());
+            nodeList.add(sNode);
+          }
+        }
+      }        
+    }
+    _resolveStyleWork(context, forIconNode, styleSheets, resolvedStyles, resolvedNamedStyles,  
+                      includesStack, namedIncludesStack, entry, nodeList);
+    
+    // Pop the include stack
+    if (isNamed)
+    {
+      namedIncludesStack.pop();
+    }
+    else
+    {
+      includesStack.pop();
+    }
+
+    StyleNode resolvedNode = entry.toStyleNode();
+
+    // If we got a node, add it in to our list
+    if (resolvedNode != null)
+    {
+      // cache already resolved styles so we don't
+      // resolve them again. This saves (a lot of) time.
+      // TODO: AdamWiner: entry.toStyleNode() will return
+      // null if it's an empty style.  But that means
+      // that this cache doesn't get used, so we end
+      // up hammering on these.  This doesn't appear
+      // to be a performance issue at this time.
+      String namedStyle = resolvedNode.getName();
+      if (namedStyle != null)
+      {
+        resolvedNamedStyles.put(namedStyle, resolvedNode);
+      }
+      else 
+      {
+        String selectorStyle = resolvedNode.getSelector();
+        if (selectorStyle != null)
+        {
+          resolvedStyles.put(selectorStyle, resolvedNode);
+        }
+      }
+    }
+    
+    // Convert the StyleEntry to a StyleNode and return it
+    return resolvedNode;
+  }
+
+  private void _resolveStyleWork(
+    StyleContext context,
+    boolean      forIconNode,
+    StyleSheetList styleSheets,
+    Map<String, StyleNode> resolvedStyles,
+    Map<String, StyleNode> resolvedNamedStyles,
+    Stack<String> includesStack,
+    Stack<String> namedIncludesStack,
+    StyleEntry entry,
+    List<StyleNode> nodeList)
+  {
     if (nodeList != null)
     {
       for (StyleNode node : nodeList)
@@ -493,7 +821,7 @@ public class StyleSheetDocument
         // 0. Reset properties?
         if (node.__getResetProperties() || node.isInhibitingAll())
           entry.resetProperties();
-  
+    
         // 1. Resolve included styles
         Iterable<IncludeStyleNode> includedStyles = node.getIncludedStyles();
         for (IncludeStyleNode includeStyle : includedStyles)
@@ -510,8 +838,8 @@ public class StyleSheetDocument
           {
             includeID = includeStyle.getSelector();
           }
-
-          StyleNode resolvedNode = _resolveStyle(context,
+        
+          StyleNode resolvedNode = _resolveStyleNode(context, forIconNode,
                                                  styleSheets,
                                                  resolvedStyles,
                                                  resolvedNamedStyles,
@@ -523,15 +851,15 @@ public class StyleSheetDocument
           if (resolvedNode != null)
             _addIncludedProperties(entry, resolvedNode);
         }
-  
-  
+    
+    
         // 2. Resolve included properties
         Iterable<IncludePropertyNode> includedProperties = node.getIncludedProperties();
         for (IncludePropertyNode includeProperty : includedProperties)
         {
           String includeID = null;
           boolean includeIsNamed = false;
-  
+    
           if (includeProperty.getName() != null)
           {
             includeID = includeProperty.getName();
@@ -541,8 +869,8 @@ public class StyleSheetDocument
           {
             includeID = includeProperty.getSelector();
           }
-  
-          StyleNode resolvedNode = _resolveStyle(context,
+    
+          StyleNode resolvedNode = _resolveStyleNode(context, forIconNode,
                                                  styleSheets,
                                                  resolvedStyles,
                                                  resolvedNamedStyles,
@@ -550,7 +878,7 @@ public class StyleSheetDocument
                                                  namedIncludesStack,
                                                  includeID,
                                                  includeIsNamed);
-  
+    
           if (resolvedNode != null)
           {
             _addIncludedProperty(entry,
@@ -559,7 +887,7 @@ public class StyleSheetDocument
                                  includeProperty.getLocalPropertyName());
           }
         }
-  
+    
         // 3. Check inhibited properties
         Iterable<String> inhibitedProperties = node.getInhibitedProperties();
         for (String inhibitedPropertyName : inhibitedProperties)
@@ -567,59 +895,21 @@ public class StyleSheetDocument
           entry.removeProperty(inhibitedPropertyName);
         }
         
-  
+    
         // 4. Add non-included properties
         Iterable<PropertyNode> properties = node.getProperties();
         for (PropertyNode propertyNode : properties)
         {
           entry.addProperty(propertyNode);
+          
         }
-      }
-    }
-    
-    // Pop the include stack
-    if (isNamed)
-    {
-      namedIncludesStack.pop();
-    }
-    else
-    {
-      includesStack.pop();
-    }
-
-    StyleNode resolvedNode = entry.toStyleNode();
+        
 
-    // If we got a node, add it in to our list
-    if (resolvedNode != null)
-    {
-      // cache already resolved styles so we don't
-      // resolve them again. This saves (a lot of) time.
-      // TODO: AdamWiner: entry.toStyleNode() will return
-      // null if it's an empty style.  But that means
-      // that this cache doesn't get used, so we end
-      // up hammering on these.  This doesn't appear
-      // to be a performance issue at this time.
-      String namedStyle = resolvedNode.getName();
-      if (namedStyle != null)
-      {
-        resolvedNamedStyles.put(namedStyle, resolvedNode);
-      }
-      else 
-      {
-        String selectorStyle = resolvedNode.getSelector();
-        if (selectorStyle != null)
-        {
-          resolvedStyles.put(selectorStyle, resolvedNode);
-        }
       }
     }
-    
-    // Convert the StyleEntry to a StyleNode and return it
-    return resolvedNode;
   }
+  
 
-  // Adds all of the properties from the StyleNode into the StyleEntry
-  // as included properties.
   private void _addIncludedProperties(
     StyleEntry entry,
     StyleNode  node
@@ -724,7 +1014,63 @@ public class StyleSheetDocument
     return new PropertyNode(_FONT_SIZE_NAME, newValue);
   }
 
+  /**
+   * Given a String that denotes a width or height css style
+   * property, return an Integer. This will strip off 'px' from
+   * the string if there is one.
+   * e.g., if propertyValue is '7px', the Integer 7 will be returned.
+   * @param propertyValue - this is a string that indicates width
+   * or height.
+   * @return Integer
+   */
+  private static Integer _convertPxDimensionStringToInteger(
+    String propertyValue)
+  {
+    int pxPosition = propertyValue.indexOf("px");
+    if (pxPosition > -1)
+      propertyValue = propertyValue.substring(0, pxPosition);
+    return Integer.valueOf(propertyValue);
+  }
+  
+  // Tests whether the specified property value is an "url" property.
+  private static boolean _isURLValue(String propertyValue)
+  {
+    // URL property values start with "url("
+    return propertyValue.startsWith("url(");
+  }
+  
+  /**
+   * Trim the leading/ending quotes, if any.
+   */
+  private static String _trimQuotes(String in)
+  {
+    int length = in.length();
+    if (length <= 1)
+      return in;
+    // strip off the starting/ending quotes if there are any
+    char firstChar = in.charAt(0);
+    int firstCharIndex = 0;
+    if ((firstChar == '\'') || (firstChar == '"'))
+      firstCharIndex = 1;
+
+    char lastChar = in.charAt(length-1);
+    if ((lastChar == '\'') || (lastChar == '"'))
+      length--;
+
+    return in.substring(firstCharIndex, length);
+  }
+  
+  // Returns the uri portion of the url property value
+  private static String _getURIString(String propertyValue)
+  {
+    assert(_isURLValue(propertyValue));
+
+    int uriEnd = propertyValue.indexOf(')');
+    String uri = propertyValue.substring(4, uriEnd);
 
+    return _trimQuotes(uri);
+  }
+  
   // Tests whether the value is present in the (possibly null) stack.
   private static boolean _stackContains(Stack<?> stack, Object value)
   {
@@ -758,14 +1104,14 @@ public class StyleSheetDocument
      * we're going to be retrieving a lot (generally, all)
      * of the nodes at some point.
      */
-    public void cacheIds()
+    public void cacheStyleIds()
     {
       // Typically, there are a lot more selector nodes than
       // name nodes.  These numbers come from some statistics gathered
       // on Trinidad and other libraries
-      _nameNodes = new HashMap<String, List<StyleNode>>(256);
-      _selectorNodes = new HashMap<String, List<StyleNode>>(1024);
-      
+      _nameNodes = new HashMap<String, List<StyleNode>>(512);
+      _selectorNodes = new HashMap<String, List<StyleNode>>(4096);
+            
       for (int i = 0; i < _styleSheets.length; i++)
       {
         StyleSheetNode styleSheet = _styleSheets[i];
@@ -773,17 +1119,53 @@ public class StyleSheetDocument
 
         for (StyleNode node : styleNodeList)
         {
+          // Add to the map where the key is the 'name' or 'selector name', and the value
+          // is a List of StyleNodes that have that same 'name' or 'selector name'. This 
+          // is in case someone created the same selector more than once, or overrode a selector.
           if (node.getName() != null)
-            _addToMap(_nameNodes, node, node.getName());
+          {
+            _addToStyleMap(_nameNodes, node, node.getName());
+          }
           else
-            _addToMap(_selectorNodes, node, node.getSelector());
+          {
+            _addToStyleMap(_selectorNodes, node, node.getSelector());
+          }
+         
+        }
+      }
+
+    }
+    
+
+    /**
+     * Forcibly caches IDs.  Caching IDs is only useful if
+     * we're going to be retrieving a lot (generally, all)
+     * of the nodes at some point.
+     */
+    public void cacheIconIds()
+    {
+      _iconNodes = new HashMap<String, List<IconNode>>(1024);
+      
+      
+      for (int i = 0; i < _styleSheets.length; i++)
+      {
+        StyleSheetNode styleSheet = _styleSheets[i];
+        Iterable<IconNode> iconNodeList = styleSheet.getIcons();
+        for (IconNode node : iconNodeList)
+        {
+          // Add to the map where the key is the 'icon name', and the value
+          // is a List of StyleNodes that have that same 'icon name'. This 
+          // is in case someone created the same selector more than once, or overrode a selector.
+          if (node.getIconName() != null)
+            _addToIconMap(_iconNodes, node, node.getIconName()); 
         }
       }
     }
     
     /**
      * Return a List of StyleNodes based on the "id" (either
-     * a selector or name)
+     * a selector or name).
+     * Call cacheStyleIds first if you want everything to be cached.
      * @param id the selector or name
      * @param isNamed if true, interpret "id" as a name, otherwise
      *   as a selector
@@ -793,13 +1175,15 @@ public class StyleSheetDocument
     {
       if (_styleSheets == null)
         return Collections.emptyList();
+      // _nameNodes and _selectorNodes are initialized in cacheStyleIds
       Map<String, List<StyleNode>> m = isNamed ? _nameNodes : _selectorNodes;
       // Cached version - go to the Map
       if (m != null)
         return m.get(id);
       
       // Uncached version - iterate through everything and build up
-      // the List
+      // the List, but do not cache it.
+      // This gets called if you do not call cacheStyleIds() first.
       List<StyleNode> l = new ArrayList<StyleNode>();
       for (int i = 0; i < _styleSheets.length; i++)
       {
@@ -823,7 +1207,41 @@ public class StyleSheetDocument
       
       return l;
     }
-    
+   
+    /**
+     * Return a List of IconNodes based on the "id"
+     * @param id the icon selector name
+     * @return the list of IconNodes (potentially null)
+     */
+    public List<IconNode> iconNodes(String id)
+    {
+      if (_styleSheets == null)
+        return Collections.emptyList();
+      Map<String, List<IconNode>> m = _iconNodes;
+      // Cached version - go to the Map
+      if (m != null)
+        return m.get(id);
+      
+      // Uncached version - iterate through everything and build up
+      // the List, but do not cache it.
+      // This gets called if you do not call cacheIconIds() first.
+      List<IconNode> l = new ArrayList<IconNode>();
+      for (int i = 0; i < _styleSheets.length; i++)
+      {
+        StyleSheetNode styleSheet = _styleSheets[i];
+        Iterable<IconNode> iconNodeList = styleSheet.getIcons();
+
+        for (IconNode node : iconNodeList)
+        {
+
+            if (id.equals(node.getIconName()))
+              l.add(node);
+          
+        }
+      }
+      
+      return l;
+    } 
     /**
      * @return an unmodifiable list of all StyleSheetNodes
      */
@@ -839,7 +1257,7 @@ public class StyleSheetDocument
       }
     }
 
-    static private void _addToMap(
+    static private void _addToStyleMap(
       Map<String, List<StyleNode>> m, 
       StyleNode                    node,
       String                       id)
@@ -847,19 +1265,42 @@ public class StyleSheetDocument
       List<StyleNode> l = m.get(id);
       if (l == null)
       {
-        // Most properties, in fact, have only one entry
-        // The most I've seen is 8.  This could probably
-        // be reduced to 2
-        l = new ArrayList<StyleNode>(4);
+        // you may see this id (aka selector) multiple times in the resolved skin.
+        // The reasons could be: duplicated in the css file due to @rules, in one css file then 
+        // another that extends that one.
+        // the most I've seen is 6. But as new skins are created that extend old skins,
+        // this number could increase.
+        l = new ArrayList<StyleNode>(6);
         m.put(id, l);
       }
       
       l.add(node);
     }
 
+
+    static private void _addToIconMap(
+      Map<String, List<IconNode>> m, 
+      IconNode                    node,
+      String                      id)
+    {
+      List<IconNode> l = m.get(id);
+      if (l == null)
+      {
+        // you may see this id (aka selector) multiple times in the resolved skin.
+        // The reasons could be: duplicated in the css file due to @rules, in one css file then 
+        // another that extends that one.
+        // The most I've seen is 6. But as new skins are created that extend old skins,
+        // this number could increase.
+        l = new ArrayList<IconNode>(6);
+        m.put(id, l);
+      }
+      
+      l.add(node);
+    }
     
     private Map<String, List<StyleNode>> _nameNodes;
     private Map<String, List<StyleNode>> _selectorNodes;
+    private Map<String, List<IconNode>>  _iconNodes;
     
     private final StyleSheetNode[] _styleSheets;
   }
@@ -1425,5 +1866,6 @@ public class StyleSheetDocument
   // Error messages
   private static final String _CIRCULAR_INCLUDE_ERROR =
     "Circular dependency detected in style ";
+  private static final Pattern _INTEGER_PATTERN = Pattern.compile("\\d+(px)?");
   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(StyleSheetDocument.class);
 }