You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2010/03/24 23:07:10 UTC

svn commit: r927200 - in /shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/features/ test/java/org/apache/shindig/gadgets/ test/java/org/apache/shindig/gadgets/features/

Author: lindner
Date: Wed Mar 24 22:07:09 2010
New Revision: 927200

URL: http://svn.apache.org/viewvc?rev=927200&view=rev
Log:
SHINDIG-1283 | concurrent access semantics for featureMap

Modified:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureParser.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureRegistry.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/features/FeatureRegistryTest.java

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureParser.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureParser.java?rev=927200&r1=927199&r2=927200&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureParser.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureParser.java Wed Mar 24 22:07:09 2010
@@ -17,6 +17,8 @@
  */
 package org.apache.shindig.gadgets.features;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
@@ -91,7 +93,7 @@ class FeatureParser {
   }
   
   private Map<String, String> getAttribs(Element element) {
-    Map<String, String> attribs = Maps.newHashMap();
+    ImmutableMap.Builder<String, String> attribs = ImmutableMap.builder();
     NamedNodeMap attribNodes = element.getAttributes();
     for (int x = 0, y = attribNodes.getLength(); x < y; ++x) {
       Attr attr = (Attr)attribNodes.item(x);
@@ -99,7 +101,7 @@ class FeatureParser {
         attribs.put(attr.getName(), attr.getValue());
       }
     }
-    return Collections.unmodifiableMap(attribs);
+    return attribs.build();
   }
   
   static class ParsedFeature {
@@ -109,8 +111,8 @@ class FeatureParser {
     
     private ParsedFeature(String name, List<String> deps, List<Bundle> bundles) {
       this.name = name;
-      this.deps = Collections.unmodifiableList(deps);
-      this.bundles = Collections.unmodifiableList(bundles);
+      this.deps = ImmutableList.copyOf(deps);
+      this.bundles = ImmutableList.copyOf(bundles);
     }
     
     public String getName() {
@@ -157,7 +159,7 @@ class FeatureParser {
       private Resource(Uri source, String content, Map<String, String> attribs) {
         this.source = source;
         this.content = content;
-        this.attribs = Collections.unmodifiableMap(attribs);
+        this.attribs = ImmutableMap.copyOf(attribs);
       }
       
       public Uri getSource() {

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureRegistry.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureRegistry.java?rev=927200&r1=927199&r2=927200&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureRegistry.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/features/FeatureRegistry.java Wed Mar 24 22:07:09 2010
@@ -17,6 +17,11 @@
  */
 package org.apache.shindig.gadgets.features;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.MapMaker;
 import com.google.common.collect.Maps;
@@ -67,25 +72,28 @@ public class FeatureRegistry {
 
   private final FeatureParser parser;
   private final FeatureResourceLoader resourceLoader;
-  private final Map<String, FeatureNode> featureMap;
+  private final ImmutableMap<String, FeatureNode> featureMap;
   
   @Inject
-  public FeatureRegistry(FeatureResourceLoader resourceLoader) {
+
+/**
+ *
+ * @param featureFiles
+ * @throws GadgetException
+ */
+  public FeatureRegistry(FeatureResourceLoader resourceLoader,
+                         @Named("shindig.features.default") String featureFiles) throws GadgetException {
     this.parser = new FeatureParser();
     this.resourceLoader = resourceLoader;
-    this.featureMap = Maps.newHashMap();
-  }
-  
-  /**
-   * For compatibility with GadgetFeatureRegistry, and to provide a programmatic hook
-   * for adding feature files by config, @Inject the @Named featureFiles variable.
-   * @param featureFiles
-   * @throws GadgetException
-   */
-  @Inject(optional = true)
-  public void addDefaultFeatures(
-      @Named("shindig.features.default") String featureFiles) throws GadgetException {
-    register(featureFiles);
+
+    featureMap = register(featureFiles);
+
+    // Connect the dependency graph made up of all features and validate there
+    // are no circular deps.
+    connectDependencyGraph();
+
+    // Clear caches.
+    cache.clear();
   }
   
   /**
@@ -109,7 +117,9 @@ public class FeatureRegistry {
    *    them with a comma.
    * @throws GadgetException If any of the files can't be read, are malformed, or invalid.
    */
-  public void register(String resourceKey) throws GadgetException {
+  protected ImmutableMap<String,FeatureNode> register(String resourceKey) throws GadgetException {
+    Map<String,FeatureNode> featureMapBuilder = Maps.newHashMap();
+
     try {
       for (String location : StringUtils.split(resourceKey, FILE_SEPARATOR)) {
         Uri uriLoc = getComponentUri(location);
@@ -139,21 +149,15 @@ public class FeatureRegistry {
             resources.add(location);
           }
           
-          loadResources(resources);
+          loadResources(resources, featureMapBuilder);
         } else {
           // Load files in directory structure.
           logger.info("Loading files from: " + location);
           
-          loadFile(new File(uriLoc.getPath()));
+          loadFile(new File(uriLoc.getPath()), featureMapBuilder);
         }
       }
-      
-      // Connect the dependency graph made up of all features and validate there
-      // are no circular deps.
-      connectDependencyGraph();
-      
-      // Clear caches.
-      cache.clear();
+      return ImmutableMap.copyOf(featureMapBuilder);
     } catch (IOException e) {
       throw new GadgetException(GadgetException.Code.INVALID_PATH, e);
     }
@@ -180,9 +184,7 @@ public class FeatureRegistry {
   public List<FeatureResource> getFeatureResources(
       GadgetContext ctx, Collection<String> needed, List<String> unsupported, boolean transitive) {
     boolean useCache = (transitive && !ctx.getIgnoreCache());
-    
     FeatureCacheKey cacheKey = new FeatureCacheKey(needed, ctx, unsupported != null);
-    List<FeatureResource> resources = Lists.newLinkedList();
     
     if (useCache && cache.containsKey(cacheKey)) {
       return cache.get(cacheKey);
@@ -195,9 +197,9 @@ public class FeatureRegistry {
       featureNodes = getRequestedNodes(needed, unsupported);
     }
 
-    String targetBundleType =
-        ctx.getRenderingContext() == RenderingContext.CONTAINER ? "container" : "gadget";
-    
+    String targetBundleType = ctx.getRenderingContext() == RenderingContext.CONTAINER ? "container" : "gadget";
+    ImmutableList.Builder<FeatureResource> resourcesBuilder = new ImmutableList.Builder<FeatureResource>();
+
     for (FeatureNode entry : featureNodes) {
       boolean specificContainerMatched = false;
       List<FeatureBundle> noContainerBundles = Lists.newLinkedList();
@@ -206,7 +208,7 @@ public class FeatureRegistry {
           String containerAttrib = bundle.getAttribs().get("container");
           if (containerAttrib != null) {
             if (containerMatch(containerAttrib, ctx.getContainer())) {
-              resources.addAll(bundle.getResources());
+              resourcesBuilder.addAll(bundle.getResources());
               specificContainerMatched = true;
             }
           } else {
@@ -217,11 +219,11 @@ public class FeatureRegistry {
       }
       if (!specificContainerMatched) {
         for (FeatureBundle bundle : noContainerBundles) {
-          resources.addAll(bundle.getResources());
+          resourcesBuilder.addAll(bundle.getResources());
         }
       }
     }
-    
+    List<FeatureResource> resources = resourcesBuilder.build();
     if (useCache && (unsupported == null || unsupported.isEmpty())) {
       cache.put(cacheKey, resources);
     }
@@ -236,11 +238,10 @@ public class FeatureRegistry {
    * @param unsupported If non-null, a List populated with unknown features from the needed list.
    * @return List of FeatureResources that may be used to render the needed features.
    */
-  public List<FeatureResource> getFeatureResources(
-      GadgetContext ctx, Collection<String> needed, List<String> unsupported) {
+  public List<FeatureResource> getFeatureResources(GadgetContext ctx, Collection<String> needed, List<String> unsupported) {
     return getFeatureResources(ctx, needed, unsupported, true);
   }
-  
+
   /**
    * Returns all known FeatureResources in dependency order, as described in getFeatureResources.
    * Returns only GADGET-context resources. This is a convenience method largely for calculating
@@ -248,8 +249,7 @@ public class FeatureRegistry {
    * @return List of all known (RenderingContext.GADGET) FeatureResources.
    */
   public List<FeatureResource> getAllFeatures() {
-    return getFeatureResources(
-        new GadgetContext(), Lists.newArrayList(featureMap.keySet()), null);
+    return getFeatureResources(new GadgetContext(), featureMap.keySet(), null);
   }
   
   /**
@@ -259,7 +259,7 @@ public class FeatureRegistry {
    * @param needed List of features for which to obtain an ordered dep list.
    * @return Ordered list of feature names, as described.
    */
-  public List<String> getFeatures(List<String> needed) {
+  public List<String> getFeatures(Collection<String> needed) {
     List<FeatureNode> fullTree = getTransitiveDeps(needed, Lists.<String>newLinkedList());
     List<String> allFeatures = Lists.newLinkedList();
     for (FeatureNode node : fullTree) {
@@ -267,13 +267,13 @@ public class FeatureRegistry {
     }
     return allFeatures;
   }
-  
+
   /**
    * Helper method, returns all known feature names.
    * @return All known feature names.
    */
-  public List<String> getAllFeatureNames() {
-    return Lists.newArrayList(featureMap.keySet());
+  public Set<String> getAllFeatureNames() {
+    return featureMap.keySet();
   }
   
   // Visible for testing.
@@ -344,11 +344,10 @@ public class FeatureRegistry {
   }
   
   private boolean containerMatch(String containerAttrib, String container) {
-    Set<String> containers = Sets.newHashSet();
     for (String attr : StringUtils.split(containerAttrib, ',')) {
-      containers.add(attr.trim());
+      if (attr.trim().equals(container)) return true;
     }
-    return containers.contains(container);
+    return false;
   }
   
   private void connectDependencyGraph() throws GadgetException {
@@ -394,7 +393,7 @@ public class FeatureRegistry {
     }
   }
   
-  private void loadResources(List<String> resources) throws GadgetException {
+  private void loadResources(List<String> resources, Map<String,FeatureNode> featureMapBuilder) throws GadgetException {
     try {
       for (String resource : resources) {
         if (logger.isLoggable(Level.FINE)) {
@@ -403,34 +402,29 @@ public class FeatureRegistry {
         
         String content = getResourceContent(resource);
         Uri parent = new UriBuilder().setScheme(RESOURCE_SCHEME).setPath(resource).toUri();
-        loadFeature(parent, content);
+        loadFeature(parent, content, featureMapBuilder);
       }
     } catch (IOException e) {
       throw new GadgetException(GadgetException.Code.INVALID_PATH, e);
     }
   }
 
-  private void loadFile(File file) throws GadgetException, IOException {
+  private void loadFile(File file, Map<String,FeatureNode> featureMapBuilder) throws GadgetException, IOException {
     if (!file.exists() || !file.canRead()) {
       throw new GadgetException(GadgetException.Code.INVALID_CONFIG,
           "Feature file '" + file.getPath() + "' doesn't exist or can't be read");
     }
     
-    File[] toLoad = null;
-    if (file.isDirectory()) {
-      toLoad = file.listFiles();
-    } else {
-      toLoad = new File[] { file };
-    }
-    
+    File[] toLoad = file.isDirectory() ? file.listFiles() : new File[] { file };
+
     for (File featureFile : toLoad) {
       if (featureFile.isDirectory()) {
         // Traverse into subdirectories.
-        loadFile(featureFile);
+        loadFile(featureFile, featureMapBuilder);
       } else if (featureFile.getName().toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
         String content = ResourceLoader.getContent(featureFile);
         Uri parent = Uri.fromJavaUri(featureFile.toURI());
-        loadFeature(parent, content);
+        loadFeature(parent, content, featureMapBuilder);
       } else {
         if (logger.isLoggable(Level.FINEST)) {
           logger.finest(featureFile.getAbsolutePath() + " doesn't seem to be an XML file.");
@@ -438,12 +432,18 @@ public class FeatureRegistry {
       }
     }
   }
-  
-  protected void loadFeature(Uri parent, String xml) throws GadgetException {
+
+  /**
+   * Method that loads gadget features.
+   *
+   * @param parent uri of parent
+   * @param xml xml to parse
+   * @throws GadgetException
+   */
+  protected void loadFeature(Uri parent, String xml, Map<String,FeatureNode> featureMapBuilder) throws GadgetException {
     FeatureParser.ParsedFeature parsed = parser.parse(parent, xml);
-    
     // Duplicate feature = OK, just indicate it's being overridden.
-    if (featureMap.containsKey(parsed.getName())) {
+    if (featureMapBuilder.containsKey(parsed.getName())) {
       if (logger.isLoggable(Level.WARNING)) {
         logger.warning("Overriding feature: " + parsed.getName() + " with def at: " + parent);
       }
@@ -458,15 +458,14 @@ public class FeatureRegistry {
           resources.add(new InlineFeatureResource(parsedResource.getContent()));
         } else {
           // Load using resourceLoader
-          resources.add(
-              resourceLoader.load(parsedResource.getSource(), parsedResource.getAttribs()));
+          resources.add(resourceLoader.load(parsedResource.getSource(), parsedResource.getAttribs()));
         }
       }
       bundles.add(new FeatureBundle(parsedBundle.getType(), parsedBundle.getAttribs(), resources));
     }
     
     // Add feature to the master Map. The dependency tree isn't connected/validated/linked yet.
-    featureMap.put(parsed.getName(), new FeatureNode(parsed.getName(), bundles, parsed.getDeps()));
+    featureMapBuilder.put(parsed.getName(), new FeatureNode(parsed.getName(), bundles, parsed.getDeps()));
   }
   
   private static class InlineFeatureResource extends FeatureResource.Default {
@@ -490,11 +489,10 @@ public class FeatureRegistry {
     private final Map<String, String> attribs;
     private final List<FeatureResource> resources;
     
-    private FeatureBundle(String type, Map<String, String> attribs,
-        List<FeatureResource> resources) {
+    private FeatureBundle(String type, Map<String, String> attribs, List<FeatureResource> resources) {
       this.type = type;
-      this.attribs = Collections.unmodifiableMap(attribs);
-      this.resources = Collections.unmodifiableList(resources);
+      this.attribs = ImmutableMap.copyOf(attribs);
+      this.resources = ImmutableList.copyOf(resources);
     }
     
     public String getType() {
@@ -521,8 +519,8 @@ public class FeatureRegistry {
     
     private FeatureNode(String name, List<FeatureBundle> bundles, List<String> rawDeps) {
       this.name = name;
-      this.bundles = Collections.unmodifiableList(bundles);
-      this.requestedDeps = Collections.unmodifiableList(rawDeps);
+      this.bundles = ImmutableList.copyOf(bundles);
+      this.requestedDeps = ImmutableList.copyOf(rawDeps);
       this.depList = Lists.newLinkedList();
       this.transitiveDeps = Lists.newArrayList(this);
       this.calculatedDepsStale = false;
@@ -544,7 +542,7 @@ public class FeatureRegistry {
     private List<FeatureNode> getDepList() {
       List<FeatureNode> revOrderDeps = Lists.newArrayList(depList);
       Collections.reverse(revOrderDeps);
-      return Collections.unmodifiableList(revOrderDeps);
+      return ImmutableList.copyOf(revOrderDeps);
     }
     
     public void completeNodeGraph() throws GadgetException {
@@ -611,7 +609,7 @@ public class FeatureRegistry {
     @Override
     public int hashCode() {
       // Doesn't need to be good, just cheap and consistent.
-      return needed.hashCode() + rCtx.hashCode() + container.hashCode() + useUnsupported.hashCode();
+      return Objects.hashCode(needed, rCtx, container, useUnsupported);
     }
   }
 }

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java?rev=927200&r1=927199&r2=927200&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java Wed Mar 24 22:07:09 2010
@@ -28,6 +28,7 @@ import org.apache.shindig.gadgets.spec.L
 
 import org.junit.Test;
 
+import java.util.Collection;
 import java.util.List;
 
 import com.google.common.collect.Lists;
@@ -75,7 +76,7 @@ public class GadgetTest extends EasyMock
         .setContext(context)
         .setGadgetFeatureRegistry(registry)
         .setSpec(new GadgetSpec(Uri.parse(SPEC_URL), xml));
-    List<String> needed = Lists.newArrayList(gadget.getSpec().getModulePrefs().getFeatures().keySet());
+    Collection<String> needed = Lists.newArrayList(gadget.getSpec().getModulePrefs().getFeatures().keySet());
     List<String> returned = Lists.newArrayList();
     // Call should only happen once, and be cached from there on out.
     expect(registry.getFeatures(eq(needed))).andReturn(returned).anyTimes();

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java?rev=927200&r1=927199&r2=927200&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java Wed Mar 24 22:07:09 2010
@@ -35,6 +35,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 public class HashLockedDomainServiceTest extends EasyMockTestCase {
@@ -72,7 +73,7 @@ public class HashLockedDomainServiceTest
     }
 
     FeatureRegistry registry = mock(FeatureRegistry.class);
-    expect(registry.getFeatures(isA(List.class))).andReturn(gadgetFeatures).anyTimes();
+    expect(registry.getFeatures(isA(Collection.class))).andReturn(gadgetFeatures).anyTimes();
     return new Gadget().setSpec(spec).setGadgetFeatureRegistry(registry);
   }
 

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/features/FeatureRegistryTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/features/FeatureRegistryTest.java?rev=927200&r1=927199&r2=927200&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/features/FeatureRegistryTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/features/FeatureRegistryTest.java Wed Mar 24 22:07:09 2010
@@ -17,11 +17,7 @@
  */
 package org.apache.shindig.gadgets.features;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
@@ -31,6 +27,7 @@ import org.apache.shindig.config.Contain
 import org.apache.shindig.gadgets.GadgetContext;
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.RenderingContext;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -40,6 +37,10 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 public class FeatureRegistryTest {
   private static final String NODEP_TPL =
@@ -63,10 +64,10 @@ public class FeatureRegistryTest {
   
   private static String RESOURCE_BASE_PATH = "/resource/base/path";
   private static int resourceIdx = 0;
-  private FeatureRegistry registry;
   private FeatureResourceLoader resourceLoader;
   private ResourceMock resourceMock;
-  
+  FeatureRegistry registry;
+
   @Before
   public void setUp() {
     resourceMock = new ResourceMock();
@@ -80,14 +81,17 @@ public class FeatureRegistryTest {
         }
       }
     };
-    registry = new FeatureRegistry(resourceLoader) {
-      @Override
-      String getResourceContent(String resource) throws IOException {
-        return resourceMock.get(resource);
-      }
-    };
   }
-  
+
+  private class TestFeatureRegistry extends FeatureRegistry {
+    TestFeatureRegistry(String featureFiles) throws GadgetException {
+      super(resourceLoader, featureFiles);
+    }
+    @Override
+    String getResourceContent(String resource) throws IOException {
+      return resourceMock.get(resource);
+    }
+  }
   @Test
   public void registerFromFileFeatureXmlFileScheme() throws Exception {
     checkRegisterFromFileFeatureXml(true);
@@ -103,7 +107,7 @@ public class FeatureRegistryTest {
     Uri resUri = makeFile(content);
     Uri featureFile = makeFile(xml(NODEP_TPL, "gadget",
         withScheme ? resUri.toString() : resUri.getPath(), null));
-    registry.register(withScheme ? featureFile.toString() : featureFile.getPath());
+    registry = new TestFeatureRegistry(withScheme ? featureFile.toString() : featureFile.getPath());
     
     // Verify single resource works all the way through.
     List<FeatureResource> resources = registry.getAllFeatures();
@@ -135,7 +139,7 @@ public class FeatureRegistryTest {
     out = new BufferedWriter(new FileWriter(featureFile));
     out.write(xml(NODEP_TPL, "gadget", resFile.toURI().toString(), null));
     out.close();
-    registry.register(childDir.toURI().toString());
+    registry = new TestFeatureRegistry(childDir.toURI().toString());
     
     // Verify single resource works all the way through.
     List<FeatureResource> resources = registry.getAllFeatures();
@@ -148,7 +152,7 @@ public class FeatureRegistryTest {
     String content = "resource-content()";
     Uri contentUri = expectResource(content);
     Uri featureUri = expectResource(xml(NODEP_TPL, "gadget", contentUri.getPath(), null));
-    registry.addDefaultFeatures(featureUri.toString());
+    registry = new TestFeatureRegistry(featureUri.toString());
     
     // Verify single resource works all the way through.
     List<FeatureResource> resources = registry.getAllFeatures();
@@ -162,7 +166,7 @@ public class FeatureRegistryTest {
     Uri contentUri = expectResource(content);
     String relativePath = contentUri.getPath().substring(contentUri.getPath().lastIndexOf('/') + 1);
     Uri featureUri = expectResource(xml(NODEP_TPL, "gadget", relativePath, null));
-    registry.register(featureUri.toString());
+    registry = new TestFeatureRegistry(featureUri.toString());
     
     // Verify single resource works all the way through.
     List<FeatureResource> resources = registry.getAllFeatures();
@@ -185,7 +189,7 @@ public class FeatureRegistryTest {
     Uri txtFile = expectResource(feature1Uri.toString() + '\n' + feature2Uri.toString(), ".txt");
     
     // Load resources from the text file and do basic validation they're good.
-    registry.register(txtFile.toString());
+    registry = new TestFeatureRegistry(txtFile.toString());
     
     // Contents should be ordered based on the way they went in.
     List<FeatureResource> resources = registry.getAllFeatures();
@@ -205,17 +209,17 @@ public class FeatureRegistryTest {
     Uri content2Uri = expectResource(content2);
     Uri feature2Uri = expectResource(xml(BOTTOM_TPL, "gadget", content2Uri.getPath(), null));
     
-    registry.register(feature1Uri.toString());
+    registry = new TestFeatureRegistry(feature1Uri.toString());
     List<FeatureResource> resources1 = registry.getAllFeatures();
     assertEquals(1, resources1.size());
     assertEquals(content1, resources1.get(0).getContent());
     
     // Register it again, different def.
-    registry.register(feature2Uri.toString());
+    registry = new TestFeatureRegistry(feature2Uri.toString());
     List<FeatureResource> resources2 = registry.getAllFeatures();
     assertEquals(1, resources2.size());
     assertEquals(content2, resources2.get(0).getContent());
-    
+
     // Check cached resources too.
     List<FeatureResource> resourcesAgain = registry.getAllFeatures();
     assertSame(resources2, resourcesAgain);
@@ -231,7 +235,7 @@ public class FeatureRegistryTest {
     Uri feature1Uri = expectResource(xml(BOTTOM_TPL, "gadget", content1Uri.getPath(), null, attribs));
     
     // Register it.
-    registry.register(feature1Uri.toString());
+    registry = new TestFeatureRegistry(feature1Uri.toString());
     
     // Retrieve content for matching context.
     List<String> needed = Lists.newArrayList("bottom");
@@ -247,7 +251,7 @@ public class FeatureRegistryTest {
     assertEquals(resources, resourcesUnsup);
     assertEquals(1, resources.size());
     assertEquals(content1, resources.get(0).getContent());
-    
+
     // Now make sure the cache DOES work when needed.
     List<FeatureResource> resources2 = registry.getFeatureResources(
         getCtx(RenderingContext.GADGET, theContainer), needed, unsupported);
@@ -275,7 +279,7 @@ public class FeatureRegistryTest {
     Uri feature1Uri = expectResource(xml(BOTTOM_TPL, "gadget", content1Uri.getPath(), null, attribs));
     
     // Register it.
-    registry.register(feature1Uri.toString());
+    registry = new TestFeatureRegistry(feature1Uri.toString());
     
     // Retrieve content for matching context.
     List<String> needed = Lists.newArrayList("bottom");
@@ -293,7 +297,6 @@ public class FeatureRegistryTest {
 
     assertNotSame(resources, resourcesNoMatch);
     assertNotSame(resources, ctxMismatch);
-    assertNotSame(resourcesNoMatch, ctxMismatch);
     
     assertEquals(1, resources.size());
     assertEquals(content1, resources.get(0).getContent());
@@ -319,19 +322,20 @@ public class FeatureRegistryTest {
         getCtx(RenderingContext.GADGET, theContainer, true), needed, unsupported);
     assertNotSame(resources, resourcesIC);
     
-    List<FeatureResource> resourcesNoMatchIC = registry.getFeatureResources(
-        getCtx(RenderingContext.GADGET, "foo", true), needed, unsupported);
-    assertNotSame(resourcesNoMatch, resourcesNoMatchIC);
-    
-    List<FeatureResource> ctxMismatchIC = registry.getFeatureResources(
-        getCtx(RenderingContext.CONTAINER, theContainer, true), needed, unsupported);
-    assertNotSame(ctxMismatch, ctxMismatchIC);
+//    bogus tests - both requests return EMPTY_LIST now, so you can't ascertain cache behavior.
+//    List<FeatureResource> resourcesNoMatchIC = registry.getFeatureResources(
+//        getCtx(RenderingContext.GADGET, "foo", true), needed, unsupported);
+//    assertNotSame(resourcesNoMatch, resourcesNoMatchIC);
+//
+//    List<FeatureResource> ctxMismatchIC = registry.getFeatureResources(
+//        getCtx(RenderingContext.CONTAINER, theContainer, true), needed, unsupported);
+//    assertNotSame(ctxMismatch, ctxMismatchIC);
   }
   
   @Test
   public void missingIndexResultsInException() throws Exception {
     try {
-      registry.register(makeResourceUri(".txt").toString());
+      registry = new TestFeatureRegistry(makeResourceUri(".txt").toString());
       fail("Should have thrown an exception for missing .txt file");
     } catch (GadgetException e) {
       // Expected. Verify code.
@@ -342,7 +346,7 @@ public class FeatureRegistryTest {
   @Test
   public void missingFileResultsInException() throws Exception {
     try {
-      registry.register(new UriBuilder().setScheme("file")
+      registry = new TestFeatureRegistry(new UriBuilder().setScheme("file")
           .setPath("/is/not/there.foo.xml").toUri().toString());
       fail("Should have thrown missing .xml file exception");
     } catch (GadgetException e) {
@@ -521,7 +525,7 @@ public class FeatureRegistryTest {
     Uri txtFile = expectResource(loopAUri.toString() + '\n' + loopBUri.toString() + '\n' +
         loopCUri.toString(), ".txt");
     try {
-      registry.register(txtFile.toString());
+      registry = new TestFeatureRegistry(txtFile.toString());
       fail("Should have thrown a loop-detected exception");
     } catch (GadgetException e) {
       assertEquals(GadgetException.Code.INVALID_CONFIG, e.getCode());
@@ -532,7 +536,7 @@ public class FeatureRegistryTest {
   public void unavailableFeatureCrashes() throws Exception {
     Uri featUri = expectResource(xml(BAD_DEP_TPL, "gadget", null, "content"));
     try {
-      registry.register(featUri.toString());
+      registry = new TestFeatureRegistry(featUri.toString());
     } catch (GadgetException e) {
       assertEquals(GadgetException.Code.INVALID_CONFIG, e.getCode());
     }
@@ -547,10 +551,10 @@ public class FeatureRegistryTest {
     Uri featureUri =
         expectResource(
           getContainerAndDefaultTpl(feature, container, containerContent, defaultContent));
-    registry.register(featureUri.toString());
+    registry = new TestFeatureRegistry(featureUri.toString());
     List<String> needed = Lists.newArrayList(feature);
     List<String> unsupported = Lists.newLinkedList();
-    List<FeatureResource> resources = 
+    List<FeatureResource> resources =
         registry.getFeatureResources(
           getCtx(RenderingContext.GADGET, container), needed, unsupported);
     assertEquals(1, resources.size());
@@ -566,7 +570,7 @@ public class FeatureRegistryTest {
     Uri featureUri =
         expectResource(
           getContainerAndDefaultTpl(feature, container, containerContent, defaultContent));
-    registry.register(featureUri.toString());
+    registry = new TestFeatureRegistry(featureUri.toString());
     List<String> needed = Lists.newArrayList(feature);
     List<String> unsupported = Lists.newLinkedList();
     List<FeatureResource> resources = 
@@ -632,7 +636,7 @@ public class FeatureRegistryTest {
     Uri bottomUri = expectResource(xml(BOTTOM_TPL, type, null, "bottom", attribs));
     Uri txtFile = expectResource(nodepUri.toString() + '\n' + topUri.toString() + '\n' +
         midAUri.toString() + '\n' + midBUri.toString() + '\n' + bottomUri.toString(), ".txt");
-    registry.register(txtFile.toString());
+    registry = new TestFeatureRegistry(txtFile.toString());
   }
   
   private Uri expectResource(String content) {