Author: zhoresh
Date: Wed Aug 25 17:44:17 2010
New Revision: 989253

URL: http://svn.apache.org/viewvc?rev=989253&view=rev
Log:
url: http://codereview.appspot.com/1997041/
 Fixing ConcatVisitor for cases where media type is "all", no media type and 
having title attribute
 Patch provided by @Satya

Modified:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java?rev=989253&r1=989252&r2=989253&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
 Wed Aug 25 17:44:17 2010
@@ -82,35 +82,73 @@ public class ConcatVisitor implements Do
     
     return VisitStatus.BYPASS;
   }
-  
+
+  /**
+   * For css:
+   * Link tags are first split into buckets separated by tags with mediaType 
== "all"
+   * / title attribute different from their previous link tag / nodes that are 
+   * not 'link' tags. 
+   * This ensures that the buckets can be processed separately without losing 
title /
+   * "all" mediaType information.
+   *
+   * Link tags with same mediaType are concatenated within each bucket.
+   * This exercise ensures that css information is loaded in the same relative 
order
+   * as that of the original html page, and that the css information within
+   * mediaType=="all" is retained and applies to all media types.
+   *
+   * Look at the areLinkNodesBucketable method for details on mediaType=="all" 
and
+   * title attribute
+   *
+   * Example: Assume we have the following node list. (all have same parent,
+   * nodes between Node6 and Node12 are non link nodes, and hence did not come
+   * to revisit() call)
+   *    <link href="1.css" rel="stylesheet" type="text/css" media="screen">    
   -- Node1
+   *    <link href="2.css" rel="stylesheet" type="text/css" media="print">     
   -- Node2
+   *    <link href="3.css" rel="stylesheet" type="text/css" media="screen">    
   -- Node3
+   *    <link href="4.css" rel="stylesheet" type="text/css" media="all">       
   -- Node4
+   *    <link href="5.css" rel="stylesheet" type="text/css" media="all">       
   -- Node5
+   *    <link href="6.css" rel="stylesheet" type="text/css" media="screen">    
   -- Node6
+   *    <link href="12.css" rel="stylesheet" type="text/css" media="screen">   
   -- Node12
+   *    <link href="13.css" rel="stylesheet" type="text/css" media="screen">   
   -- Node13
+   *
+   *    First we split to buckets bassed on the adjacency and other conditions.
+   *    buckets - [ [ Node1, Node2, Node3 ], [ Node4, Node 5 ], [ Node6 ], [ 
Node12, Node13 ]
+   *    Within each bucket we group them based on media type.
+   *    batches - [ Node1, Node2, Node3 ] --> [ [Node1, Node3], [Node2] ]
+   *            - [ Node4, Node 5 ] --> [ [ Node4, Node 5 ] ]
+   *            - [ Node6 ] --> [ [ Node6 ] ]
+   *            - [ Node12, Node13 ] --> [ [ Node12, Node13 ] ]
+   *
+   * Refer Tests for more examples.
+   */
   public boolean revisit(Gadget gadget, List<Node> nodes) throws 
RewritingException {
-    // Collate Elements into batches to be concatenated. 
-    List<List<Element>> concatBatches = Lists.newLinkedList();
-    List<Element> curBatch = Lists.newLinkedList();
+    // Collate Elements into Buckets.
+    List<List<Element>> concatBuckets = Lists.newLinkedList();
+    List<Element> curBucket = Lists.newLinkedList();
     Iterator<Node> nodeIter = nodes.iterator();
     Element cur = (Element)nodeIter.next();
-    curBatch.add(cur);
+    curBucket.add(cur);
     while (nodeIter.hasNext()) {
       Element next = (Element)nodeIter.next();
-      if (!split && cur != getSibling(next, true)) {
-        // Break off current batch and add to list of all.
-        concatBatches.add(curBatch);
-        curBatch = Lists.newLinkedList();
+      if ((!split && cur != getSibling(next, true)) ||
+          (type == ConcatUriManager.Type.CSS && !areLinkNodesBucketable(cur, 
next))) {
+        // Break off current bucket and add to list of all.
+        concatBuckets.add(curBucket);
+        curBucket = Lists.newLinkedList();
       }
-      curBatch.add(next);
+      curBucket.add(next);
       cur = next;
     }
     
     // Add leftovers.
-    concatBatches.add(curBatch);
+    concatBuckets.add(curBucket);
 
-    // Split the existing batches based on media types.
-    List<List<Element>> mediaSpecificConcatBatches = Lists.newLinkedList();
-    Iterator<List<Element>> batchesIter = concatBatches.iterator();
+    // Split the existing buckets based on media types into concat batches.
+    List<List<Element>> concatBatches = Lists.newLinkedList();
+    Iterator<List<Element>> batchesIter = concatBuckets.iterator();
     while (batchesIter.hasNext()) {
-      splitBatchOnMedia(batchesIter.next(), mediaSpecificConcatBatches);
+      splitBatchOnMedia(batchesIter.next(), concatBatches);
     }
-    concatBatches = mediaSpecificConcatBatches;
     
     // Prepare batches of Uris to send to generate concat Uris
     List<List<Uri>> uriBatches = Lists.newLinkedList();
@@ -178,8 +216,8 @@ public class ConcatVisitor implements Do
     // Multimap to hold the ordered list of elements encountered for a given 
media type.
     Multimap<String, Element> mediaBatchMap = LinkedHashMultimap.create();
     for (Element element : elements) {
-      Element next = element;
-      mediaBatchMap.put(next.getAttribute("media"), next);
+      String mediaType = element.getAttribute("media");
+      mediaBatchMap.put(mediaType.isEmpty() ? "screen" : mediaType, element);
     }
     Set<String> mediaTypes = mediaBatchMap.keySet();
     for (String mediaType : mediaTypes) {
@@ -195,9 +233,9 @@ public class ConcatVisitor implements Do
       return false;
     }
     if (type == ConcatUriManager.Type.CSS) {
-      // rel="stylesheet" and type="css" also required.
-      return ("stylesheet".equalsIgnoreCase(elem.getAttribute("rel")) ||
-              elem.getAttribute("type").contains("css"));
+      // rel="stylesheet" and type="text/css" also required.
+      return ("stylesheet".equalsIgnoreCase(elem.getAttribute("rel")) &&
+              "text/css".equalsIgnoreCase(elem.getAttribute("type")));
     }
     return true;
   }
@@ -233,4 +271,31 @@ public class ConcatVisitor implements Do
     return true;
   }
 
+  /**
+   * Checks if the css link tags can be put into the same bucket.
+   */
+  private boolean areLinkNodesBucketable(Element current, Element next) {
+    boolean areLinkNodesCompatible = false;
+    // All link tags with media='all' should be placed in their own buckets.
+    // Except for adjacent css links with media='all', which can belong to the 
+    // same bucket.
+    String currMediaType = current.getAttribute("media");
+    String nextMediaType = next.getAttribute("media");
+    if (("all".equalsIgnoreCase(currMediaType) && 
"all".equalsIgnoreCase(nextMediaType)) ||
+        (!"all".equalsIgnoreCase(currMediaType) && 
!"all".equalsIgnoreCase(nextMediaType))) {
+      areLinkNodesCompatible = true;
+    }
+    
+    // we can't keep the link tags with different 'title' attribute in same 
+    // bucket.
+    // An example that proves the above comment.
+    // <link rel="stylesheet" type="text/css" href="a.css" />
+    // <link rel="stylesheet" type="text/css" href="b.css" title="small font"/>
+    // <link rel="stylesheet" type="text/css" href="c.css" />
+    // <link rel="alternate stylesheet" type="text/css" href="d.css" 
title="large font"/>
+    // Since browser allows to switch between the perferred styles 'small 
font' and 'large font',
+    // we should not batch across the links with title attribute, as it will 
lead to reordering of
+    // styles.
+    return areLinkNodesCompatible && 
current.getAttribute("title").equals(next.getAttribute("title"));
+  }
 }

Modified: 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java?rev=989253&r1=989252&r2=989253&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java
 Wed Aug 25 17:44:17 2010
@@ -41,7 +41,7 @@ import java.util.List;
 import java.util.Map;
 
 public class ConcatVisitorTest extends DomWalkerTestBase {
-  private static final String JS1_URL_STR = "http://one.com/foo.js";;
+  private static final String JS1_URL_STR = 
"http://one.com/foo.js?test=1&ui=2";;
   private Node js1;
   
   private static final String JS2_URL_STR = "http://two.com/foo.js";;
@@ -85,7 +85,16 @@ public class ConcatVisitorTest extends D
   
   private static final String CSS9_URL_STR = "http://nine.com/foo.js";;
   private Node css9;
-  
+
+  private static final String CSS10_URL_STR = "http://ten.com/foo.js";;
+  private Node css10;
+
+  private static final String CSS11_URL_STR = "http://eleven.com/foo.js";;
+  private Node css11;
+
+  private static final String CSS12_URL_STR = "http://twelve.com/foo.js";;
+  private Node css12;
+
   private static final Uri CONCAT_BASE_URI = 
Uri.parse("http://test.com/proxy";);
   
   @Before
@@ -97,7 +106,7 @@ public class ConcatVisitorTest extends D
     js4 = elem("script", "src", JS4_URL_STR);
     js5 = elem("script", "src", JS5_URL_STR);
     js6 = elem("script", "src", JS6_URL_STR);
-    css1 = elem("link", "rel", "stylesheet", "type", "text/css", "href", 
CSS1_URL_STR);
+    css1 = elem("link", "rel", "Stylesheet", "type", "Text/css", "href", 
CSS1_URL_STR);
     css2 = elem("link", "rel", "stylesheet", "type", "text/css", "href", 
CSS2_URL_STR);
     css3 = elem("link", "rel", "stylesheet", "type", "text/css", "href", 
CSS3_URL_STR);
     css4 = elem("link", "rel", "stylesheet", "type", "text/css", "href", 
CSS4_URL_STR);
@@ -107,7 +116,9 @@ public class ConcatVisitorTest extends D
     css7 = elem("link", "rel", "stylesheet", "type", "text/css", "media", 
"screen", "href", CSS7_URL_STR);
     css8 = elem("link", "rel", "stylesheet", "type", "text/css", "media", 
"screen", "href", CSS8_URL_STR);
     css9 = elem("link", "rel", "stylesheet", "type", "text/css", "href", 
CSS9_URL_STR);
-  
+    css10 = elem("link", "rel", "stylesheet", "type", "text/css", "media", 
"all", "href", CSS10_URL_STR);
+    css11 = elem("link", "rel", "stylesheet", "type", "text/css", "media", 
"all", "href", CSS11_URL_STR);
+    css12 = elem("link", "rel", "stylesheet", "type", "text/css", "media", 
"all", "href", CSS12_URL_STR);
   }
   
   @Test
@@ -161,19 +172,49 @@ public class ConcatVisitorTest extends D
     assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, sep2));
     assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js3));
   }
-  
+
+  @Test
+  public void visitValidCss() throws Exception {
+    Node textNode = doc.createTextNode("");
+    Node node = elem("link", "type", "text/css", "rel", "stylesheet", "href", 
CSS1_URL_STR);
+    seqNodes(node, textNode, css1);
+    assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(node, null));
+  }
+
+  @Test
+  public void dontVisitCssSeperatedByNonEmptyTextNode() throws Exception {
+    Node textNode = doc.createTextNode("Data\n");
+    Node node = elem("link", "type", "text/css", "rel", "stylesheet", "href", 
CSS1_URL_STR);
+    seqNodes(node, textNode, css1);
+    assertEquals(VisitStatus.BYPASS, getVisitStatusCss(node, null));
+  }
+
   @Test
-  public void visitRelFreeCss() throws Exception {
+  public void dontVisitRelFreeCss() throws Exception {
     Node node = elem("link", "type", "text/css", "href", CSS1_URL_STR);
     seqNodes(node, css1);
-    assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(node, null));
+    assertEquals(VisitStatus.BYPASS, getVisitStatusCss(node, null));
   }
   
   @Test
-  public void visitTypeCssFreeCss() throws Exception {
+  public void dontVisitTypeCssFreeCss() throws Exception {
     Node node = elem("link", "rel", "stylesheet", "href", CSS1_URL_STR);
     seqNodes(node, css1);
-    assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(node, null));
+    assertEquals(VisitStatus.BYPASS, getVisitStatusCss(node, null));
+  }
+
+  @Test
+  public void dontVisitTypeCssWrongRelAttributes() throws Exception {
+    Node node = elem("link", "rel", "alternate", "type", "text/css", "href", 
CSS1_URL_STR);
+    seqNodes(node, css1);
+    assertEquals(VisitStatus.BYPASS, getVisitStatusCss(node, null));
+  }
+
+  @Test
+  public void dontVisitTypeCssWrongTypeAttributes() throws Exception {
+    Node node = elem("link", "rel", "stylesheet", "type", "text/javascript", 
"href", CSS1_URL_STR);
+    seqNodes(node, css1);
+    assertEquals(VisitStatus.BYPASS, getVisitStatusCss(node, null));
   }
   
   @Test
@@ -331,7 +372,7 @@ public class ConcatVisitorTest extends D
     assertEquals(CSS2_URL_STR, concatUri1.getQueryParameter("2"));
     assertNull(concatUri1.getQueryParameter("3"));
     
-    assertEquals(3, parent2.getChildNodes().getLength());
+    assertEquals(2, parent2.getChildNodes().getLength());
     Element cn2 = (Element)parent2.getChildNodes().item(0);
     Uri concatUri2 = Uri.parse(cn2.getAttribute("href").replace("&amp;", "&"));
     assertEquals(CONCAT_BASE_URI.getScheme(), concatUri2.getScheme());
@@ -339,8 +380,10 @@ public class ConcatVisitorTest extends D
     assertEquals(CONCAT_BASE_URI.getPath(), concatUri2.getPath());
     assertEquals(CSS3_URL_STR, concatUri2.getQueryParameter("1"));
     assertEquals(CSS4_URL_STR, concatUri2.getQueryParameter("2"));
-    assertEquals(CSS9_URL_STR, concatUri2.getQueryParameter("3"));
-    assertNull(concatUri2.getQueryParameter("4"));
+    assertEquals(CSS7_URL_STR, concatUri2.getQueryParameter("3"));
+    assertEquals(CSS8_URL_STR, concatUri2.getQueryParameter("4"));
+    assertEquals(CSS9_URL_STR, concatUri2.getQueryParameter("5"));
+    assertNull(concatUri2.getQueryParameter("6"));
     assertEquals("", cn2.getAttribute("media"));
     
     Element cn3 = (Element)parent2.getChildNodes().item(1);
@@ -352,18 +395,90 @@ public class ConcatVisitorTest extends D
     assertEquals(CSS6_URL_STR, concatUri3.getQueryParameter("2"));
     assertNull(concatUri3.getQueryParameter("3"));
     assertEquals("print", cn3.getAttribute("media"));
-  
-    Element cn4 = (Element)parent2.getChildNodes().item(2);
+  }
+
+  @Test
+  public void concatMultiBatchCssWithAllMediaTypeAndTitle() throws Exception {
+  List<Node> fullListCss = Lists.newArrayList();
+    // modify few node to have the title attriblue.
+    ((Element) css2).setAttribute("title", "one");
+    ((Element) css3).setAttribute("title", "two");
+    ((Element) css4).setAttribute("title", "two");
+    ((Element) css10).setAttribute("title", "two");
+    fullListCss.addAll(seqNodes(css1, css2, css3, css4, css10, css11, css12, 
css7, css8, css9));
+    Node parent1 = css1.getParentNode();
+    assertEquals(10, parent1.getChildNodes().getLength());
+
+    SimpleConcatUriManager mgr = simpleMgr();
+    ConcatVisitor.Css rewriter = new ConcatVisitor.Css(config(null, false), 
mgr);
+    assertTrue(rewriter.revisit(gadget(), fullListCss));
+
+    // Should have been split across 'all' media type and then batches should 
be independently
+    // concatenated.
+    Element cn1 = (Element)parent1.getChildNodes().item(0);
+    Uri concatUri1 = Uri.parse(cn1.getAttribute("href").replace("&amp;", "&"));
+    assertEquals(CONCAT_BASE_URI.getScheme(), concatUri1.getScheme());
+    assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri1.getAuthority());
+    assertEquals(CONCAT_BASE_URI.getPath(), concatUri1.getPath());
+    assertEquals(CSS1_URL_STR, concatUri1.getQueryParameter("1"));
+    assertNull(concatUri1.getQueryParameter("2"));
+    assertEquals("", cn1.getAttribute("media"));
+
+    Element cn2 = (Element)parent1.getChildNodes().item(1);
+    Uri concatUri2 = Uri.parse(cn2.getAttribute("href").replace("&amp;", "&"));
+    assertEquals(CONCAT_BASE_URI.getScheme(), concatUri2.getScheme());
+    assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri2.getAuthority());
+    assertEquals(CONCAT_BASE_URI.getPath(), concatUri2.getPath());
+    assertEquals(CSS2_URL_STR, concatUri2.getQueryParameter("1"));
+    assertNull(concatUri2.getQueryParameter("2"));
+    assertEquals("", cn2.getAttribute("media"));
+    assertEquals("one", cn2.getAttribute("title"));
+
+    Element cn3 = (Element)parent1.getChildNodes().item(2);
+    Uri concatUri3 = Uri.parse(cn3.getAttribute("href").replace("&amp;", "&"));
+    assertEquals(CONCAT_BASE_URI.getScheme(), concatUri3.getScheme());
+    assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri3.getAuthority());
+    assertEquals(CONCAT_BASE_URI.getPath(), concatUri3.getPath());
+    assertEquals(CSS3_URL_STR, concatUri3.getQueryParameter("1"));
+    assertEquals(CSS4_URL_STR, concatUri3.getQueryParameter("2"));
+    assertNull(concatUri3.getQueryParameter("3"));
+    assertEquals("", cn3.getAttribute("media"));
+    assertEquals("two", cn3.getAttribute("title"));
+
+    Element cn4 = (Element)parent1.getChildNodes().item(3);
     Uri concatUri4 = Uri.parse(cn4.getAttribute("href").replace("&amp;", "&"));
     assertEquals(CONCAT_BASE_URI.getScheme(), concatUri4.getScheme());
     assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri4.getAuthority());
     assertEquals(CONCAT_BASE_URI.getPath(), concatUri4.getPath());
-    assertEquals(CSS7_URL_STR, concatUri4.getQueryParameter("1"));
-    assertEquals(CSS8_URL_STR, concatUri4.getQueryParameter("2"));
-    assertNull(concatUri4.getQueryParameter("3"));
-    assertEquals("screen", cn4.getAttribute("media"));
+    assertEquals(CSS10_URL_STR, concatUri4.getQueryParameter("1"));
+    assertNull(concatUri4.getQueryParameter("2"));
+    assertEquals("all", cn4.getAttribute("media"));
+    assertEquals("two", cn4.getAttribute("title"));
+
+    Element cn5 = (Element)parent1.getChildNodes().item(4);
+    Uri concatUri5 = Uri.parse(cn5.getAttribute("href").replace("&amp;", "&"));
+    assertEquals(CONCAT_BASE_URI.getScheme(), concatUri5.getScheme());
+    assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri5.getAuthority());
+    assertEquals(CONCAT_BASE_URI.getPath(), concatUri5.getPath());
+    assertEquals(CSS11_URL_STR, concatUri5.getQueryParameter("1"));
+    assertEquals(CSS12_URL_STR, concatUri5.getQueryParameter("2"));
+    assertNull(concatUri5.getQueryParameter("3"));
+    assertEquals("all", cn5.getAttribute("media"));
+    assertEquals("", cn5.getAttribute("title"));
+
+    Element cn6 = (Element)parent1.getChildNodes().item(5);
+    Uri concatUri6 = Uri.parse(cn6.getAttribute("href").replace("&amp;", "&"));
+    assertEquals(CONCAT_BASE_URI.getScheme(), concatUri6.getScheme());
+    assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri6.getAuthority());
+    assertEquals(CONCAT_BASE_URI.getPath(), concatUri6.getPath());
+    assertEquals(CSS7_URL_STR, concatUri6.getQueryParameter("1"));
+    assertEquals(CSS8_URL_STR, concatUri6.getQueryParameter("2"));
+    assertEquals(CSS9_URL_STR, concatUri6.getQueryParameter("3"));
+    assertNull(concatUri6.getQueryParameter("4"));
+    assertEquals("screen", cn6.getAttribute("media"));
+    assertEquals("", cn6.getAttribute("title"));
   }
-  
+
   @Test
   public void concatMultiBatchJsBadBatch() throws Exception {
     List<Node> fullListJs = Lists.newArrayList();


Reply via email to