Skip to content

Commit

Permalink
Rework to use reflector class
Browse files Browse the repository at this point in the history
  • Loading branch information
Thrameos committed Nov 28, 2024
1 parent 4e63379 commit 4344295
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 87 deletions.
6 changes: 3 additions & 3 deletions jpype/_classpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def addClassPath(path1: typing.Union[str, _os.PathLike]) -> None:
path1 = path2.joinpath(path1)

# If the JVM is already started then we will have to load the paths
# immediately into the DynamicClassLoader
# immediately into the JPypeClassLoader
if _jpype.isStarted():
Paths = _jpype.JClass('java.nio.file.Paths')
JContext = _jpype.JClass('org.jpype.JPypeContext')
Expand All @@ -62,9 +62,9 @@ def addClassPath(path1: typing.Union[str, _os.PathLike]) -> None:
if len(paths) == 0:
return
for path in paths:
classLoader.addFile(Paths.get(str(path)))
classLoader.addPath(Paths.get(str(path)))

Check warning on line 65 in jpype/_classpath.py

View check run for this annotation

Codecov / codecov/patch

jpype/_classpath.py#L65

Added line #L65 was not covered by tests
else:
classLoader.addFile(Paths.get(str(path1)))
classLoader.addPath(Paths.get(str(path1)))
_CLASSPATHS.append(path1)


Expand Down
2 changes: 1 addition & 1 deletion jpype/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def startJVM(
# ok, setup the jpype system classloader and add to the path after startup
# this guarentees all classes have the same permissions as they did in the past
extra_jvm_args += [
'-Djava.system.class.loader=org.jpype.classloader.DynamicClassLoader',
'-Djava.system.class.loader=org.jpype.JPypeClassLoader',
'-Djava.class.path=%s'%support_lib,
'-Djpype.class.path=%s'%java_class_path,
'-Xshare:off'
Expand Down
2 changes: 1 addition & 1 deletion native/common/jp_classloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ JPClassLoader::JPClassLoader(JPJavaFrame& frame)
m_SystemClassLoader = JPObjectRef(frame,
frame.CallStaticObjectMethodA(classLoaderClass, getSystemClassLoader, nullptr));

jclass dynamicLoaderClass = frame.getEnv()->FindClass("org/jpype/classloader/DynamicClassLoader");
jclass dynamicLoaderClass = frame.getEnv()->FindClass("org/jpype/JPypeClassLoader");
if (dynamicLoaderClass != nullptr)
{
// Use the one in place already
Expand Down
2 changes: 1 addition & 1 deletion native/java/manifest.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Manifest-Version: 1.0
Premain-Class: org.jpype.agent.JPypeAgent

Multi-Release: true
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.jpype.classloader;
package org.jpype;

import java.io.ByteArrayOutputStream;
import java.io.File;
Expand All @@ -7,6 +7,7 @@
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
Expand All @@ -22,21 +23,24 @@
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;

public class DynamicClassLoader extends URLClassLoader
public class JPypeClassLoader extends URLClassLoader
{

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

public DynamicClassLoader(ClassLoader parent)
public JPypeClassLoader(ClassLoader parent)
{
super(launch(), parent);
super(initial(), parent);
}

public int getCode()
{
return code;
}

/**
Expand All @@ -48,7 +52,7 @@ public DynamicClassLoader(ClassLoader parent)
*
* @return
*/
private static URL[] launch()
private static URL[] initial()
{
String cp = System.getProperty("jpype.class.path");
if (cp == null)
Expand Down Expand Up @@ -91,23 +95,17 @@ private void appendToClassPathForInstrumentation(String path) throws Throwable
addURL(Paths.get(path).toAbsolutePath().toUri().toURL());
}

public int getCode()
{
return loaders.hashCode();
}

/**
* Add a set of jars to the classpath.
*
* @param root
* @param glob
* @throws IOException
*/
public void addFiles(Path root, String glob) throws IOException
public void addPaths(Path root, String glob) throws IOException
{
final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);

List<URL> urls = new LinkedList<>();
Files.walkFileTree(root, new SimpleFileVisitor<Path>()
{

Expand All @@ -118,7 +116,7 @@ public FileVisitResult visitFile(Path path,
if (pathMatcher.matches(root.relativize(path)))
{
URL url = path.toUri().toURL();
urls.add(url);
addURL(url);
}
return FileVisitResult.CONTINUE;
}
Expand All @@ -131,23 +129,15 @@ public FileVisitResult visitFileFailed(Path file, IOException exc)
}
});

loaders.add(new URLClassLoader(urls.toArray(new URL[0])));
}

public void addFile(Path path) throws FileNotFoundException
public void addPath(Path path) throws FileNotFoundException
{
try
{
if (!Files.exists(path))
throw new FileNotFoundException(path.toString());
URL[] urls = new URL[]
{
path.toUri().toURL()
};
loaders.add(new URLClassLoader(urls));

// Scan the file for directory entries
this.scanJar(path);
this.addURL(path.toUri().toURL());
} catch (MalformedURLException ex)
{
// This should never happen
Expand Down Expand Up @@ -186,26 +176,14 @@ public Class findClass(String name) throws ClassNotFoundException, ClassFormatEr

buffer.flush();
byte[] data = buffer.toByteArray();
return defineClass(name, data, 0, data.length);
return defineClass(null, data, 0, data.length);
}
} catch (IOException ex)
{
}
throw new ClassNotFoundException(name);
}

@Override
public URL getResource(String name)
{
// Search our parent
URL url = this.getParent().getResource(name);
if (url != null)
return url;

// Otherwise search locally
return findResource(name);
}

@Override
public URL findResource(String name)
{
Expand All @@ -214,31 +192,23 @@ public URL findResource(String name)
if (url != null)
return url;

// Use one of the subs
for (URLClassLoader cl : this.loaders)
{
url = cl.findResource(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);

// We have some resource which must be sourced to a particular class loader
if (name.startsWith("org/jpype/"))
return getResource("META-INF/versions/0/" + name);
return null;
}

@Override
public Enumeration<URL> getResources(String name) throws IOException
public Enumeration<URL> findResources(String name) throws IOException
{
ArrayList<URL> out = new ArrayList<>();
out.addAll(Collections.list(getParent().getResources(name)));
out.addAll(Collections.list(super.getResources(name)));
for (URLClassLoader cl : this.loaders)
{
out.addAll(Collections.list(cl.findResources(name)));
}
out.addAll(Collections.list(super.findResources(name)));
// Both with and without / should generate the same result
if (name.endsWith("/"))
name = name.substring(0, name.length() - 1);
Expand All @@ -254,25 +224,45 @@ public void addResource(String name, URL url)
this.map.get(name).add(url);
}

@Override
public void addURL(URL url)
{
// Mark our cache as dirty
code = code * 98745623 + url.hashCode();
super.addURL(url);
Path path;
try
{
path = Paths.get(url.toURI());
} catch (URISyntaxException ex)
{
return;
}

// Scan for missing resources
scanJar(path);
}

/**
* 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
* @param path
*/
public void scanJar(Path p1)
void scanJar(Path path)
{
if (!Files.exists(p1))
if (!Files.exists(path))
return;
if (Files.isDirectory(p1))
if (Files.isDirectory(path))
return;
try (JarFile jf = new JarFile(p1.toFile()))

try (JarFile jf = new JarFile(path.toFile()))
{
Enumeration<JarEntry> entries = jf.entries();
URI abs = p1.toAbsolutePath().toUri();
URI abs = path.toAbsolutePath().toUri();
Set urls = new java.util.HashSet();
while (entries.hasMoreElements())
{
Expand Down
33 changes: 19 additions & 14 deletions native/java/org/jpype/JPypeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.jpype.classloader.DynamicClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jpype.manager.TypeFactory;
import org.jpype.manager.TypeFactoryNative;
import org.jpype.manager.TypeManager;
Expand Down Expand Up @@ -77,11 +78,12 @@ public class JPypeContext
private long context;
private TypeFactory typeFactory;
private TypeManager typeManager;
private DynamicClassLoader classLoader;
private JPypeClassLoader classLoader;
private final AtomicInteger shutdownFlag = new AtomicInteger();
private final List<Thread> shutdownHooks = new ArrayList<>();
private final List<Runnable> postHooks = new ArrayList<>();
public static boolean freeResources = true;
public JPypeReflector reflector = null;

static public JPypeContext getInstance()
{
Expand All @@ -92,20 +94,32 @@ static public JPypeContext getInstance()
* Start the JPype system.
*
* @param context is the C++ portion of the context.
* @param bootLoader is the classloader holding JPype resources.
* @param loader is the classloader holding JPype resources.
* @return the created context.
*/
static JPypeContext createContext(long context, ClassLoader bootLoader, String nativeLib, boolean interrupt)
static JPypeContext createContext(long context, ClassLoader loader, String nativeLib, boolean interrupt)
{
if (nativeLib != null)
{
System.load(nativeLib);
}
INSTANCE.context = context;
INSTANCE.classLoader = (DynamicClassLoader) bootLoader;
INSTANCE.classLoader = (JPypeClassLoader) loader;
INSTANCE.typeFactory = new TypeFactoryNative();
INSTANCE.typeManager = new TypeManager(context, INSTANCE.typeFactory);
INSTANCE.initialize(interrupt);

try
{
INSTANCE.reflector = (JPypeReflector) Class.forName("org.jpype.Reflector0", true, loader)
.getConstructor()
.newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException
| InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException ex)
{
System.err.println("Unable to create reflector "+ ex);
}

scanExistingJars();
return INSTANCE;
Expand Down Expand Up @@ -460,15 +474,6 @@ public boolean isShutdown()
return shutdownFlag.get() > 0;
}

// public void incrementProxy()
// {
// proxyCount.incrementAndGet();
// }
//
// public void decrementProxy()
// {
// proxyCount.decrementAndGet();
// }
/**
* Clear the current interrupt.
*
Expand Down
27 changes: 27 additions & 0 deletions native/java/org/jpype/JPypeReflector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.jpype;

import java.lang.reflect.Method;

/**
*
* @author nelson85
*/
public interface JPypeReflector
{
/**
* Call a method using reflection.
*
* This method creates a stackframe so that caller sensitive methods will
* execute properly.
*
* @param method is the method to call.
* @param obj is the object to operate on, it will be null if the method is
* static.
* @param args the arguments to method.
* @return the object that results form the invocation.
* @throws java.lang.Throwable throws whatever type the called method
* produces.
*/
public Object callMethod(Method method, Object obj, Object[] args)
throws Throwable;
}
4 changes: 2 additions & 2 deletions native/java/org/jpype/html/Html.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public static List<Attr> parseAttributes(Document doc, String str)

static
{
try (InputStream is = JPypeContext.getInstance().getClass().getClassLoader()
.getResourceAsStream("org/jpype/html/entities.txt");
ClassLoader cl = ClassLoader.getSystemClassLoader();
try (InputStream is = cl.getResourceAsStream("org/jpype/html/entities.txt");
InputStreamReader isr = new InputStreamReader(is);
BufferedReader rd = new BufferedReader(isr))
{
Expand Down
Loading

0 comments on commit 4344295

Please sign in to comment.