Skip to content

Commit

Permalink
Fix for missing directory entries in jar files.
Browse files Browse the repository at this point in the history
  • Loading branch information
Thrameos committed Dec 3, 2020
1 parent 8cc805d commit fa487cc
Show file tree
Hide file tree
Showing 19 changed files with 132 additions and 4 deletions.
4 changes: 4 additions & 0 deletions doc/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ This changelog *only* contains changes from the *first* pypi release (0.5.4.3) o

Latest Changes:
- **1.2.1_dev0 - 2020-11-29**

- JPype scans jar files and rebuilding missing directories to allow imports
from stripped and obfuscated jar files.

- **1.2.0 - 2020-11-29**

- Added builds for Python 3.9. Python 3.9 on Windows is currently failing
Expand Down
18 changes: 17 additions & 1 deletion native/java/org/jpype/JPypeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
**************************************************************************** */
package org.jpype;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.Buffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -104,6 +108,8 @@ static JPypeContext createContext(long context, ClassLoader bootLoader, String n
INSTANCE.typeFactory = new TypeFactoryNative();
INSTANCE.typeManager = new TypeManager(context, INSTANCE.typeFactory);
INSTANCE.initialize(interrupt);

scanExistingJars();
return INSTANCE;
}

Expand Down Expand Up @@ -487,7 +493,7 @@ public static void clearInterrupt(boolean x) throws InterruptedException
{
// Clear the flag in C++
JPypeSignal.acknowledgePy();

// Clear the flag in Java
Thread.sleep(1);
}
Expand Down Expand Up @@ -637,4 +643,14 @@ public void newWrapper(long l)
}
}

private static void scanExistingJars()
{
// Scan existing jars for missing directory entries
String[] paths = System.getProperty("java.class.path").split(File.pathSeparator);
for (String path : paths)
{
INSTANCE.classLoader.scanJar(Paths.get(path));
}
}

}
94 changes: 93 additions & 1 deletion native/java/org/jpype/classloader/DynamicClassLoader.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jpype.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -13,24 +14,32 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DynamicClassLoader extends ClassLoader
{

List<URLClassLoader> loaders = new LinkedList<>();
HashMap<String, ArrayList<URL>> map = new HashMap<>();

public DynamicClassLoader(ClassLoader parent)
{
super(parent);
}

public int getCode()
{
return loaders.hashCode();
Expand Down Expand Up @@ -85,6 +94,9 @@ public void addFile(Path path) throws FileNotFoundException
path.toUri().toURL()
};
loaders.add(new URLClassLoader(urls));

// Scan the file for directory entries
this.scanJar(path);
} catch (MalformedURLException ex)
{
// This should never happen
Expand Down Expand Up @@ -143,6 +155,11 @@ public URL getResource(String name)
if (url != null)
return url;
}
// Both with and without / should generate the same result
if (name.endsWith("/"))
name = name.substring(0, name.length() - 1);
if (map.containsKey(name))
return map.get(name).get(0);
return null;
}

Expand All @@ -157,6 +174,81 @@ public Enumeration<URL> getResources(String name) throws IOException
urls = cl.findResources(name);
out.addAll(Collections.list(urls));
}
// Both with and without / should generate the same result
if (name.endsWith("/"))
name = name.substring(0, name.length() - 1);
if (map.containsKey(name))
out.addAll(map.get(name));
return Collections.enumeration(out);
}

public void addResource(String name, URL url)
{
if (!this.map.containsKey(name))
this.map.put(name, new ArrayList<>());
this.map.get(name).add(url);
}

/**
* Recreate missing directory entries for Jars that lack indexing.
*
* Some jar files are missing the directory entries that prevents use from
* properly importing their contents. This procedure scans a jar file when
* loaded to build missing directories.
*
* @param p1
*/
public void scanJar(Path p1)
{
if (!Files.exists(p1))
return;
if (Files.isDirectory(p1))
return;
try ( JarFile jf = new JarFile(p1.toFile()))
{
Enumeration<JarEntry> entries = jf.entries();
Path abs = p1.toAbsolutePath();
Set urls = new java.util.HashSet();
while (entries.hasMoreElements())
{
JarEntry next = entries.nextElement();
String name = next.getName();

// Skip over META-INF
if (name.startsWith("META-INF/"))
continue;

if (next.isDirectory())
{
// If we find a directory entry then the jar has directories already
return;
}

// Split on each separator in the name
int i = 0;
while (true)
{
i = name.indexOf("/", i);
if (i == -1)
break;
String name2 = name.substring(0, i);

i++;

// Already have an entry no problem
if (urls.contains(name2))
continue;

// Add a new entry for the missing directory
String jar = "jar:file:" + abs + "!/" + name2 + "/";
urls.add(name2);
this.addResource(name2, new URL(jar));
}
}
} catch (IOException ex)
{
// Anything goes wrong skip it
}
}

}
1 change: 1 addition & 0 deletions project/jars/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This directory contains patterns for constructing special jar files used in testing.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions project/jars/missing/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
javac --release 7 -d classes src/org/jpype/missing/*.java
jar --create --file missing.jar -M -C classes META-INF/MANIFEST.MF -C classes org/jpype/missing/Test.class
3 changes: 3 additions & 0 deletions project/jars/missing/classes/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Created-By: 11.0.9.1 (Ubuntu)

6 changes: 6 additions & 0 deletions project/jars/missing/src/org/jpype/missing/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.jpype.missing;

public class Test
{
}

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions resolve.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# This is used to pull dependencies needed for this package

# drill has a bunch of dependencies, so we are going to use ivy to collect it all at once.
wget -nc "https://repo1.maven.org/maven2/org/apache/ivy/ivy/2.4.0/ivy-2.4.0.jar" -P lib
java -jar lib/ivy-2.4.0.jar -ivy ivy.xml -retrieve 'lib/[artifact]-[revision](-[classifier]).[ext]'
wget -nc "https://repo1.maven.org/maven2/org/apache/ivy/ivy/2.5.0/ivy-2.5.0.jar" -P lib
java -jar lib/ivy-2.5.0.jar -ivy ivy.xml -retrieve 'lib/[artifact]-[revision](-[classifier]).[ext]'
Binary file added test/jar/missing.jar
Binary file not shown.
4 changes: 4 additions & 0 deletions test/jpypetest/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ def testAddClassPath(self):
def testStar(self):
import importstar

def testMissing(self):
import org
self.assertTrue("missing" in dir(org.jpype))


@subrun.TestCase
class ImportsBeforeCase(common.unittest.TestCase):
Expand Down

0 comments on commit fa487cc

Please sign in to comment.