View Javadoc

1   package com.atlassian.plugin.webresource;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.HashSet;
6   import java.util.LinkedHashSet;
7   import java.util.Set;
8   
9   import org.apache.commons.collections.CollectionUtils;
10  
11  import com.google.common.collect.Iterables;
12  
13  /**
14   * A helper classes that knows how to perform certain operations on ContextBatch beans.
15   */
16  class ContextBatchOperations
17  {
18      static final String CONTEXT_SEPARATOR = ",";
19      static final String CONTEXT_SUBTRACTION = "-";
20          
21      private PluginResourceLocator pluginResourceLocator;
22      private final WebResourceFilter filter;
23      
24      /**
25       * Parse a context batch key which typically has the form batch1,batch2,-excludedBatch3,-excludedBatch4
26       * 
27       * @param key the key to parse
28       * @param included the ordered Set to be populated with included batch names.
29       * @param excluded the Set to be populated with excluded batch names. The order of exclusion is not important.
30       */
31      static void parseContexts(final String key, LinkedHashSet<String> included, Set<String> excluded)
32      {
33          String[] split = key.split(CONTEXT_SEPARATOR);
34          for (String s : split)
35          {
36              if (s.startsWith(CONTEXT_SUBTRACTION))
37              {
38                  excluded.add(s.substring(1));
39              }
40              else
41              {
42                  included.add(s);
43              }
44          }
45      }    
46      
47      ContextBatchOperations(PluginResourceLocator pluginResourceLocator, WebResourceFilter filter)
48      {
49          this.pluginResourceLocator = pluginResourceLocator;
50          this.filter = filter;
51      }
52      
53      /**
54       * Merges context batches into a single context batch. 
55       * <p/>
56       * <strong>Note:</strong>you can only merge batches if all batches <strong>do not</strong> 
57       * have any excluded contexts. The problem is that with excluded contexts you can't know
58       * if the batch you are merging with should have resources (and resource parameters)
59       * removed and you are therefore going to end up with a merged batch with a potentially
60       * wrong hash. (See {@link #createHash()})
61       * 
62       * @param batchesToMerge - one or more batches to merge together 
63       * @return a single context batch which is the result of merging all the supplied batches.
64       * @throw IllegalArgumentException if any of the batches have excludedContexts
65       */
66      ContextBatch merge(final Collection<ContextBatch> batchesToMerge)
67      {
68          if (CollectionUtils.isEmpty(batchesToMerge))
69              return null;
70          
71          if (batchesToMerge.size() == 1)
72              return batchesToMerge.iterator().next();
73          
74          final StringBuilder mergedKey = new StringBuilder();
75          // the ordering of contexts must be maintained for consistency of resources within the batch
76          final LinkedHashSet<String> includedContexts = new LinkedHashSet<String>();
77          final Set<WebResourceModuleDescriptor> resources = new HashSet<WebResourceModuleDescriptor>();
78          final Set<PluginResourceBatchParams> batchResourceParams = new HashSet<PluginResourceBatchParams>();
79          
80          for (ContextBatch batch : batchesToMerge)
81          {
82              if (!Iterables.isEmpty(batch.getExcludedContexts()))
83                  throw new IllegalArgumentException("The ContextBatch " + batch.getKey() + " has excludedContexts.");
84              
85              mergedKey.append(batch.getKey()).append(CONTEXT_SEPARATOR);
86              includedContexts.addAll(batch.getContexts());
87              Iterables.addAll(resources, batch.getResources());
88              Iterables.addAll(batchResourceParams, batch.getResourceParams());
89          }
90          
91          mergedKey.deleteCharAt(mergedKey.length() - 1);
92  
93          return new ContextBatch(mergedKey.toString(), new ArrayList<String>(includedContexts), null, resources, batchResourceParams);        
94      }
95      
96      /**
97       * Subtract ContextBatches from the supplied operand, creating a new ContextBatch (unless there are no
98       * batches to subtract in which case the supplied operand is returned unchanged).
99       * <p/>
100      * Subtraction for a ContextBatch means the removal of any WebResourceModuleDescriptor that exist within
101      * the ContextBatch being subtracted. Consequently it also means that some PluginResourceBatchParams
102      * may also be removed (if there is no longer an applicable PluginResource). 
103      * <p/> 
104      * <strong>Note:</strong>you can only subtract batches if all batches <strong>do not</strong> 
105      * have any excluded contexts. The problem is that with excluded contexts you end up with the
106      * possibility of subtracting a subtracted context. And since the already subtracted context no
107      * longer has reference to the WebResourceModuleDescriptor it caused to be removed you cannot
108      * ensure that the resultant ContextBatch also has them removed.
109      * 
110      * @param operand the ContextBatch to be operated upon
111      * @param batchesToSubtract the ContextBatches to be subtracted.
112      * @return the ContextBatch resulting in subtracting the batchesToSubtract.
113      * @throw IllegalArgumentException if any of the batchesToSubtract already have excluded contexts.
114      */
115     ContextBatch subtract(final ContextBatch operand, final Collection<ContextBatch> batchesToSubtract)
116     {
117         if (CollectionUtils.isEmpty(batchesToSubtract))
118             return operand;
119         
120         Collection<String> excludedContexts = new HashSet<String>();
121         Iterables.addAll(excludedContexts, operand.getExcludedContexts());
122         final Collection<WebResourceModuleDescriptor> resources = new HashSet<WebResourceModuleDescriptor>();
123         Iterables.addAll(resources, operand.getResources());
124 
125         for (ContextBatch subtract : batchesToSubtract)
126         {
127             if (!Iterables.isEmpty(subtract.getExcludedContexts()))
128             {
129                 throw new IllegalArgumentException("The ContextBatch " + subtract.getKey() + " has excludedContexts.");
130             }
131             
132             Iterables.addAll(excludedContexts, subtract.getContexts());
133             Iterable<WebResourceModuleDescriptor> subtractResources = subtract.getResources();
134             for (WebResourceModuleDescriptor resource : subtractResources)
135             {
136                 resources.remove(resource);
137             }
138         }
139 
140         StringBuilder key = new StringBuilder();
141         for (String includedContext : operand.getContexts())
142         {
143             key.append(includedContext).append(CONTEXT_SEPARATOR);
144         }
145         
146         for (String excludedContext : excludedContexts)
147         {
148             key.append(CONTEXT_SUBTRACTION).append(excludedContext).append(CONTEXT_SEPARATOR);
149         }
150         
151         key.deleteCharAt(key.length() - 1);
152         ContextBatch subtractionResult = new ContextBatch(key.toString(), operand.getContexts(),excludedContexts, resources, new HashSet<PluginResourceBatchParams>());
153         
154         // now calculate the new PluginResourceBatchParams for the remaining WebResourceModuleDescriptors
155         for (WebResourceModuleDescriptor resource : resources)
156         {
157             for (final PluginResource pluginResource : pluginResourceLocator.getPluginResources(resource.getCompleteKey()))
158             {
159                 if (filter.matches(pluginResource.getResourceName()))
160                 {
161                     subtractionResult.addResourceType(pluginResource);
162                 }
163             }
164         }
165         
166         return subtractionResult;
167     }
168 }