Ciao
credo di aver capito cosa intendi con i .jar che possono leggere dei .class: vuoi implementare dei plug in, giusto?
Un parser e' una buona soluzione, ma da quanto ho capito il plug in sarebbe decisamente meglio, perche' ti permetterebbe piu' liberta' di implementazione. Penso che tu abbia ragione.
La cosa non e' difficile. Una soluzione veloce potrebbe essere:
1 - ti definisci un'interfaccia, cosa che peraltro hai gia' fatto:
public interface GeneralRegression
{
public double calculate(double a, double b, double c, double d, double e);
}
Finora hai sempre implementato questa interfaccia all'interno del tuo codice, ora la vuoi implementare anche in jar esterni;
2 - Compili il tuo codice e crei un .jar. L'interfaccia in questione (i.e. GeneralRegression) potresti decidere di tenerla in questo jar principale oppure di creare un jar a parte, questione di gusti.
3 - Apri un nuovo progetto, ESTERNO al precedente e ci scrivi la tua nuova funzione:
package com.sottovento.highfly;
public class pluginTest implements GeneralRegression
{
@Override
public double calculate(double a, double b, double c, double d, double e)
{
return a + b + c + d + e;
}
}
Ovviamente per poterla compilare e' necessario conoscere l'interfaccia GeneralRegression, vale a dire devi mettere nel CLASSPATH il jar compilato precedentemente. Se utilizzi un IDE tipo NetBeans lo puoi fare facilmente aggiungendo la libreria nelle properties del progetto. In Netbeans (ma immagino sia possibile anche negli altri IDE) puoi aggiungere il progetto stesso invece della .jar; questo facilita il debug poiche' passerai da un progetto all'altro senza soluzione di continuita', ed e' un grandissimo vantaggio.
4 - Sarebbe bello poter configurare i plug-in utilizzando un file di configurazione, giusto? Allora lo creiamo, per esempio nella directory corrente (dove l'applicazione partira'), e decidiamo un formato. Siccome non mi voglio sbattere con XML e quant'altro, uso un file .properties. Dentro potrei scriverci, per esempio:
plugins.count=1
plugins1.classname=com.sottovento.highfly.GeneralRegression
jarfiles.count=1
jarfile1.filename=C:/MyApplication/pluginTest.jar
salvo ed esco. Nota che ho separato la configurazione dei jar da quella dei plugin. I motivi principali sono:
- Un singolo progetto potrebbe implementare piu' plug-in, vale a dire potrebbe avere piu' classi che implementano l'interfaccia che ti interessa;
- Potresti avere bisogno di librerie esterne (i.e. altri jar), per esempio librerie che hai trovato su internet per fare i calcoli, o librerie contenenti driver per accedere ai dati di un database, ecc.
Penso sia chiaro come devi fare se vuoi aggiungere altri plug in...
5 - Sai come fare a caricare un file del genere, giusto?
Per sicurezza, te lo scrivo:
// ritorna le properties lette dal file
public Properties loadProperties() throws IOException
{
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream("myConfigurationFile.properties"))
{
props.load(fis);
}
}
6 - Torniamo al programma principale, visto che siamo pronti per implementare il caricamento. Per prima cosa, occorre modificare il classpath dinamicamente, in modo da aggiungere i .jar che hai configurato.
Puoi fare una cosa del genere (tralascio il controllo delle eccezioni e tutto il resto, e' solo uno schema):
private URLClassLoader addJars(Properties props) throws MalformedURLException
{
int jarCount = Integer.parseInt(props.getProperty("jarfiles.count"));
URL[] vectURL = new URL[jarCount];
for (int i = 0; i < jarCount; i++)
{
String filename = props.getProperty("jarfile" + (i+1) + ".filename");
URL u = new File(filename).toURI().toURL();
vectURL[i] = u;
}
URLClassLoader loader = new URLClassLoader(vectURL);
return loader;
}
Ok, hai il tuo nuovo "classpath".
7 - Ora puoi caricare i plug in usando questo nuovo classpath dinamico (anche in questo caso non controllo tutte le condizioni di errore, e' solo uno schema):
private GeneralRegression[] loadPlugins(Properties props, URLClassLoader cl) throws LinkageError, ExceptionInInitializerError, ClassNotFoundException
{
int pluginCount = Integer.parseInt(props.getProperty("plugins.count")); // Fai qualche controllo, per esempio se hai l'eccezione NumberFormatException, ecc
GeneralRegression[] vectPlugins = new GeneralRegression[pluginCount];
for (int i = 0; i < pluginCount; i++)
{
String className = props.getProperty("plugins" + (i+1) + ".classname"); // Fai qualche controllo sul nome della classe, ecc.
vectPlugins[i] = (GeneralRegression)Class.forName(className, true, cl).newInstance(); // Dovresti fare il controllo sul tipo, per evitare ClassCastException nel caso qualcuno si dimentichi di implementare l'interfaccia
}
return vectPlugins;
}
Nel tuo codice ti bastera'
- caricare il file di configurazione
Properties props = loadProperties();
- chiamare il loader:
URLClassLoader cl = addJars(props);
- caricare i plugins:
GeneralRegression[] vectRegressions = loadPlugins(props, cl);
voila'. In vectRegression[] hai tutte le formule che ti interessano, caricabili runtime (quindi senza cambiare il tuo programma tutte le volte) e facilmente usabili, come qualsiasi altra formula che hai all'interno della tua applicazione.
Per esempio:
ArrayList<GeneralRegression> v = Arrays.asList(vectRegressions);
v.stream().forEach(gr -> risultato = gr.calculate(a, b, c, d, e));
Se le formule necessitano di una propria HMI, il discorso non cambia, puoi sempre aggiungere altri metodi da implementare che ritornano un component, e aggiungerli per esempio ad una JTabbed pane, mostrarli al click su un JTree o qualsiasi altra cosa la tua fantasia ti suggerisce