Fix: wireActors, wenn App als JAR laeuft
 
	
	
	
	
	
	
		
		3 files modified
	
		
		1 files added
	
	
 
	
	
	
	
	
	
	
	
 |  |  | 
 |  |  | import com.sun.net.httpserver.HttpContext; | 
 |  |  | import com.sun.net.httpserver.HttpHandler; | 
 |  |  | import com.sun.net.httpserver.HttpServer; | 
 |  |  | import de.uhilger.neon.JarScanner.JarScannerListener; | 
 |  |  | import de.uhilger.neon.entity.ContextDescriptor; | 
 |  |  | import de.uhilger.neon.entity.NeonDescriptor; | 
 |  |  | import de.uhilger.neon.entity.ServerDescriptor; | 
 |  |  | 
 |  |  | import java.lang.reflect.InvocationTargetException; | 
 |  |  | import java.lang.reflect.Method; | 
 |  |  | import java.net.InetSocketAddress; | 
 |  |  | import java.net.MalformedURLException; | 
 |  |  | import java.net.URI; | 
 |  |  | import java.net.URISyntaxException; | 
 |  |  | import java.util.ArrayList; | 
 |  |  | import java.util.Arrays; | 
 |  |  | import java.util.HashMap; | 
 |  |  | 
 |  |  | import java.util.List; | 
 |  |  | import java.util.Map; | 
 |  |  | import java.util.concurrent.Executors; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Einen Neon-Server aus einer Beschreibungsdatei herstellen | 
 |  |  | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  * @version 1, 6.2.2024 | 
 |  |  |  */ | 
 |  |  | public class Factory { | 
 |  |  | public class Factory implements JarScannerListener { | 
 |  |  |  | 
 |  |  |   public Factory() { | 
 |  |  |     listeners = new ArrayList<>(); | 
 |  |  | 
 |  |  |     return gson.fromJson(sb.toString(), NeonDescriptor.class); | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   public void runInstance(NeonDescriptor d)  | 
 |  |  |   public void runInstance(Class c, NeonDescriptor d)  | 
 |  |  |           throws ClassNotFoundException, NoSuchMethodException, InstantiationException,  | 
 |  |  |           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { | 
 |  |  |     this.runInstance(d, null, new ArrayList<>()); | 
 |  |  |     this.runInstance(c, d, null, new ArrayList<>()); | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   public void runInstance(NeonDescriptor d, List<String> packageNames)  | 
 |  |  |   public void runInstance(Class c, NeonDescriptor d, List<String> packageNames)  | 
 |  |  |           throws ClassNotFoundException, NoSuchMethodException, InstantiationException,  | 
 |  |  |           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { | 
 |  |  |     this.runInstance(d, packageNames, new ArrayList<>()); | 
 |  |  |     this.runInstance(c, d, packageNames, new ArrayList<>()); | 
 |  |  |   } | 
 |  |  |   /** | 
 |  |  |    * Einen Neon-Server gemaess einem Serverbeschreibungsobjekt herstellen und starten | 
 |  |  | 
 |  |  |    * @throws InvocationTargetException | 
 |  |  |    * @throws IOException | 
 |  |  |    */ | 
 |  |  |   public void runInstance(NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp)  | 
 |  |  |   public void runInstance(Class c, NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp)  | 
 |  |  |           throws ClassNotFoundException, NoSuchMethodException, InstantiationException,  | 
 |  |  |           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { | 
 |  |  |     List serverList = d.server; | 
 |  |  | 
 |  |  |       if(packageNames == null) { | 
 |  |  |         packageNames = d.actorPackages; | 
 |  |  |       } | 
 |  |  |       addContexts(d, server, sd.contexts, packageNames, sdp); | 
 |  |  |       addContexts(c, d, server, sd.contexts, packageNames, sdp); | 
 |  |  |  | 
 |  |  |       server.setExecutor(Executors.newFixedThreadPool(10)); | 
 |  |  |       server.start(); | 
 |  |  | 
 |  |  |     return auth; | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   private void addContexts(NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames,  | 
 |  |  |   private void addContexts(Class c, NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames,  | 
 |  |  |           List<DataProvider> sdp)  | 
 |  |  |           throws ClassNotFoundException, NoSuchMethodException, InstantiationException,  | 
 |  |  |           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { | 
 |  |  | 
 |  |  |         ctxAttrs.putAll(cd.attributes);         | 
 |  |  |         if (h instanceof Handler) {          | 
 |  |  |           for (String packageName : packageNames) { | 
 |  |  |             wireActors( | 
 |  |  |             wireActors(c,  | 
 |  |  |                     packageName, Actor.class, (Handler) h,  | 
 |  |  |                     cd.attributes.get("contextName")); | 
 |  |  |               ctx.getAttributes().put("serverDataProviderList", sdp); | 
 |  |  | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   private void wireActors(String packageName, Class annotation, Handler h, String contextName) { | 
 |  |  |     ClassLoader cl = ClassLoader.getSystemClassLoader(); | 
 |  |  |     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 = Class.forName(packageName + "." | 
 |  |  |                   + line.substring(0, line.lastIndexOf('.'))); | 
 |  |  |           if (actorClass != null && actorClass.isAnnotationPresent(annotation)) { | 
 |  |  |             wire(h, actorClass, contextName); | 
 |  |  |   private void wireActors(Class c, String packageName, Class annotation, Handler h, String contextName) { | 
 |  |  |     JarScanner js = new JarScanner(); | 
 |  |  |     URI path; | 
 |  |  |     try { | 
 |  |  |       path = js.getPath(c); | 
 |  |  |       if(path.toString().endsWith(".class")) { | 
 |  |  |         ClassLoader cl = c.getClassLoader(); | 
 |  |  |         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(annotation)) { | 
 |  |  |                 wire(h, actorClass, contextName); | 
 |  |  |               } | 
 |  |  |             } catch (ClassNotFoundException ex) { | 
 |  |  |               // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden? | 
 |  |  |             } | 
 |  |  |           } else { | 
 |  |  |             wireActors(c, packageName + "." + line, annotation, h, contextName); | 
 |  |  |           } | 
 |  |  |         } catch (ClassNotFoundException ex) { | 
 |  |  |           // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden? | 
 |  |  |         } | 
 |  |  |       } else { | 
 |  |  |         wireActors(packageName + "." + line, annotation, h, contextName); | 
 |  |  |         ClassLoader cl = js.getUrlClassLoader(c); | 
 |  |  |         js.processZipContent(cl, new File(path), packageName, this, h, contextName); | 
 |  |  |       } | 
 |  |  |     } catch (URISyntaxException ex) { | 
 |  |  |       Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |    | 
 |  |  | 
 |  |  |       l.instanceStarted(); | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   /* -------------- JarScannerListener Implementierung --------------- */ | 
 |  |  |      | 
 |  |  |   @Override | 
 |  |  |   public void actorFound(Class actorClass, Handler h, String contextName) { | 
 |  |  |     wire(h, actorClass, contextName); | 
 |  |  |   } | 
 |  |  | } | 
 
 |  |  | 
 |  |  | import de.uhilger.neon.Action.Type; | 
 |  |  | import de.uhilger.neon.entity.ActionDescriptor; | 
 |  |  | import java.io.IOException; | 
 |  |  | import java.lang.reflect.Constructor; | 
 |  |  | import java.lang.reflect.InvocationTargetException; | 
 |  |  | import java.lang.reflect.Method; | 
 |  |  | import java.lang.reflect.Parameter; | 
 |  |  | 
 |  |  | import java.util.Iterator; | 
 |  |  | import java.util.List; | 
 |  |  | import java.util.Map; | 
 |  |  | import java.util.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen NeonActor enthalten. | 
 |  |  | 
 |  |  |  | 
 |  |  |     //Logger.getLogger(Handler.class.getName()) | 
 |  |  |     //        .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className}); | 
 |  |  |     dispatcher.get(methodType).put(ad.route, ad); | 
 |  |  |     //dispatcher.get(methodType).put(ad.route, ad); | 
 |  |  |     Object adMapObj = dispatcher.get(methodType); | 
 |  |  |     if(adMapObj instanceof HashMap hashMap) { | 
 |  |  |       @SuppressWarnings("unchecked") | 
 |  |  |       HashMap<String, ActionDescriptor> map = hashMap; | 
 |  |  |       map.put(ad.route, ad); | 
 |  |  |       Logger.getLogger(Handler.class.getName()).log(Level.FINER, "ActionDescriptor route {0} className {1}", new Object[]{route, className});               | 
 |  |  |     } else { | 
 |  |  |       Logger.getLogger(Handler.class.getName()).finer("ActionDescriptorMap nicht gefunden");         | 
 |  |  |     } | 
 |  |  |      | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   /** | 
 |  |  | 
 |  |  |                     .getHttpContext() | 
 |  |  |                     .getPath() | 
 |  |  |                     .length());             | 
 |  |  |              | 
 |  |  |     Type requestMethod = Type.valueOf(exchange.getRequestMethod()); | 
 |  |  |     String requestMethodStr = exchange.getRequestMethod();         | 
 |  |  |     Logger.getLogger(Handler.class.getName()).log(Level.FINER, "method {0} route {1}", new Object[]{requestMethodStr, route});    | 
 |  |  |     Type requestMethod = Type.valueOf(requestMethodStr); | 
 |  |  |     /* | 
 |  |  |       Es wird erst geprueft, ob zu einer bestimmten Route  | 
 |  |  |       ein Actor registriert wurde. Wenn kein Actor mit dieser  | 
 |  |  | 
 |  |  |     Object md = dispatcher.get(requestMethod); | 
 |  |  |     if (md instanceof Map) { | 
 |  |  |       int pos = route.lastIndexOf("/"); | 
 |  |  |       Logger.getLogger(Handler.class.getName()).log(Level.FINER, "pos {0}", pos);    | 
 |  |  |       Object o = ((Map) md).get(route); | 
 |  |  |       if (!(o instanceof ActionDescriptor)) { | 
 |  |  |         while (!found && (pos > -1)) { | 
 |  |  |           String routeRest = route.substring(0, pos); | 
 |  |  |           Logger.getLogger(Handler.class.getName()).log(Level.FINER, "pos {0} routeRest {1}", new Object[]{pos, routeRest}); | 
 |  |  |           o = ((Map) md).get(routeRest); | 
 |  |  |           if (o instanceof ActionDescriptor) { | 
 |  |  |             found = true; | 
 |  |  | 
 |  |  |         handleRequest(exchange, o, route, route, requestMethod); | 
 |  |  |       } | 
 |  |  |       if (!found) { | 
 |  |  |         Logger.getLogger(Handler.class.getName()).log(Level.FINER, "{0} not found ", route); | 
 |  |  |         o = dispatcher.get(requestMethod).get("/"); | 
 |  |  |         if (o instanceof ActionDescriptor) { | 
 |  |  |           handleRequest(exchange, o, route, route, requestMethod); | 
 |  |  |         } else { | 
 |  |  |           // kein ActionDescriptor für '/' | 
 |  |  |           Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Kein Actiondescriptor fuer '/'");    | 
 |  |  |         } | 
 |  |  |       } | 
 |  |  |     } else { | 
 |  |  |       // keine Actions fuer HTTP Methode | 
 |  |  |       Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Kein Actions fuer HTTP-Methode {0}", requestMethodStr);    | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   private void handleRequest(HttpExchange exchange, Object o, String route, String subroute, Type requestMethod) throws IOException { | 
 |  |  |     Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Handle Request route {0} subroute {1}", new Object[]{route, subroute});   | 
 |  |  |     ActionDescriptor ad = (ActionDescriptor) o; | 
 |  |  |     String actorClassName = ad.className; | 
 |  |  |     try { | 
 |  |  | 
 |  |  |         if (action != null) { | 
 |  |  |           if ((action.route().equals("/") || action.route().startsWith(route)) && action.type().equals(requestMethod)) { | 
 |  |  |             Object[] actionArgs = getActionArgs(exchange, method, ad, subroute); | 
 |  |  |             Object actorObj = actorClass.getDeclaredConstructor().newInstance(); | 
 |  |  |             addDataProvider(exchange, actorObj); | 
 |  |  |             Object antwort = method.invoke(actorObj, actionArgs); | 
 |  |  |             if (!action.handlesResponse()) { | 
 |  |  |               respond(exchange, antwort); | 
 |  |  |             @SuppressWarnings("unchecked") | 
 |  |  |             Object conObj = actorClass.getDeclaredConstructor(); | 
 |  |  |             if(conObj instanceof Constructor) { | 
 |  |  |               Constructor con = (Constructor) conObj; | 
 |  |  |               Object actorObj; | 
 |  |  |               actorObj = con.newInstance(); | 
 |  |  |               addDataProvider(exchange, actorObj); | 
 |  |  |               Object antwort = method.invoke(actorObj, actionArgs); | 
 |  |  |               if (!action.handlesResponse()) { | 
 |  |  |                 respond(exchange, antwort); | 
 |  |  |               } | 
 |  |  |             } else { | 
 |  |  |               // kein Konstruktor | 
 |  |  |               Logger.getLogger(Handler.class.getName()).info("Kein Konstruktor gefunden");         | 
 |  |  |             } | 
 |  |  |           } | 
 |  |  |         } | 
 |  |  | 
 |  |  |             InstantiationException | IllegalAccessException | IllegalArgumentException |  | 
 |  |  |             InvocationTargetException ex) { | 
 |  |  |       // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden? | 
 |  |  |       Logger.getLogger(Handler.class.getName()).finer("Kein passende Actor-Klasse gefunden");         | 
 |  |  |     } | 
 |  |  |     //} | 
 |  |  |   } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license | 
 |  |  |  * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | package de.uhilger.neon; | 
 |  |  |  | 
 |  |  | import java.io.File; | 
 |  |  | import java.io.IOException; | 
 |  |  | 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.logging.Level; | 
 |  |  | import java.util.logging.Logger; | 
 |  |  | import java.util.zip.ZipEntry; | 
 |  |  | import java.util.zip.ZipFile; | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * Die Klasse JarScanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, in  | 
 |  |  |  * welcher JAR-Datei sie liegt und diese JAR-Datei nach Klassen zu durchsuchen. | 
 |  |  |  *  | 
 |  |  |  * @author Ulrich Hilger | 
 |  |  |  * @version 0.1, 30.11.2024 | 
 |  |  |  */ | 
 |  |  | public class JarScanner { | 
 |  |  |    | 
 |  |  |    | 
 |  |  |   public void processZipContent(ClassLoader urlCL, File archive, String packageName, JarScannerListener l, Handler h, String contextName) { | 
 |  |  |     try { | 
 |  |  |       ZipFile zipfile = new ZipFile(archive); | 
 |  |  |       Enumeration en = zipfile.entries(); | 
 |  |  |       while (en.hasMoreElements()) { | 
 |  |  |         ZipEntry zipentry = (ZipEntry) en.nextElement(); | 
 |  |  |         if (!zipentry.isDirectory()) { | 
 |  |  |           processZipEntry(urlCL, zipentry, packageName, l, h, contextName); | 
 |  |  |         } else { | 
 |  |  |           // ZIP-Dir muss nicht bearbeitet werden | 
 |  |  |         } | 
 |  |  |       } | 
 |  |  |       zipfile.close(); | 
 |  |  |     } catch (IOException ex) { | 
 |  |  |       log(Level.SEVERE, ex.getLocalizedMessage()); | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |  | 
 |  |  |   private void processZipEntry(ClassLoader urlCL, ZipEntry zipentry, String packageName, JarScannerListener l, Handler h, String contextName) { | 
 |  |  |     log(Level.FINEST, zipentry.getName()); | 
 |  |  |     String zName = zipentry.getName(); | 
 |  |  |     if (zName.toLowerCase().endsWith(".class")) { | 
 |  |  |       int pos = zName.indexOf(".class"); | 
 |  |  |       String fullClassName = zName.substring(0, pos); | 
 |  |  |       log(Level.FINEST, "full class name: " + zName); | 
 |  |  |       String fullClassNameDots = fullClassName.replace('/', '.'); | 
 |  |  |       log(Level.FINEST, "full class name dots: " + fullClassNameDots); | 
 |  |  |       String pkgName = getPackageName(fullClassNameDots); | 
 |  |  |       log(Level.FINEST, " -- package name: " + pkgName); | 
 |  |  |       if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) { | 
 |  |  |         Class c = null; | 
 |  |  |         try { | 
 |  |  |           c = urlCL.loadClass(fullClassNameDots); | 
 |  |  |           if (c != null) { | 
 |  |  |             if (c.isAnnotationPresent(Actor.class)) { | 
 |  |  |               log(Level.FINER, " ---- ACTOR ---- " + fullClassNameDots); | 
 |  |  |               l.actorFound(c, h, contextName); | 
 |  |  |             } else { | 
 |  |  |               log(Level.FINER, "kein Actor " + fullClassNameDots); | 
 |  |  |             } | 
 |  |  |           } else { | 
 |  |  |             log(Level.FINER, "class NOT loaded: " + zName); | 
 |  |  |           } | 
 |  |  |         } catch (ClassNotFoundException ex) { | 
 |  |  |           log(Level.FINER, " +++++ Class not found: " + ex.getMessage()); | 
 |  |  |         } | 
 |  |  |       } | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   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 getUrlClassLoader(Class c) { | 
 |  |  |     URL url; | 
 |  |  |     ClassLoader urlCL = null; | 
 |  |  |     try { | 
 |  |  |       url = getPath(c).toURL(); | 
 |  |  |       log(Level.FINER, "url: " + url.getPath()); | 
 |  |  |       urlCL = new URLClassLoader(new URL[]{url}); | 
 |  |  |     } catch (URISyntaxException ex) { | 
 |  |  |       log(Level.SEVERE, ex.getMessage()); | 
 |  |  |     } catch (MalformedURLException ex) { | 
 |  |  |       log(Level.SEVERE, ex.getMessage()); | 
 |  |  |     } finally { | 
 |  |  |       return urlCL; | 
 |  |  |     } | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   public URI getPath(Class c) throws URISyntaxException { | 
 |  |  |     //Class c = this.getClass(); | 
 |  |  |     String className = c.getName(); | 
 |  |  |     finer("this name: " + className); | 
 |  |  |      | 
 |  |  |     int pos = className.indexOf(".class"); | 
 |  |  |     if(pos > -1) { | 
 |  |  |       String classNameWoExt = className.substring(0, pos); | 
 |  |  |     } | 
 |  |  |     String classNameWoPkg = className.substring(className.lastIndexOf(".") + 1); | 
 |  |  |     finer("Class name: " + classNameWoPkg); | 
 |  |  |     String classPath = c.getResource(classNameWoPkg + ".class").getPath(); | 
 |  |  |     pos = classPath.indexOf("!"); | 
 |  |  |     String jarPath; | 
 |  |  |     if(pos > -1) { | 
 |  |  |       jarPath = /*"jar:" + */ classPath.substring(0, pos); | 
 |  |  |     } else { | 
 |  |  |       jarPath = classPath; | 
 |  |  |     } | 
 |  |  |     finer("path: " + jarPath); | 
 |  |  |     return new URI(jarPath); | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   private void finer(String msg) { | 
 |  |  |     log(Level.FINER, msg); | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |   private void log(Level l, String msg) { | 
 |  |  |     Logger.getLogger(JarScanner.class.getName()).log(l, msg); | 
 |  |  |   } | 
 |  |  |    | 
 |  |  |    | 
 |  |  |   public interface JarScannerListener { | 
 |  |  |     public void actorFound(Class actorClass, Handler h, String contextName); | 
 |  |  |   } | 
 |  |  |    | 
 |  |  | } | 
 
 |  |  | 
 |  |  |      * Ein neues Objekt der Klasse RangeGroup erzeugen | 
 |  |  |      */ | 
 |  |  |     public RangeGroup() { | 
 |  |  |       ranges = new ArrayList(); | 
 |  |  |       ranges = new ArrayList<>(); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** |