Synthesize missing directories in JAR files for entry-related methods. (FELIX-1210)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@939795 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/EntryFilterEnumeration.java b/framework/src/main/java/org/apache/felix/framework/EntryFilterEnumeration.java
new file mode 100644
index 0000000..ae5f8aa
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/EntryFilterEnumeration.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import org.apache.felix.framework.capabilityset.SimpleFilter;
+import org.apache.felix.framework.resolver.Module;
+
+class EntryFilterEnumeration implements Enumeration
+{
+    private final BundleImpl m_bundle;
+    private final List<Enumeration> m_enumerations;
+    private final List<Module> m_modules;
+    private int m_moduleIndex = 0;
+    private final String m_path;
+    private final List<String> m_filePattern;
+    private final boolean m_recurse;
+    private final boolean m_isURLValues;
+    private final Set<String> m_dirEntries = new HashSet();
+    private final List<Object> m_nextEntries = new ArrayList(2);
+
+    public EntryFilterEnumeration(
+        BundleImpl bundle, boolean includeFragments, String path,
+        String filePattern, boolean recurse, boolean isURLValues)
+    {
+        m_bundle = bundle;
+        Module bundleModule = m_bundle.getCurrentModule();
+        List<Module> fragmentModules = ((ModuleImpl) bundleModule).getFragments();
+        if (includeFragments && (fragmentModules != null))
+        {
+            m_modules = new ArrayList(fragmentModules.size() + 1);
+            m_modules.addAll(fragmentModules);
+        }
+        else
+        {
+            m_modules = new ArrayList(1);
+        }
+        m_modules.add(0, bundleModule);
+        m_enumerations = new ArrayList(m_modules.size());
+        for (int i = 0; i < m_modules.size(); i++)
+        {
+            m_enumerations.add(m_modules.get(i).getContent() != null ?
+                m_modules.get(i).getContent().getEntries() : null);
+        }
+        m_recurse = recurse;
+        m_isURLValues = isURLValues;
+
+        // Sanity check the parameters.
+        if (path == null)
+        {
+            throw new IllegalArgumentException("The path for findEntries() cannot be null.");
+        }
+        // Strip leading '/' if present.
+        if ((path.length() > 0) && (path.charAt(0) == '/'))
+        {
+            path = path.substring(1);
+        }
+        // Add a '/' to the end if not present.
+        if ((path.length() > 0) && (path.charAt(path.length() - 1) != '/'))
+        {
+            path = path + "/";
+        }
+        m_path = path;
+
+        // File pattern defaults to "*" if not specified.
+        filePattern = (filePattern == null) ? "*" : filePattern;
+
+        m_filePattern = SimpleFilter.parseSubstring(filePattern);
+
+        findNext();
+    }
+
+    public synchronized boolean hasMoreElements()
+    {
+        return (m_nextEntries.size() != 0);
+    }
+
+    public synchronized Object nextElement()
+    {
+        if (m_nextEntries.size() == 0)
+        {
+            throw new NoSuchElementException("No more entries.");
+        }
+        Object last = m_nextEntries.remove(0);
+        findNext();
+        return last;
+    }
+
+    private void findNext()
+    {
+        // This method filters the content entry enumeration, such that
+        // it only displays the contents of the directory specified by
+        // the path argument either recursively or not; much like using
+        // "ls -R" or "ls" to list the contents of a directory, respectively.
+        if (m_enumerations == null)
+        {
+            return;
+        }
+        while ((m_moduleIndex < m_enumerations.size()) && (m_nextEntries.size() == 0))
+        {
+            while (m_enumerations.get(m_moduleIndex) != null
+                && m_enumerations.get(m_moduleIndex).hasMoreElements()
+                && m_nextEntries.size() == 0)
+            {
+                // Get the current entry to determine if it should be filtered or not.
+                String entryName = (String) m_enumerations.get(m_moduleIndex).nextElement();
+                // Check to see if the current entry is a descendent of the specified path.
+                if (!entryName.equals(m_path) && entryName.startsWith(m_path))
+                {
+                    // Cached entry URL. If we are returning URLs, we use this
+                    // cached URL to avoid doing multiple URL lookups from a module
+                    // when synthesizing directory URLs.
+                    URL entryURL = null;
+
+                    // If the current entry is in a subdirectory of the specified path,
+                    // get the index of the slash character.
+                    int dirSlashIdx = entryName.indexOf('/', m_path.length());
+
+                    // JAR files are supposed to contain entries for directories,
+                    // but not all do. So calculate the directory for this entry
+                    // and see if we've already seen an entry for the directory.
+                    // If not, synthesize an entry for the directory. If we are
+                    // doing a recursive match, we need to synthesize each matching
+                    // subdirectory of the entry.
+                    if (dirSlashIdx >= 0)
+                    {
+                        // Start synthesizing directories for the current entry
+                        // at the subdirectory after the initial path.
+                        int subDirSlashIdx = dirSlashIdx;
+                        String dir;
+                        do
+                        {
+                            // Calculate the subdirectory name.
+                            dir = entryName.substring(0, subDirSlashIdx + 1);
+                            // If we have not seen this directory before, then record
+                            // it and potentially synthesize an entry for it.
+                            if (!m_dirEntries.contains(dir))
+                            {
+                                // Record it.
+                                m_dirEntries.add(dir);
+                                // If the entry is actually a directory entry (i.e.,
+                                // it ends with a slash), then we don't need to
+                                // synthesize an entry since it exists; otherwise,
+                                // synthesize an entry if it matches the file pattern.
+                                if (entryName.length() != (subDirSlashIdx + 1))
+                                {
+                                    // See if the file pattern matches the last
+                                    // element of the path.
+                                    if (SimpleFilter.compareSubstring(
+                                        m_filePattern, getLastPathElement(dir)))
+                                    {
+                                        if (m_isURLValues)
+                                        {
+                                            entryURL = (entryURL == null)
+                                                ? m_modules.get(m_moduleIndex).getEntry(entryName)
+                                                : entryURL;
+                                            try
+                                            {
+                                                m_nextEntries.add(new URL(entryURL, "/" + dir));
+                                            }
+                                            catch (MalformedURLException ex)
+                                            {
+                                            }
+                                        }
+                                        else
+                                        {
+                                            m_nextEntries.add(dir);
+                                        }
+                                    }
+                                }
+                            }
+                            // Now prepare to synthesize the next subdirectory
+                            // if we are matching recursively.
+                            subDirSlashIdx = entryName.indexOf('/', dir.length());
+                        }
+                        while (m_recurse && (subDirSlashIdx >= 0));
+                    }
+
+                    // Now we actually need to check if the current entry itself should
+                    // be filtered or not. If we are recursive or the current entry
+                    // is a child (not a grandchild) of the initial path, then we need
+                    // to check if it matches the file pattern.
+                    if (m_recurse || (dirSlashIdx < 0) || (dirSlashIdx == entryName.length() - 1))
+                    {
+                        // See if the file pattern matches the last element of the path.
+                        if (SimpleFilter.compareSubstring(
+                            m_filePattern, getLastPathElement(entryName)))
+                        {
+                            if (m_isURLValues)
+                            {
+                                entryURL = (entryURL == null)
+                                    ? m_modules.get(m_moduleIndex).getEntry(entryName)
+                                    : entryURL;
+                                m_nextEntries.add(entryURL);
+                            }
+                            else
+                            {
+                                m_nextEntries.add(entryName);
+                            }
+                        }
+                    }
+                }
+            }
+            if (m_nextEntries.size() == 0)
+            {
+                m_moduleIndex++;
+            }
+        }
+    }
+
+    private static String getLastPathElement(String entryName)
+    {
+        int endIdx = (entryName.charAt(entryName.length() - 1) == '/')
+            ? entryName.length() - 1
+            : entryName.length();
+        int startIdx = (entryName.charAt(entryName.length() - 1) == '/')
+            ? entryName.lastIndexOf('/', endIdx - 1) + 1
+            : entryName.lastIndexOf('/', endIdx) + 1;
+        return entryName.substring(startIdx, endIdx);
+    }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 590cc56..8134ed4 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -1466,7 +1466,34 @@
         {
             throw new IllegalStateException("The bundle is uninstalled.");
         }
-        return bundle.getCurrentModule().getEntry(name);
+
+        URL url = bundle.getCurrentModule().getEntry(name);
+
+        // Some JAR files do not contain directory entries, so if
+        // the entry wasn't found and is a directory, scan the entries
+        // to see if we should synthesize an entry for it.
+        if ((url == null) && name.endsWith("/") && !name.equals("/"))
+        {
+            // Use the entry filter enumeration to search the bundle content
+            // recursively for matching entries and return URLs to them.
+            Enumeration enumeration =
+                new EntryFilterEnumeration(bundle, false, name, "*", true, true);
+            // If the enumeration has elements, then that means we need
+            // to synthesize the directory entry.
+            if (enumeration.hasMoreElements())
+            {
+                URL entryURL = (URL) enumeration.nextElement();
+                try
+                {
+                    url = new URL(entryURL, ((name.charAt(0) == '/') ? name : "/" + name));
+                }
+                catch (MalformedURLException ex)
+                {
+                    url = null;
+                }
+            }
+        }
+        return url;
     }
 
     /**
@@ -1481,14 +1508,15 @@
 
         // Get the entry enumeration from the module content and
         // create a wrapper enumeration to filter it.
-        Enumeration enumeration = new GetEntryPathsEnumeration(bundle, path);
+        Enumeration enumeration =
+            new EntryFilterEnumeration(bundle, false, path, "*", false, false);
 
         // Return the enumeration if it has elements.
         return (!enumeration.hasMoreElements()) ? null : enumeration;
     }
 
     /**
-     * Implementation for findEntries().
+     * Implementation for Bundle.findEntries().
     **/
     Enumeration findBundleEntries(
         BundleImpl bundle, String path, String filePattern, boolean recurse)
@@ -1499,7 +1527,7 @@
         // Get the entry enumeration from the module content and
         // create a wrapper enumeration to filter it.
         Enumeration enumeration =
-            new FindEntriesEnumeration(bundle, path, filePattern, recurse);
+            new EntryFilterEnumeration(bundle, true, path, filePattern, recurse, true);
 
         // Return the enumeration if it has elements.
         return (!enumeration.hasMoreElements()) ? null : enumeration;
diff --git a/framework/src/main/java/org/apache/felix/framework/FindEntriesEnumeration.java b/framework/src/main/java/org/apache/felix/framework/FindEntriesEnumeration.java
deleted file mode 100644
index b0b13f9..0000000
--- a/framework/src/main/java/org/apache/felix/framework/FindEntriesEnumeration.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.framework;
-
-import java.util.*;
-import org.apache.felix.framework.capabilityset.SimpleFilter;
-import org.apache.felix.framework.resolver.Module;
-
-
-class FindEntriesEnumeration implements Enumeration
-{
-    private final BundleImpl m_bundle;
-    private final List<Enumeration> m_enumerations;
-    private final List<Module> m_modules;
-    private int m_moduleIndex = 0;
-    private final String m_path;
-    private final List<String> m_filePattern;
-    private final boolean m_recurse;
-    private Object m_next = null;
-
-    public FindEntriesEnumeration(
-        BundleImpl bundle, String path, String filePattern, boolean recurse)
-    {
-        m_bundle = bundle;
-        Module bundleModule = m_bundle.getCurrentModule();
-        List<Module> fragmentModules = ((ModuleImpl) bundleModule).getFragments();
-        if (fragmentModules == null)
-        {
-            fragmentModules = new ArrayList<Module>(0);
-        }
-        m_modules = new ArrayList<Module>(fragmentModules.size() + 1);
-        m_modules.add(bundleModule);
-        m_modules.addAll(fragmentModules);
-        m_enumerations = new ArrayList<Enumeration>(m_modules.size());
-        for (int i = 0; i < m_modules.size(); i++)
-        {
-            m_enumerations.add(m_modules.get(i).getContent() != null ?
-                m_modules.get(i).getContent().getEntries() : null);
-        }
-        m_recurse = recurse;
-
-        // Sanity check the parameters.
-        if (path == null)
-        {
-            throw new IllegalArgumentException("The path for findEntries() cannot be null.");
-        }
-        // Strip leading '/' if present.
-        if ((path.length() > 0) && (path.charAt(0) == '/'))
-        {
-            path = path.substring(1);
-        }
-        // Add a '/' to the end if not present.
-        if ((path.length() > 0) && (path.charAt(path.length() - 1) != '/'))
-        {
-            path = path + "/";
-        }
-        m_path = path;
-
-        // File pattern defaults to "*" if not specified.
-        filePattern = (filePattern == null) ? "*" : filePattern;
-
-        m_filePattern = SimpleFilter.parseSubstring(filePattern);
-
-        m_next = findNext();
-    }
-
-    public synchronized boolean hasMoreElements()
-    {
-        return (m_next != null);
-    }
-
-    public synchronized Object nextElement()
-    {
-        if (m_next == null)
-        {
-            throw new NoSuchElementException("No more entry paths.");
-        }
-        Object last = m_next;
-        m_next = findNext();
-        return last;
-    }
-
-    private Object findNext()
-    {
-        // This method filters the content entry enumeration, such that
-        // it only displays the contents of the directory specified by
-        // the path argument either recursively or not; much like using
-        // "ls -R" or "ls" to list the contents of a directory, respectively.
-        if (m_enumerations == null)
-        {
-            return null;
-        }
-        while (m_moduleIndex < m_enumerations.size())
-        {
-            while (m_enumerations.get(m_moduleIndex) != null
-                &&  m_enumerations.get(m_moduleIndex).hasMoreElements())
-            {
-                // Get the next entry name.
-                String entryName = (String) m_enumerations.get(m_moduleIndex).nextElement();
-                // Check to see if it is a descendent of the specified path.
-                if (!entryName.equals(m_path) && entryName.startsWith(m_path))
-                {
-                    // If this is recursive search, then try to match any
-                    // entry path that starts with the specified path;
-                    // otherwise, only try to match children of the specified
-                    // path and not any grandchild. This code uses the knowledge
-                    // that content entries corresponding to directories end in '/'.
-                    int idx = entryName.indexOf('/', m_path.length());
-                    if (m_recurse || (idx < 0) || (idx == (entryName.length() - 1)))
-                    {
-                        // Get the last element of the entry path, not including
-                        // the '/' if it is a directory.
-                        int endIdx = (entryName.charAt(entryName.length() - 1) == '/')
-                            ? entryName.length() - 1
-                            : entryName.length();
-                        int startIdx = (entryName.charAt(entryName.length() - 1) == '/')
-                            ? entryName.lastIndexOf('/', endIdx - 1) + 1
-                            : entryName.lastIndexOf('/', endIdx) + 1;
-                        String lastElement = entryName.substring(startIdx, endIdx);
-
-                        // See if the file pattern matches the last element of the path.
-                        if (SimpleFilter.compareSubstring(m_filePattern, lastElement))
-                        {
-                            // Convert entry name into an entry URL.
-                            return m_modules.get(m_moduleIndex).getEntry(entryName);
-                        }
-                    }
-                }
-            }
-            m_moduleIndex++;
-        }
-
-        return null;
-    }
-}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/GetEntryPathsEnumeration.java b/framework/src/main/java/org/apache/felix/framework/GetEntryPathsEnumeration.java
deleted file mode 100644
index 5398708..0000000
--- a/framework/src/main/java/org/apache/felix/framework/GetEntryPathsEnumeration.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.framework;
-
-import java.util.Enumeration;
-import java.util.NoSuchElementException;
-
-class GetEntryPathsEnumeration implements Enumeration
-{
-    private final BundleImpl m_bundle;
-    private final Enumeration m_enumeration;
-    private final String m_path;
-    private Object m_next = null;
-
-    public GetEntryPathsEnumeration(BundleImpl bundle, String path)
-    {
-        m_bundle = bundle;
-        m_enumeration = m_bundle.getCurrentModule().getContent().getEntries();
-
-        // Sanity check the parameters.
-        if (path == null)
-        {
-            throw new IllegalArgumentException("The path for findEntries() cannot be null.");
-        }
-        // Strip leading '/' if present.
-        if ((path.length() > 0) && (path.charAt(0) == '/'))
-        {
-            path = path.substring(1);
-        }
-        // Add a '/' to the end if not present.
-        if ((path.length() > 0) && (path.charAt(path.length() - 1) != '/'))
-        {
-            path = path + "/";
-        }
-        m_path = path;
-
-        m_next = findNext();
-    }
-
-    public synchronized boolean hasMoreElements()
-    {
-        return (m_next != null);
-    }
-
-    public synchronized Object nextElement()
-    {
-        if (m_next == null)
-        {
-            throw new NoSuchElementException("No more entry paths.");
-        }
-        Object last = m_next;
-        m_next = findNext();
-        return last;
-    }
-
-    private Object findNext()
-    {
-        // This method filters the content entry enumeration, such that
-        // it only displays the contents of the directory specified by
-        // the path argument; much like using "ls" to list the contents
-        // of a directory.
-        while (m_enumeration.hasMoreElements())
-        {
-            // Get the next entry name.
-            String entryName = (String) m_enumeration.nextElement();
-            // Check to see if it is a descendent of the specified path.
-            if (!entryName.equals(m_path) && entryName.startsWith(m_path))
-            {
-                // Verify that it is a child of the path and not a
-                // grandchild by examining its remaining path length.
-                // This code uses the knowledge that content entries
-                // corresponding to directories end in '/'. It checks
-                // to see if the next occurrence of '/' is also the
-                // end of the string, which means that this entry
-                // represents a child directory of the path.
-                int idx = entryName.indexOf('/', m_path.length());
-                if ((idx < 0) || (idx == (entryName.length() - 1)))
-                {
-                    return entryName;
-                }
-            }
-        }
-        return null;
-    }
-}
\ No newline at end of file