Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace direct usage of ClassLoader with ResourcesLoader #2059

Merged
merged 6 commits into from
Aug 21, 2023

Conversation

riccardobl
Copy link
Member

To enhance compatibility with JVMs that deviate from standard norms and may lack complete or partial support for classloaders (such as teavm), this pull request introduces a new encapsulating class called ResourcesLoader. Functioning analogously to BufferAllocators, this addition empowers developers to specify a personalized implementation. The default implementation, named ResourcesLoaderJImpl, operates by interfacing with the classloader.

Throughout the core, all instances where classloaders were used have been substituted with the utilization of ResourcesLoader, excluding specific parts in GL debug classes and jme3-networking which involve proxies and might need more careful consideration.

The methods within AssetManager that depend on ClassLoader usage have been deprecated and could potentially be marked for removal, as they are not utilized by the engine itself.

…or deprecate direct usage of class loaders from the engine
@Ali-RS
Copy link
Member

Ali-RS commented Aug 10, 2023

Do you have an implementation of ResourceLoader that is compatible with Teavm?

@riccardobl
Copy link
Member Author

riccardobl commented Aug 10, 2023

Do you have an implementation of ResourceLoader that is compatible with Teavm?

Yes, the resource loader for teavm loads from the site root

package com.jme3.web.filesystem;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.teavm.jso.browser.Window;

import com.jme3.util.res.ResourcesLoaderImpl;

public class WebResourceLoaderImpl implements ResourcesLoaderImpl {
    private static final Logger logger = Logger.getLogger(WebResourceLoaderImpl.class.getName());

    private String getFullPath(Class<?> clazz, String path) {
        String resourcePath = path;
        if (clazz != null) {
            String className = clazz.getName();
            String classPath = className.replace('.', '/') + ".class";
            classPath = classPath.substring(0, classPath.lastIndexOf('/'));
            resourcePath = classPath + "/" + path;
        }
        try {
            URL baseURL = new URL(Window.current().getLocation().getFullURL());
            resourcePath = new URL(baseURL, resourcePath).toString();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return resourcePath;
    }

    @Override
    public URL getResource(String path, Class<?> clazz) {
        path = getFullPath(clazz, path);
        logger.log(Level.FINE, "Fetch resource {0}", path);
        try {
            URL url = new URL(path);
            return url;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public InputStream getResourceAsStream(String path, Class<?> clazz) {
        path = getFullPath(clazz, path);
        logger.log(Level.FINE, "Fetch resource as stream {0}", path);
        try {
            URL url = new URL(path);
            InputStream is = url.openStream();
            return is;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Enumeration<URL> getResources(String path, Class<?> clazz) throws IOException {
        List<URL> urls = new ArrayList<URL>();
        try {
            String indexPath = "/index.dat";
            logger.log(Level.FINE, "Fetch resources index {0}", indexPath);
            URL resourcesIndex = getResource(indexPath, null);
            ByteArrayOutputStream resourcesBo = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int read = 0;
            InputStream is = resourcesIndex.openStream();
            while ((read = is.read(buffer)) > 0) {
                resourcesBo.write(buffer, 0, read);
            }
            is.close();
            logger.log(Level.FINE, "Listing resources in {0}", path);
            String resources[] = new String(resourcesBo.toByteArray(), Charset.forName("UTF-8")).split("\n");
            for (String resource : resources) {
                resource = resource.trim();
                if (resource.length() > 0 && resource.startsWith(path)) {
                    URL url = getResource(resource, null);
                    urls.add(url);
                }
            }
            logger.log(Level.FINE, "Found {0} resources in {1}", new Object[] { urls.size(), path });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.enumeration(urls);
    }

}

@MeFisto94
Copy link
Member

When reviewing, I've asked myself what the actual problem/purpose here is and to me I could come up with 2-3 actual different use-cases:

  1. We seem to have quite some code that loads ominous .cfg files and by doing so, it doesn't even leverage the asset manager but uses class.getResourceAsStream(). This in itself may be questionable, because you have a config file, but can maybe hardly overwrite it? And if you're supposed to override it, why can't we use the AssetManager for such, perspectively?
  2. There are some cases that have a legitimate class loader, especially around the Savable interface. In order to properly deserialize, we need class instances and it looks like the class loader just tries to locate and instantiate those (instead of manually registering implementations or Class<?>s.
  3. Then there's the ClassLoader AssetLocator, probably the default for DesktopAssetManager, but do we really need to replace it with a ResourceLoader, if we could just swap in another AssetLocator?

So basically I just have the gut feeling that we could solve each of those even better, given good design/refactorings.

@riccardobl
Copy link
Member Author

riccardobl commented Aug 12, 2023

We seem to have quite some code that loads ominous .cfg files and by doing so, it doesn't even leverage the asset manager but uses class.getResourceAsStream(). This in itself may be questionable, because you have a config file, but can maybe hardly overwrite it? And if you're supposed to override it, why can't we use the AssetManager for such, perspectively?

These files are used to initialize the assetManager , so it doesn't exist yet at that point. Also it is a good thing they don't use the asset manage features, because they aren't assets, they don't need caching or to be overwritten by things in the asset path.

2. There are some cases that have a legitimate class loader, especially around the Savable interface. In order to properly deserialize, we need class instances and it looks like the class loader just tries to locate and instantiate those (instead of manually registering implementations or Class<?>s.

This change is only related to resources in the classpath, not classes.

Then there's the ClassLoader AssetLocator, probably the default for DesktopAssetManager, but do we really need to replace it with a ResourceLoader, if we could just swap in another AssetLocator?

If the problem was only with the asset locator, i would have written a new one of my teavm implementation, but this affect various parts of the engine outside of the asset locator and the side effect of this patch is that the default classpath locator becomes automatically compatible.

So basically I just have the gut feeling that we could solve each of those even better, given good design/refactorings.

Maybe, but redesigning all this would take way too much time vs this simple patch that doesn't break anything.
And i don't think the redesign would look much better, you would need to initialize the asset locator way sooner in an hardcoded "default" state and then replace the configuration with the content of the cfg file. And you would have to deal with the risk of having assets replacing important configuration files

@riccardobl riccardobl merged commit f915e56 into jMonkeyEngine:master Aug 21, 2023
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants