Ultrakompakter HTTP Server
ulrich
2024-12-03 24ad39eec2a0fdca1ef9768de0b92e45a34c1a24
commit | author | age
f4025a 1 /*
c2e8cf 2   neon - Embeddable HTTP Server based on jdk.httpserver
U 3   Copyright (C) 2024  Ulrich Hilger
f4025a 4
c2e8cf 5   This program is free software: you can redistribute it and/or modify
U 6   it under the terms of the GNU Affero General Public License as
7   published by the Free Software Foundation, either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
f4025a 18 package de.uhilger.neon;
U 19
692dc7 20 import java.io.BufferedReader;
f4025a 21 import java.io.File;
U 22 import java.io.IOException;
692dc7 23 import java.io.InputStream;
U 24 import java.io.InputStreamReader;
f4025a 25 import java.net.MalformedURLException;
U 26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.util.Enumeration;
692dc7 31 import java.util.Iterator;
f4025a 32 import java.util.logging.Level;
U 33 import java.util.logging.Logger;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipFile;
36
37 /**
12fdfa 38  * Die Klasse Scanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, an welchem Ablageort sie
U 39  * sich befindet und diesen Ort nach Klassen zu durchsuchen, die eine gegebene Annotation besitzen.
40  *
41  * Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner im Dateisystem sein.
47e67b 42  *
f4025a 43  * @author Ulrich Hilger
U 44  * @version 0.1, 30.11.2024
45  */
692dc7 46 public final class Scanner {
47e67b 47
U 48   private final URI path;
49   private final Class annotation;
692dc7 50
47e67b 51   private final Class cls;
U 52   private final ClassLoader urlCL;
53
54   /**
12fdfa 55    * Einen Scanner erzeugen, der den Ort, in dem sich eine gegebene Klasse befindet, nach Klassen
1f6776 56    * durchsucht, die eine bestimmte Annotation besitzen
12fdfa 57    *
U 58    * Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner im Dateisystem sein.
47e67b 59    *
U 60    * @param c eine Klasse die sich im Archiv befindet, das durchsucht werden soll
61    * @param annotation die Annotation, nach der gesucht wird
62    */
692dc7 63   public Scanner(Class c, Class annotation) {
47e67b 64     this.annotation = annotation;
U 65     this.cls = c;
66     this.urlCL = getUrlClassLoader(cls);
67     this.path = getPath(c);
692dc7 68   }
U 69
70   public Class getAnnotation() {
71     return annotation;
1f6776 72   }
24ad39 73   
40f0b0 74   /**
U 75    * Klassen suchen, die die dem Konstruktor gegebene Annotation besitzen.
12fdfa 76    *
U 77    * Anhand der im Konstruktor uebergebenen Klasse wird deren Ablageort ermittelt, entweder ein
78    * Ordner im Dateisystem oder ein Java-Archiv (.jar). Dieser Ablageort wird dann nach annotierten
79    * Klassen durchsucht. Gefundene Klassen werden dem Listener gemeldet.
40f0b0 80    *
U 81    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
82    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
83    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
24ad39 84    * @param params Parameter, die an den Listener weitergereicht werden
40f0b0 85    */
24ad39 86   public void process(ScannerListener l, String packageName, Object... params) {
1f6776 87     if (isJar()) {
24ad39 88       processZipContent(packageName, l, params);
1f6776 89     } else {
24ad39 90       processClasses(l, packageName, params);
1f6776 91     }
692dc7 92   }
47e67b 93
24ad39 94   private void processZipContent(String packageName, ScannerListener l, Object... params) {
f4025a 95     try {
47e67b 96       ZipFile zipfile = new ZipFile(new File(path));
f4025a 97       Enumeration en = zipfile.entries();
47e67b 98       //ClassLoader cl = getUrlClassLoader(cls);
f4025a 99       while (en.hasMoreElements()) {
U 100         ZipEntry zipentry = (ZipEntry) en.nextElement();
101         if (!zipentry.isDirectory()) {
24ad39 102           processZipEntry(zipentry, packageName, l, params);
f4025a 103         } else {
U 104           // ZIP-Dir muss nicht bearbeitet werden
105         }
106       }
107     } catch (IOException ex) {
108       log(Level.SEVERE, ex.getLocalizedMessage());
109     }
110   }
24ad39 111          
U 112   /**
113    * Den Inhalt einer Jar-Datei nach Klassen durchsuchen, die die dem Konstruktor gegebene
114    * Annotation besitzen.
115    *
116    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
117    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
118    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
119    * @param params Parameter, die an den Listener weitergereicht werden
120    */
121   private void processZipEntry(ZipEntry zipentry, String packageName, ScannerListener l, Object... params) {
7456da 122     finest(zipentry.getName());
f4025a 123     String zName = zipentry.getName();
U 124     if (zName.toLowerCase().endsWith(".class")) {
125       int pos = zName.indexOf(".class");
126       String fullClassName = zName.substring(0, pos);
7456da 127       finest("full class name: " + zName);
f4025a 128       String fullClassNameDots = fullClassName.replace('/', '.');
7456da 129       finest("full class name dots: " + fullClassNameDots);
f4025a 130       String pkgName = getPackageName(fullClassNameDots);
7456da 131       finest(" -- package name: " + pkgName);
f4025a 132       if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) {
U 133         try {
47e67b 134           Class c = urlCL.loadClass(fullClassNameDots);
f4025a 135           if (c != null) {
47e67b 136             if (c.isAnnotationPresent(annotation)) {
7456da 137               finest(" ---- ACTOR ---- " + fullClassNameDots);
24ad39 138               l.annotationFound(c, params);
f4025a 139             } else {
7456da 140               finest("kein Actor " + fullClassNameDots);
f4025a 141             }
U 142           } else {
7456da 143             finest("class NOT loaded: " + zName);
f4025a 144           }
U 145         } catch (ClassNotFoundException ex) {
7456da 146           finest(" +++++ Class not found: " + ex.getMessage());
f4025a 147         }
U 148       }
149     }
150   }
24ad39 151   
40f0b0 152   /**
U 153    * Einen Ordner mit Klassen durchsuchen
12fdfa 154    *
40f0b0 155    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
U 156    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
157    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
24ad39 158    * @param params Parameter, die an den Listener weitergereicht werden
40f0b0 159    */
24ad39 160   private void processClasses(ScannerListener l, String packageName, Object... params) {
1f6776 161     ClassLoader cl = getCl();
U 162     InputStream stream = cl.getResourceAsStream(packageName.replaceAll("[.]", "/"));
163     BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
164     Iterator i = reader.lines().iterator();
165     while (i.hasNext()) {
166       String line = i.next().toString();
167       if (line.endsWith(".class")) {
168         try {
169           Class actorClass = cl.loadClass(packageName + "."
170                   + line.substring(0, line.lastIndexOf('.')));
171           if (actorClass != null && actorClass.isAnnotationPresent(getAnnotation())) {
172             //wire(h, actorClass, contextName);
24ad39 173             l.annotationFound(actorClass, params);
692dc7 174           }
1f6776 175         } catch (ClassNotFoundException ex) {
U 176           // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
692dc7 177         }
1f6776 178       } else {
U 179         //wireActors(js, packageName + "." + line, h, contextName);
24ad39 180         processClasses(l, packageName + "." + line, params);
692dc7 181       }
1f6776 182     }
692dc7 183   }
24ad39 184   
f4025a 185   private String getPackageName(String fullClassName) {
U 186     String packageName;
187     int pos = fullClassName.lastIndexOf(".");
188     if (pos > 0) {
189       packageName = fullClassName.substring(0, pos);
190     } else {
191       packageName = fullClassName;
192     }
193     return packageName;
692dc7 194   }
U 195
196   public ClassLoader getCl() {
197     return cls.getClassLoader();
f4025a 198   }
47e67b 199
f4025a 200   public ClassLoader getUrlClassLoader(Class c) {
47e67b 201     ClassLoader cl = null;
f4025a 202     try {
47e67b 203       URL url = getPath(c).toURL();
7456da 204       finer("url: " + url.getPath());
47e67b 205       cl = new URLClassLoader(new URL[]{url});
f4025a 206     } catch (MalformedURLException ex) {
U 207       log(Level.SEVERE, ex.getMessage());
208     } finally {
47e67b 209       return cl;
f4025a 210     }
U 211   }
47e67b 212
U 213   public String getPathStr() {
214     if (path != null) {
215       return path.toString();
216     } else {
217       return "";
218     }
219   }
220
221   public boolean isJar() {
222     return !getPathStr().toLowerCase().endsWith(".class");
223   }
224
225   private URI getPath(Class c) {
f4025a 226     String className = c.getName();
7456da 227     finest("this name: " + className);
47e67b 228     String classNameWoPkg = c.getSimpleName();//className.substring(className.lastIndexOf(".") + 1);
7456da 229     finest("Class name: " + classNameWoPkg);
f4025a 230     String classPath = c.getResource(classNameWoPkg + ".class").getPath();
47e67b 231     int pos = classPath.indexOf("!");
f4025a 232     String jarPath;
47e67b 233     if (pos > -1) {
f4025a 234       jarPath = /*"jar:" + */ classPath.substring(0, pos);
U 235     } else {
236       jarPath = classPath;
237     }
7456da 238     finest("path: " + jarPath);
47e67b 239     try {
U 240       return new URI(jarPath);
241     } catch (URISyntaxException ex) {
692dc7 242       Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
47e67b 243       return null;
U 244     }
f4025a 245   }
47e67b 246
7456da 247   private void finest(String msg) {
U 248     log(Level.FINEST, msg);
249   }
47e67b 250
f4025a 251   private void finer(String msg) {
U 252     log(Level.FINER, msg);
253   }
47e67b 254
f4025a 255   private void log(Level l, String msg) {
692dc7 256     Logger.getLogger(Scanner.class.getName()).log(l, msg);
f4025a 257   }
47e67b 258
692dc7 259   public interface ScannerListener {
24ad39 260     public void annotationFound(Class foundClass, Object... params);
f4025a 261   }
1f6776 262 }