/*
|
neon - Embeddable HTTP Server based on jdk.httpserver
|
Copyright (C) 2024 Ulrich Hilger
|
|
This program is free software: you can redistribute it and/or modify
|
it under the terms of the GNU Affero General Public License as
|
published by the Free Software Foundation, either version 3 of the
|
License, or (at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
GNU Affero General Public License for more details.
|
|
You should have received a copy of the GNU Affero General Public License
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
*/
|
package de.uhilger.neon;
|
|
import java.io.BufferedReader;
|
import java.io.File;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.InputStreamReader;
|
import java.net.MalformedURLException;
|
import java.net.URI;
|
import java.net.URISyntaxException;
|
import java.net.URL;
|
import java.net.URLClassLoader;
|
import java.util.Enumeration;
|
import java.util.Iterator;
|
import java.util.logging.Level;
|
import java.util.logging.Logger;
|
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipFile;
|
|
/**
|
* Die Klasse Scanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, an welchem Ablageort sie
|
* sich befindet und diesen Ort nach Klassen zu durchsuchen, die eine gegebene Annotation besitzen.
|
*
|
* Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner im Dateisystem sein.
|
*
|
* @author Ulrich Hilger
|
* @version 0.1, 30.11.2024
|
*/
|
public final class Scanner {
|
|
private final URI path;
|
private final Class annotation;
|
|
private final Class cls;
|
private final ClassLoader urlCL;
|
|
/**
|
* Einen Scanner erzeugen, der den Ort, in dem sich eine gegebene Klasse befindet, nach Klassen
|
* durchsucht, die eine bestimmte Annotation besitzen
|
*
|
* Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner im Dateisystem sein.
|
*
|
* @param c eine Klasse die sich im Archiv befindet, das durchsucht werden soll
|
* @param annotation die Annotation, nach der gesucht wird
|
*/
|
public Scanner(Class c, Class annotation) {
|
this.annotation = annotation;
|
this.cls = c;
|
this.urlCL = getUrlClassLoader(cls);
|
this.path = getPath(c);
|
}
|
|
public Class getAnnotation() {
|
return annotation;
|
}
|
|
/**
|
* Klassen suchen, die die dem Konstruktor gegebene Annotation besitzen.
|
*
|
* Anhand der im Konstruktor uebergebenen Klasse wird deren Ablageort ermittelt, entweder ein
|
* Ordner im Dateisystem oder ein Java-Archiv (.jar). Dieser Ablageort wird dann nach annotierten
|
* Klassen durchsucht. Gefundene Klassen werden dem Listener gemeldet.
|
*
|
* @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
|
* dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
|
* @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
|
* @param params Parameter, die an den Listener weitergereicht werden
|
*/
|
public void process(ScannerListener l, String packageName, Object... params) {
|
if (isJar()) {
|
processZipContent(packageName, l, params);
|
} else {
|
processClasses(l, packageName, params);
|
}
|
}
|
|
private void processZipContent(String packageName, ScannerListener l, Object... params) {
|
try {
|
ZipFile zipfile = new ZipFile(new File(path));
|
Enumeration en = zipfile.entries();
|
//ClassLoader cl = getUrlClassLoader(cls);
|
while (en.hasMoreElements()) {
|
ZipEntry zipentry = (ZipEntry) en.nextElement();
|
if (!zipentry.isDirectory()) {
|
processZipEntry(zipentry, packageName, l, params);
|
} else {
|
// ZIP-Dir muss nicht bearbeitet werden
|
}
|
}
|
} catch (IOException ex) {
|
log(Level.SEVERE, ex.getLocalizedMessage());
|
}
|
}
|
|
/**
|
* Den Inhalt einer Jar-Datei nach Klassen durchsuchen, die die dem Konstruktor gegebene
|
* Annotation besitzen.
|
*
|
* @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
|
* dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
|
* @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
|
* @param params Parameter, die an den Listener weitergereicht werden
|
*/
|
private void processZipEntry(ZipEntry zipentry, String packageName, ScannerListener l, Object... params) {
|
finest(zipentry.getName());
|
String zName = zipentry.getName();
|
if (zName.toLowerCase().endsWith(".class")) {
|
int pos = zName.indexOf(".class");
|
String fullClassName = zName.substring(0, pos);
|
finest("full class name: " + zName);
|
String fullClassNameDots = fullClassName.replace('/', '.');
|
finest("full class name dots: " + fullClassNameDots);
|
String pkgName = getPackageName(fullClassNameDots);
|
finest(" -- package name: " + pkgName);
|
if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) {
|
try {
|
Class c = urlCL.loadClass(fullClassNameDots);
|
if (c != null) {
|
if (c.isAnnotationPresent(annotation)) {
|
finest(" ---- ACTOR ---- " + fullClassNameDots);
|
l.annotationFound(c, params);
|
} else {
|
finest("kein Actor " + fullClassNameDots);
|
}
|
} else {
|
finest("class NOT loaded: " + zName);
|
}
|
} catch (ClassNotFoundException ex) {
|
finest(" +++++ Class not found: " + ex.getMessage());
|
}
|
}
|
}
|
}
|
|
/**
|
* Einen Ordner mit Klassen durchsuchen
|
*
|
* @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
|
* dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
|
* @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
|
* @param params Parameter, die an den Listener weitergereicht werden
|
*/
|
private void processClasses(ScannerListener l, String packageName, Object... params) {
|
ClassLoader cl = getCl();
|
InputStream stream = cl.getResourceAsStream(packageName.replaceAll("[.]", "/"));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
Iterator i = reader.lines().iterator();
|
while (i.hasNext()) {
|
String line = i.next().toString();
|
if (line.endsWith(".class")) {
|
try {
|
Class actorClass = cl.loadClass(packageName + "."
|
+ line.substring(0, line.lastIndexOf('.')));
|
if (actorClass != null && actorClass.isAnnotationPresent(getAnnotation())) {
|
//wire(h, actorClass, contextName);
|
l.annotationFound(actorClass, params);
|
}
|
} catch (ClassNotFoundException ex) {
|
// Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
|
}
|
} else {
|
//wireActors(js, packageName + "." + line, h, contextName);
|
processClasses(l, packageName + "." + line, params);
|
}
|
}
|
}
|
|
private String getPackageName(String fullClassName) {
|
String packageName;
|
int pos = fullClassName.lastIndexOf(".");
|
if (pos > 0) {
|
packageName = fullClassName.substring(0, pos);
|
} else {
|
packageName = fullClassName;
|
}
|
return packageName;
|
}
|
|
public ClassLoader getCl() {
|
return cls.getClassLoader();
|
}
|
|
public ClassLoader getUrlClassLoader(Class c) {
|
ClassLoader cl = null;
|
try {
|
URL url = getPath(c).toURL();
|
finer("url: " + url.getPath());
|
cl = new URLClassLoader(new URL[]{url});
|
} catch (MalformedURLException ex) {
|
log(Level.SEVERE, ex.getMessage());
|
} finally {
|
return cl;
|
}
|
}
|
|
public String getPathStr() {
|
if (path != null) {
|
return path.toString();
|
} else {
|
return "";
|
}
|
}
|
|
public boolean isJar() {
|
return !getPathStr().toLowerCase().endsWith(".class");
|
}
|
|
private URI getPath(Class c) {
|
String className = c.getName();
|
finest("this name: " + className);
|
String classNameWoPkg = c.getSimpleName();//className.substring(className.lastIndexOf(".") + 1);
|
finest("Class name: " + classNameWoPkg);
|
String classPath = c.getResource(classNameWoPkg + ".class").getPath();
|
int pos = classPath.indexOf("!");
|
String jarPath;
|
if (pos > -1) {
|
jarPath = /*"jar:" + */ classPath.substring(0, pos);
|
} else {
|
jarPath = classPath;
|
}
|
finest("path: " + jarPath);
|
try {
|
return new URI(jarPath);
|
} catch (URISyntaxException ex) {
|
Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
|
return null;
|
}
|
}
|
|
private void finest(String msg) {
|
log(Level.FINEST, msg);
|
}
|
|
private void finer(String msg) {
|
log(Level.FINER, msg);
|
}
|
|
private void log(Level l, String msg) {
|
Logger.getLogger(Scanner.class.getName()).log(l, msg);
|
}
|
|
public interface ScannerListener {
|
public void annotationFound(Class foundClass, Object... params);
|
}
|
}
|