/* * Mibble MIB Parser (www.mibble.org) * * See LICENSE.txt for licensing information. * * Copyright (c) 2009-2017 Per Cederberg. All rights reserved. */ package net.percederberg.mibble; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.InputStreamReader; import java.io.Reader; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A MIB module locator. This class attempts to map MIB module names * to files in a directory or on a resource path. It keeps two * internal caches; one based on file names, and one based on the * first few lines of file content. Each of these caches are created * upon first use and the content cache is normally a secondary * alternative due to the performance penalty when created. * * @author Per Cederberg * @version 2.10 * @since 2.10 */ public class MibLocator { /** * The MIB name regular expression pattern. */ private static final Pattern NAME = Pattern.compile("[a-zA-Z][a-zA-Z0-9-_]*"); /** * The optional class loader to use for locating MIB files. If * set, the MIB modules are searched as resources via this loader * and the directory path prefix. */ private ClassLoader classLoader; /** * The directory to search. If used together with a class loader, * this contains the resource path to search. */ private File dir; /** * The file name cache. This cache is indexed by upper-case MIB * name and points to the MIB source. */ private HashMap<String,MibSource> nameCache = null; /** * The content cache. This cache is indexed by the MIB name read * from the file and points to the MIB source. */ private HashMap<String,MibSource> contentCache = null; /** * Creates a new MIB module locator for a file directory. * * @param dir the directory to index */ public MibLocator(File dir) { this.dir = dir; } /** * Creates a new MIB module locator for a resource path on the * class path. Note that the resource path must be unique to a * specific JAR (or directory). * * @param classLoader the class loader to use * @param path the resource path to index */ public MibLocator(ClassLoader classLoader, String path) { this.classLoader = classLoader; this.dir = new File(path); } /** * Checks if the class loader is used for locating resources. * * @return true if the directory is a resource path, or * false if it represents a local directory */ public boolean isResourceDir() { return this.classLoader != null; } /** * Returns the directory or resource path indexed. If used with * a class loader, the file object is be a resource path (not an * existing file). * * @return the directory or resource path indexed */ public File getDir() { return dir; } /** * Returns a URL to the directory or resource path indexed. If * used with a class loader, the URL may point to a JAR file * (with a path suffix). Otherwise, the URL uses the "file:" * protocol and points to the absolute file path. * * @return the URL to the directory or resource path indexed */ public URL getUrl() { if (isResourceDir()) { return this.classLoader.getResource(this.dir.toString()); } else { try { return this.dir.toURI().toURL(); } catch (MalformedURLException ignore) { return null; } } } /** * Returns (and creates if needed) the MIB file name map. This * map converts file names to potential MIB module names and maps * them to the directory file. The MIB modules names will be all * UPPERCASE. Note that there are no guarantees that the returned * files are indeed MIB files. * * @return a map of MIB module names to files */ public Map<String,MibSource> getNameMap() { if (nameCache == null) { nameCache = new HashMap<>(); URL url = this.getUrl(); if (url == null) { // No files found } else if (url.getProtocol().equals("jar")) { nameCache.putAll(readJar(url, this.dir.toString(), false)); } else if (url.getProtocol().equals("file")) { nameCache.putAll(readDir(new File(url.getPath()), false)); } } return nameCache; } /** * Returns (and creates if needed) the MIB file content map. This * map maps MIB module names read from the initial lines of the * files to the directory file. Note that there are no absolute * guarantees that the returned files are indeed MIB files, only * the first few lines will have been read (and only to fetch the * MIB module name). * * @return a map of MIB module names to files */ public Map<String,MibSource> getContentMap() { if (contentCache == null) { contentCache = new HashMap<>(); URL url = this.getUrl(); if (url == null) { // No files found } else if (url.getProtocol().equals("jar")) { contentCache.putAll(readJar(url, this.dir.toString(), true)); } else if (url.getProtocol().equals("file")) { contentCache.putAll(readDir(new File(url.getPath()), true)); } } return contentCache; } /** * Searches for a MIB in the file name cache. The file name match * is case insensitive and ignores file extensions and suffixes. * Note that there are no guarantees that the returned file is * indeed a MIB file. * * @param mibName the MIB name * * @return the first matching MIB source, or * null if no match was found */ public MibSource findByName(String mibName) { return getNameMap().get(mibName.toUpperCase()); } /** * Searches for a MIB in the content cache. This cache is costly * to initialize (on first call), but may be more accurate as it * is based on the first few lines of each file. * * @param mibName the MIB name * * @return the first matching MIB source, or * null if no match was found */ public MibSource findByContent(String mibName) { return getContentMap().get(mibName); } /** * Finds all MIB files found in directory. The MIB names are * either guessed from the file names or read from the content. * MIB name guesses are always upper-case. * * @param dir the file directory * @param readContent the read MIB content flag * * @return a map of MIB module names to MIB sources */ private static Map<String,MibSource> readDir(File dir, boolean readContent) { HashMap<String,MibSource> res = new HashMap<>(); File[] files = dir.listFiles(); if (files != null) { for (File file : files) { String mibName = null; if (!readContent) { mibName = guessMibName(file.getName()); } else { mibName = readMibName(file); } if (mibName != null) { res.put(mibName, new MibSource(file)); } } } return res; } /** * Finds all MIB files found in a JAR URL. The MIB names are * either guessed from the file names or read from the content. * MIB name guesses are always upper-case. * * @param url the JAR URL (resource URL) * @param prefix the path prefix * @param readContent the read MIB content flag * * @return a map of MIB module names to MIB sources */ private static Map<String,MibSource> readJar(URL url, String prefix, boolean readContent) { HashMap<String,MibSource> res = new HashMap<>(); try ( JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); ){ String urlBase = url.toString().replaceAll("!.*", ""); Enumeration<JarEntry> e = jar.entries(); while (e.hasMoreElements()) { String path = e.nextElement().getName(); if (path.startsWith(prefix)) { URL resUrl = new URL(urlBase + "!/" + path); String mibName = null; if (!readContent) { mibName = guessMibName(path); } else { mibName = readMibName(resUrl); } if (mibName != null) { res.put(mibName, new MibSource(path, resUrl)); } } } } catch (Exception ignore) { // Do nothing } return res; } /** * Returns a possible matching MIB from a path. Any directory * portion of the path will be removed before matching the file * name to a MIB name regular expression. Any file extension or * non-matching file suffix is ignored. * * @param path the full file path * * @return the matching MIB name, or * null for none */ private static String guessMibName(String path) { File file = new File(path); Matcher m = NAME.matcher(file.getName()); if (m.lookingAt() && !path.endsWith("/") && !file.isDirectory()) { return m.group().toUpperCase(); } else { return null; } } /** * Reads the initial lines of a supposed text file attempting to * extract a MIB name. * * @param file the file to read * * @return the MIB name found, or * null if no name was found */ private static String readMibName(File file) { if (!file.canRead() || !file.isFile()) { return null; } try ( Reader in = new FileReader(file); ) { return readMibName(in); } catch (Exception ignore) { return null; } } /** * Reads the initial lines of a supposed text file attempting to * extract a MIB name. * * @param url the URL to read * * @return the MIB name found, or * null if no name was found */ private static String readMibName(URL url) { try ( Reader in = new InputStreamReader(url.openStream()); ) { return readMibName(in); } catch (Exception ignore) { return null; } } /** * Reads the initial lines of a supposed text file attempting to * extract a MIB name. * * @param reader the input stream to read * * @return the MIB name found, or * null if no name was found */ private static String readMibName(Reader reader) { try ( BufferedReader in = new BufferedReader(reader); ) { while (true) { String str = in.readLine(); if (str == null) { break; } str = str.trim(); if (!str.equals("") && !str.startsWith("--")) { Matcher m = NAME.matcher(str); boolean hasDefToken = str.contains("DEFINITIONS"); return m.lookingAt() && hasDefToken ? m.group() : null; } } } catch (Exception ignore) { // Do nothing } return null; } }