How would I make some .jars optional?

I want to make it so that my engine will work without some jars, to a certain point where that jar is needed, and then say something is wrong.

Say, for example, that I need Paulscode Sound System for sound to work, but as long as you don’t use Sound, it won’t throw an error. However, if you try to access sound, it will immediately kill the program, leaving an error.

How would I go about this?

-wes

You can’t really. It would require a lot of messy classloader stuff, and in the end be inefficient and a waste of time.

The best way to get similar behaviour is to separate the sound functions into a sub-library, and then let the user optionally download that sub-library and paulscode if they want to use it.

I’m not suree about the paulscode licenses, but if you are permitted, you could try packaging it in with the sound sub-library, so no-one will notice a difference.

Well I will post a little example that I use. It isn’t quite what you have in mind but could easily be used for that purpose (in fact I have on a few occasions).


/**
 *
 * @author Quew8
 * @param <T>
 */
public class ServiceImplLoader<T extends ServiceImpl> {
    private final ServiceLoader<T> loader;
    private final T[] loadedImpls;
    private final Class<T> servClazz;
    
    public ServiceImplLoader(Class<T> servClazz, ClassLoader classLoader, T... loadedImpls) {
        this.servClazz = servClazz;
        this.loadedImpls = loadedImpls;
        this.loader = ServiceLoader.load(servClazz, classLoader);
    }
    
    public ServiceImplLoader(Class<T> servClazz, URL[] urls, T... loadedImpls) {
        this(servClazz, new URLClassLoader(urls), loadedImpls);
    }
    
    public ServiceImplLoader(Class<T> servClazz, T... loadedImpls) {
        this(servClazz, (ClassLoader) null, loadedImpls);
    }
    
    public T getImplementation() throws NoSuitableImplementationException {
        T topImpl = null;
        ArrayList<T> allImpls = getAllImplementations();
        for(T t: allImpls) {
            if(
                    t.isApplicable() && 
                    (
                        (topImpl == null || topImpl.getPrecedence() == -1 ) || 
                        (t.getPrecedence() < topImpl.getPrecedence() && t.getPrecedence() != -1)
                    )
                    ) {
                topImpl = t;
            }
        }
        if(topImpl != null) {
            return topImpl;
        }
        throw new NoSuitableImplementationException(servClazz, loader);
    }
    
    public T getImplementationNoThrow() {
        try {
            return getImplementation();
        } catch(NoSuitableImplementationException ex) {
            throw new RuntimeException(ex);
        }
    }
    
    public T[] getAllImplementations(T[] dest) {
        return getAllImplementations().toArray(dest);
    }
    
    public ArrayList<T> getAllImplementations() {
        ArrayList<T> impls = new ArrayList<T>();
        for(T t: loader) {
            if(t.isApplicable()) {
                impls.add(t);
            }
        }
        for(T t: loadedImpls) {
            if(t.isApplicable()) {
                impls.add(t);
            }
        }
        return impls;
    }
}

/**
 *
 * @author Quew8
 */
public interface ServiceImpl {
    public boolean isApplicable();
    public int getPrecedence();
}

/**
 *
 * @author Quew8
 */
public class NoSuitableImplementationException extends Exception {
    public <T extends ServiceImpl> NoSuitableImplementationException(Class<T> servClazz, Iterable<T> impls) {
        super(getMsg(servClazz, impls));
    }
    
    private static <T extends ServiceImpl> String getMsg(Class<T> servClazz, Iterable<T> impls) {
        String s = "No suitable implementation found for " + servClazz.getName() + "\nFound Implementations:";
        for(T t: impls) {
            s += "\n    " + t.getClass().getName() + "\n      Applicable: " + t.isApplicable() + "\n      Precedence: " + t.getPrecedence();
        }
        return s;
    }
}

So the ServiceImpl loader finds the most appropriate ServiceImpl in it’s ClassLoader. When you want to make your own service, you create an interface extending ServiceImpl then all of the implementations implement this interface. The precedence thing is slightly stupid here. Essentially the lower the value returned by getPrecedence() in the ServiceImpl the more preferable the implementation is unless it returns -1 which I use for things like no implementations - which you’ll be wanting and I’ll explain later. The URL[] based ServiceImplLoader constructor wants a list of URLs pointing to .jar files however be warned they have to me registered as Services in their manifest files as described here: http://docs.oracle.com/javase/tutorial/ext/basics/spi.html#package-in-jar-files.

Now I consider a no implementation implementation to be an implementation that merely throws UnsupportedOperationException whenever you call one of its methods (other than getPrecedence() and is isApplicable() which should return -1 and true respectively). Generally I have these built into the core application and input them into the ServiceLoader with the list of “loadedImpls” in the constructor. That way even if none of the other Implementations are applicable you can fall back on this and the program will run as long as you don’t use the Service.

I think this is what you are looking for? Please feel free to ask if you have any questions about the code or how to use it.

@HeroesGraveDev It’s not too messy as long as you just use Java’s own class loaders and don’t try implementing your own - here the URLClassLoader really comes in useful. It’s probably easy enough to do but I steered clear of it.

The trick is to call on the optional Jar only through interfaces.
Make the optional jar implement the “extraStuff” interface.
Load the jars using Class.forName("…");

Why does everybody on this forum writes his own service discovery system, there is already on included in the JRE (its called ServiceLoader).

Because it didn’t suit my needs. Mine is built around that it just adds in some more specific features.