<commit_before>// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.io.findfile;

import jodd.io.FileNameUtil;
import jodd.util.StringUtil;
import jodd.util.Wildcard;
import jodd.util.ArraysUtil;
import jodd.io.FileUtil;
import jodd.io.StreamUtil;
import jodd.io.ZipUtil;

import java.net.URL;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.Enumeration;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileNotFoundException;

/**
 * Simple utility that scans <code>URL</code>s for classes.
 * Its purpose is to help scanning class paths for some classes.
 * Content of Jar files is also examined.
 * <p>
 * All paths are matched using {@link Wildcard#matchPath(String, String) path-style}
 * wildcard matcher. All entries are matched using {@link Wildcard#match(String, String) common-style}
 * wildcard matcher.
 * @see ClassScanner
 */
public abstract class ClassFinder {

	private static final String CLASS_FILE_EXT = ".class";
	private static final String JAR_FILE_EXT = ".jar";

	// ---------------------------------------------------------------- excluded jars

	/**
	 * Array of system jars that are excluded from the search.
	 * By default it consists of java runtime libraries.
	 */
	protected String[] systemJars = new String[] {
			"**/jre/lib/*.jar",
			"**/jre/lib/ext/*.jar",
			"**/tools.jar",
			"**/j2ee.jar"
	};
	/**
	 * Array of excluded jars.
	 */
	protected String[] excludedJars;
	/**
	 * Array of jar file name patterns that are included in the search.
	 * This rule is applied after the excluded rule.
	 */
	protected String[] includedJars;

	public String[] getSystemJars() {
		return systemJars;
	}

	/**
	 * Specifies system jars, that are always excluded first.
	 */
	public void setSystemJars(String[] systemJars) {
		this.systemJars = systemJars;
	}

	public String[] getExcludedJars() {
		return excludedJars;
	}

	public void setExcludedJars(String... excludedJars) {
		this.excludedJars = excludedJars;
	}

	public String[] getIncludedJars() {
		return includedJars;
	}

	public void setIncludedJars(String... includedJars) {
		this.includedJars = includedJars;
	}

	// ---------------------------------------------------------------- included packages

	protected String[] includedEntries;    // array of included name patterns
	protected String[] excludedEntries;    // array of excluded name patterns

	public String[] getIncludedEntries() {
		return includedEntries;
	}

	/**
	 * Sets included set of names that will be considered during configuration,
	 */
	public void setIncludedEntries(String... includedEntries) {
		this.includedEntries = includedEntries;
	}

	public String[] getExcludedEntries() {
		return excludedEntries;
	}

	/**
	 * Sets excluded names that narrows included set of packages.
	 */
	public void setExcludedEntries(String... excludedEntries) {
		this.excludedEntries = excludedEntries;
	}

	// ---------------------------------------------------------------- implementation

	/**
	 * If set to <code>true</code> all files will be scanned and not only classes.
	 */
	protected boolean includeResources;
	/**
	 * If set to <code>true</code> exceptions for entry scans are ignored.
	 */
	protected boolean ignoreException;


	public boolean isIncludeResources() {
		return includeResources;
	}

	public void setIncludeResources(boolean includeResources) {
		this.includeResources = includeResources;
	}

	public boolean isIgnoreException() {
		return ignoreException;
	}

	/**
	 * Sets if exceptions during scanning process should be ignored or not.
	 */
	public void setIgnoreException(boolean ignoreException) {
		this.ignoreException = ignoreException;
	}

	// ---------------------------------------------------------------- scan

	/**
	 * Scans several URLs. If (#ignoreExceptions} is set, exceptions
	 * per one URL will be ignored and loops continues. 
	 */
	protected void scanUrls(URL... urls) {
		for (URL path : urls) {
			scanUrl(path);
		}
	}
	
	/**
	 * Scans single URL for classes and jar files.
	 * Callback {@link #onEntry(EntryData)} is called on
	 * each class name.
	 */
	protected void scanUrl(URL url) {
		File file = FileUtil.toFile(url);
		if (file == null) {
			if (ignoreException == false) {
				throw new FindFileException("URL is not a valid file: " + url);
			}
		}
		scanPath(file);
	}


	protected void scanPaths(File... paths) {
		for (File path : paths) {
			scanPath(path);
		}
	}

	protected void scanPaths(String... paths) {
		for (String path : paths) {
			scanPath(path);
		}
	}
	
	protected void scanPath(String path) {
		scanPath(new File(path));
	}

	/**
	 * Returns <code>true</code> if some JAR file has to be accepted.
	 * The following logic is provided by default, in given order:
	 * <li>system jars are excluded
	 * <li>excluded jars are excluded (if specified)
	 * <li>only included jars are included (if specified)
	 */
	protected boolean acceptJar(File jarFile) {
		String path = jarFile.getAbsolutePath();
		path = FileNameUtil.separatorsToUnix(path);

		if (systemJars != null) {
			int ndx = Wildcard.matchPathOne(path, systemJars);
			if (ndx != -1) {
				return false;
			}
		}
		if (excludedJars != null) {
			int ndx = Wildcard.matchPathOne(path, excludedJars);
			if (ndx != -1) {
				return false;
			}
		}
		if (includedJars != null) {
			int ndx = Wildcard.matchPathOne(path, includedJars);
			if (ndx == -1) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Scans single path.
	 */
	protected void scanPath(File file) {
		String path = file.getAbsolutePath();

		if (StringUtil.endsWithIgnoreCase(path, JAR_FILE_EXT) == true) {

			if (acceptJar(file) == false) {
				return;
			}
			scanJarFile(file);
		} else if (file.isDirectory() == true) {
			scanClassPath(file);
		}
	}

	// ---------------------------------------------------------------- internal

	/**
	 * Scans classes inside single JAR archive. Archive is scanned as a zip file.
	 * @see #onEntry(EntryData)
	 */
	protected void scanJarFile(File file) {
		ZipFile zipFile;
		try {
			zipFile = new ZipFile(file);
		} catch (IOException ioex) {
			if (ignoreException == false) {
				throw new FindFileException("Unable to open zip: " + file.getName(), ioex);
			}
			return;
		}
		Enumeration entries = zipFile.entries();
		while (entries.hasMoreElements()) {
			ZipEntry zipEntry = (ZipEntry) entries.nextElement();
			String zipEntryName = zipEntry.getName();
			try {
				if (StringUtil.endsWithIgnoreCase(zipEntryName, CLASS_FILE_EXT)) {
					String entryName = prepareEntryName(zipEntryName, true);
					EntryData entryData = new EntryData(entryName, zipFile, zipEntry);
					try {
						scanEntry(entryData);
					} finally {
						entryData.closeInputStreamIfOpen();
					}
				} else if (includeResources == true) {
					String entryName = prepareEntryName(zipEntryName, false);
					EntryData entryData = new EntryData(entryName, zipFile, zipEntry);
					try {
						scanEntry(entryData);
					} finally {
						entryData.closeInputStreamIfOpen();
					}
				}
			} catch (RuntimeException rex) {
				if (ignoreException == false) {
					ZipUtil.close(zipFile);
					throw rex;
				}
			}
		}
		ZipUtil.close(zipFile);
	}

	/**
	 * Scans single classpath directory.
	 * @see #onEntry(EntryData)
	 */
	protected void scanClassPath(File root) {
		String rootPath = root.getAbsolutePath();
		if (rootPath.endsWith(File.separator) == false) {
			rootPath += File.separatorChar;
		}

		FindFile ff = new FindFile().setIncludeDirs(false).setRecursive(true).searchPath(rootPath);
		File file;
		while ((file = ff.nextFile()) != null) {
			String filePath = file.getAbsolutePath();
			try {
				if (StringUtil.endsWithIgnoreCase(filePath, CLASS_FILE_EXT)) {
					scanClassFile(filePath, rootPath, file, true);
				} else if (includeResources == true) {
					scanClassFile(filePath, rootPath, file, false);
				}
			} catch (RuntimeException rex) {
				if (ignoreException == false) {
					throw rex;
				}
			}
		}
	}

	protected void scanClassFile(String filePath, String rootPath, File file, boolean isClass) {
		if (StringUtil.startsWithIgnoreCase(filePath, rootPath) == true) {
			String entryName = prepareEntryName(filePath.substring(rootPath.length()), isClass);
			EntryData entryData = new EntryData(entryName, file);
			try {
				scanEntry(entryData);
			} finally {
				entryData.closeInputStreamIfOpen();
			}
		}
	}

	/**
	 * Prepares resource and class names. For classes, it strips '.class' from the end and converts
	 * all (back)slashes to dots. For resources, it replaces all backslashes to slashes.
	 */
	protected String prepareEntryName(String name, boolean isClass) {
		String entryName = name;
		if (isClass) {
			entryName = name.substring(0, name.length() - 6);		// 6 == ".class".length()
			entryName = StringUtil.replaceChar(entryName, '/', '.');
			entryName = StringUtil.replaceChar(entryName, '\\', '.');
		} else {
			entryName = '/' + StringUtil.replaceChar(entryName, '\\', '/');
		}
		return entryName;
	}

	/**
	 * Returns <code>true</code> if some entry name has to be accepted.
	 * @see #prepareEntryName(String, boolean)
	 * @see #scanEntry(EntryData) 
	 */
	protected boolean acceptEntry(String entryName) {
		if (excludedEntries != null) {
			if (Wildcard.matchOne(entryName, excludedEntries) != -1) {
				return false;
			}
		}
		if (includedEntries != null) {
			if (Wildcard.matchOne(entryName, includedEntries) == -1) {
				return false;
			}
		}
		return true;
	}


	/**
	 * If entry name is {@link #acceptEntry(String) accepted} invokes {@link #onEntry(EntryData)} a callback}.
	 */
	protected void scanEntry(EntryData entryData) {
		if (acceptEntry(entryData.getName()) == false) {
			return;
		}
		try {
			onEntry(entryData);
		} catch (Exception ex) {
			throw new FindFileException("Unable to scan entry: " + entryData, ex);
		}
	}


	// ---------------------------------------------------------------- callback

	/**
	 * Called during classpath scanning when class or resource is found.
	 * <li>Class name is java-alike class name (pk1.pk2.class) that may be immediately used
	 * for dynamic loading.
	 * <li>Resource name starts with '\' and represents either jar path (\pk1/pk2/res) or relative file path (\pk1\pk2\res).
     * <code>InputStream</code> is provided by InputStreamProvider and opened lazy.
	 * Once opened, input stream doesn't have to be closed - this is done by this class anyway.
	 */
	protected abstract void onEntry(EntryData entryData) throws Exception;

	// ---------------------------------------------------------------- utilities

	/**
	 * Returns type signature bytes used for searching in class file.
	 */
	protected byte[] getTypeSignatureBytes(Class type) {
		String name = 'L' + type.getName().replace('.', '/') + ';';
		return name.getBytes();
	}

	/**
	 * Returns <code>true</code> if class contains {@link #getTypeSignatureBytes(Class) type signature}.
	 * It searches the class content for bytecode signature. This is the fastest way of finding if come
	 * class uses some type. Please note that if signature exists it still doesn't means that class uses
	 * it in expected way, therefore, class should be loaded to complete the scan.
	 */
	protected boolean isTypeSignatureInUse(InputStream inputStream, byte[] bytes) {
		try {
			byte[] data = StreamUtil.readBytes(inputStream);
			int index = ArraysUtil.indexOf(data, bytes);
			return index != -1;
		} catch (IOException ioex) {
			throw new FindFileException("Unable to read from input stream.", ioex);
		}
	}


	// ---------------------------------------------------------------- provider

	/**
	 * Provides input stream on demand. Input stream is not open until get().
	 */
	protected static class EntryData {

		private final File file;
		private final ZipFile zipFile;
		private final ZipEntry zipEntry;
		private final String name;

		EntryData(String name, ZipFile zipFile, ZipEntry zipEntry) {
			this.name = name;
			this.zipFile = zipFile;
			this.zipEntry = zipEntry;
			this.file = null;
			inputStream = null;
		}
		EntryData(String name, File file) {
			this.name = name;
			this.file = file;
			this.zipEntry = null;
			this.zipFile = null;
			inputStream = null;
		}

		private InputStream inputStream;

		/**
		 * Returns entry name.
		 */
		public String getName() {
			return name;
		}

		/**
		 * Returns <code>true</code> if archive.
		 */
		public boolean isArchive() {
			return zipFile != null;
		}

		/**
		 * Returns archive name or <code>null</code> if entry is not inside archived file.
		 */
		public String getArchiveName() {
			if (zipFile != null) {
				return zipFile.getName(); 
			}
			return null;
		}

		/**
		 * Opens zip entry or plain file and returns its input stream.
		 */
		public InputStream openInputStream() {
			if (zipFile != null) {
				try {
					inputStream = zipFile.getInputStream(zipEntry);
					return inputStream;
				} catch (IOException ioex) {
					throw new FindFileException("Unable to get input stream for zip file: '" + zipFile.getName()
							+ "', entry: '" + zipEntry.getName() + "'." , ioex);
				}
			}
			try {
				inputStream = new FileInputStream(file);
				return inputStream;
			} catch (FileNotFoundException fnfex) {
				throw new FindFileException("Unable to open file: " + file.getAbsolutePath(), fnfex);
			}
		}

		/**
		 * Closes input stream if opened.
		 */
		void closeInputStreamIfOpen() {
			if (inputStream == null) {
				return;
			}
			StreamUtil.close(inputStream);
			inputStream = null;
		}

		@Override
		public String toString() {
			return "EntryData{" + name + '\'' +'}';
		}
	}
}
<commit_msg>system jars are now static<commit_after>// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.io.findfile;

import jodd.io.FileNameUtil;
import jodd.util.StringUtil;
import jodd.util.Wildcard;
import jodd.util.ArraysUtil;
import jodd.io.FileUtil;
import jodd.io.StreamUtil;
import jodd.io.ZipUtil;

import java.net.URL;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.Enumeration;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileNotFoundException;

/**
 * Simple utility that scans <code>URL</code>s for classes.
 * Its purpose is to help scanning class paths for some classes.
 * Content of Jar files is also examined.
 * <p>
 * All paths are matched using {@link Wildcard#matchPath(String, String) path-style}
 * wildcard matcher. All entries are matched using {@link Wildcard#match(String, String) common-style}
 * wildcard matcher.
 * @see ClassScanner
 */
public abstract class ClassFinder {

	private static final String CLASS_FILE_EXT = ".class";
	private static final String JAR_FILE_EXT = ".jar";

	// ---------------------------------------------------------------- excluded jars

	/**
	 * Array of system jars that are excluded from the search.
	 * By default it consists of java runtime libraries.
	 */
	protected static String[] systemJars = new String[] {
			"**/jre/lib/*.jar",
			"**/jre/lib/ext/*.jar",
			"**/tools.jar",
			"**/j2ee.jar"
	};
	/**
	 * Array of excluded jars.
	 */
	protected String[] excludedJars;
	/**
	 * Array of jar file name patterns that are included in the search.
	 * This rule is applied after the excluded rule.
	 */
	protected String[] includedJars;

	/**
	 * Returns system jars.
	 */
	public static String[] getSystemJars() {
		return systemJars;
	}

	/**
	 * Specifies system jars, that are always excluded first.
	 */
	public static void setSystemJars(String[] newSystemJars) {
		systemJars = newSystemJars;
	}

	public String[] getExcludedJars() {
		return excludedJars;
	}

	public void setExcludedJars(String... excludedJars) {
		this.excludedJars = excludedJars;
	}

	public String[] getIncludedJars() {
		return includedJars;
	}

	public void setIncludedJars(String... includedJars) {
		this.includedJars = includedJars;
	}

	// ---------------------------------------------------------------- included packages

	protected String[] includedEntries;    // array of included name patterns
	protected String[] excludedEntries;    // array of excluded name patterns

	public String[] getIncludedEntries() {
		return includedEntries;
	}

	/**
	 * Sets included set of names that will be considered during configuration,
	 */
	public void setIncludedEntries(String... includedEntries) {
		this.includedEntries = includedEntries;
	}

	public String[] getExcludedEntries() {
		return excludedEntries;
	}

	/**
	 * Sets excluded names that narrows included set of packages.
	 */
	public void setExcludedEntries(String... excludedEntries) {
		this.excludedEntries = excludedEntries;
	}

	// ---------------------------------------------------------------- implementation

	/**
	 * If set to <code>true</code> all files will be scanned and not only classes.
	 */
	protected boolean includeResources;
	/**
	 * If set to <code>true</code> exceptions for entry scans are ignored.
	 */
	protected boolean ignoreException;


	public boolean isIncludeResources() {
		return includeResources;
	}

	public void setIncludeResources(boolean includeResources) {
		this.includeResources = includeResources;
	}

	public boolean isIgnoreException() {
		return ignoreException;
	}

	/**
	 * Sets if exceptions during scanning process should be ignored or not.
	 */
	public void setIgnoreException(boolean ignoreException) {
		this.ignoreException = ignoreException;
	}

	// ---------------------------------------------------------------- scan

	/**
	 * Scans several URLs. If (#ignoreExceptions} is set, exceptions
	 * per one URL will be ignored and loops continues. 
	 */
	protected void scanUrls(URL... urls) {
		for (URL path : urls) {
			scanUrl(path);
		}
	}
	
	/**
	 * Scans single URL for classes and jar files.
	 * Callback {@link #onEntry(EntryData)} is called on
	 * each class name.
	 */
	protected void scanUrl(URL url) {
		File file = FileUtil.toFile(url);
		if (file == null) {
			if (ignoreException == false) {
				throw new FindFileException("URL is not a valid file: " + url);
			}
		}
		scanPath(file);
	}


	protected void scanPaths(File... paths) {
		for (File path : paths) {
			scanPath(path);
		}
	}

	protected void scanPaths(String... paths) {
		for (String path : paths) {
			scanPath(path);
		}
	}
	
	protected void scanPath(String path) {
		scanPath(new File(path));
	}

	/**
	 * Returns <code>true</code> if some JAR file has to be accepted.
	 * The following logic is provided by default, in given order:
	 * <li>system jars are excluded
	 * <li>excluded jars are excluded (if specified)
	 * <li>only included jars are included (if specified)
	 */
	protected boolean acceptJar(File jarFile) {
		String path = jarFile.getAbsolutePath();
		path = FileNameUtil.separatorsToUnix(path);

		if (systemJars != null) {
			int ndx = Wildcard.matchPathOne(path, systemJars);
			if (ndx != -1) {
				return false;
			}
		}
		if (excludedJars != null) {
			int ndx = Wildcard.matchPathOne(path, excludedJars);
			if (ndx != -1) {
				return false;
			}
		}
		if (includedJars != null) {
			int ndx = Wildcard.matchPathOne(path, includedJars);
			if (ndx == -1) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Scans single path.
	 */
	protected void scanPath(File file) {
		String path = file.getAbsolutePath();

		if (StringUtil.endsWithIgnoreCase(path, JAR_FILE_EXT) == true) {

			if (acceptJar(file) == false) {
				return;
			}
			scanJarFile(file);
		} else if (file.isDirectory() == true) {
			scanClassPath(file);
		}
	}

	// ---------------------------------------------------------------- internal

	/**
	 * Scans classes inside single JAR archive. Archive is scanned as a zip file.
	 * @see #onEntry(EntryData)
	 */
	protected void scanJarFile(File file) {
		ZipFile zipFile;
		try {
			zipFile = new ZipFile(file);
		} catch (IOException ioex) {
			if (ignoreException == false) {
				throw new FindFileException("Unable to open zip: " + file.getName(), ioex);
			}
			return;
		}
		Enumeration entries = zipFile.entries();
		while (entries.hasMoreElements()) {
			ZipEntry zipEntry = (ZipEntry) entries.nextElement();
			String zipEntryName = zipEntry.getName();
			try {
				if (StringUtil.endsWithIgnoreCase(zipEntryName, CLASS_FILE_EXT)) {
					String entryName = prepareEntryName(zipEntryName, true);
					EntryData entryData = new EntryData(entryName, zipFile, zipEntry);
					try {
						scanEntry(entryData);
					} finally {
						entryData.closeInputStreamIfOpen();
					}
				} else if (includeResources == true) {
					String entryName = prepareEntryName(zipEntryName, false);
					EntryData entryData = new EntryData(entryName, zipFile, zipEntry);
					try {
						scanEntry(entryData);
					} finally {
						entryData.closeInputStreamIfOpen();
					}
				}
			} catch (RuntimeException rex) {
				if (ignoreException == false) {
					ZipUtil.close(zipFile);
					throw rex;
				}
			}
		}
		ZipUtil.close(zipFile);
	}

	/**
	 * Scans single classpath directory.
	 * @see #onEntry(EntryData)
	 */
	protected void scanClassPath(File root) {
		String rootPath = root.getAbsolutePath();
		if (rootPath.endsWith(File.separator) == false) {
			rootPath += File.separatorChar;
		}

		FindFile ff = new FindFile().setIncludeDirs(false).setRecursive(true).searchPath(rootPath);
		File file;
		while ((file = ff.nextFile()) != null) {
			String filePath = file.getAbsolutePath();
			try {
				if (StringUtil.endsWithIgnoreCase(filePath, CLASS_FILE_EXT)) {
					scanClassFile(filePath, rootPath, file, true);
				} else if (includeResources == true) {
					scanClassFile(filePath, rootPath, file, false);
				}
			} catch (RuntimeException rex) {
				if (ignoreException == false) {
					throw rex;
				}
			}
		}
	}

	protected void scanClassFile(String filePath, String rootPath, File file, boolean isClass) {
		if (StringUtil.startsWithIgnoreCase(filePath, rootPath) == true) {
			String entryName = prepareEntryName(filePath.substring(rootPath.length()), isClass);
			EntryData entryData = new EntryData(entryName, file);
			try {
				scanEntry(entryData);
			} finally {
				entryData.closeInputStreamIfOpen();
			}
		}
	}

	/**
	 * Prepares resource and class names. For classes, it strips '.class' from the end and converts
	 * all (back)slashes to dots. For resources, it replaces all backslashes to slashes.
	 */
	protected String prepareEntryName(String name, boolean isClass) {
		String entryName = name;
		if (isClass) {
			entryName = name.substring(0, name.length() - 6);		// 6 == ".class".length()
			entryName = StringUtil.replaceChar(entryName, '/', '.');
			entryName = StringUtil.replaceChar(entryName, '\\', '.');
		} else {
			entryName = '/' + StringUtil.replaceChar(entryName, '\\', '/');
		}
		return entryName;
	}

	/**
	 * Returns <code>true</code> if some entry name has to be accepted.
	 * @see #prepareEntryName(String, boolean)
	 * @see #scanEntry(EntryData) 
	 */
	protected boolean acceptEntry(String entryName) {
		if (excludedEntries != null) {
			if (Wildcard.matchOne(entryName, excludedEntries) != -1) {
				return false;
			}
		}
		if (includedEntries != null) {
			if (Wildcard.matchOne(entryName, includedEntries) == -1) {
				return false;
			}
		}
		return true;
	}


	/**
	 * If entry name is {@link #acceptEntry(String) accepted} invokes {@link #onEntry(EntryData)} a callback}.
	 */
	protected void scanEntry(EntryData entryData) {
		if (acceptEntry(entryData.getName()) == false) {
			return;
		}
		try {
			onEntry(entryData);
		} catch (Exception ex) {
			throw new FindFileException("Unable to scan entry: " + entryData, ex);
		}
	}


	// ---------------------------------------------------------------- callback

	/**
	 * Called during classpath scanning when class or resource is found.
	 * <li>Class name is java-alike class name (pk1.pk2.class) that may be immediately used
	 * for dynamic loading.
	 * <li>Resource name starts with '\' and represents either jar path (\pk1/pk2/res) or relative file path (\pk1\pk2\res).
     * <code>InputStream</code> is provided by InputStreamProvider and opened lazy.
	 * Once opened, input stream doesn't have to be closed - this is done by this class anyway.
	 */
	protected abstract void onEntry(EntryData entryData) throws Exception;

	// ---------------------------------------------------------------- utilities

	/**
	 * Returns type signature bytes used for searching in class file.
	 */
	protected byte[] getTypeSignatureBytes(Class type) {
		String name = 'L' + type.getName().replace('.', '/') + ';';
		return name.getBytes();
	}

	/**
	 * Returns <code>true</code> if class contains {@link #getTypeSignatureBytes(Class) type signature}.
	 * It searches the class content for bytecode signature. This is the fastest way of finding if come
	 * class uses some type. Please note that if signature exists it still doesn't means that class uses
	 * it in expected way, therefore, class should be loaded to complete the scan.
	 */
	protected boolean isTypeSignatureInUse(InputStream inputStream, byte[] bytes) {
		try {
			byte[] data = StreamUtil.readBytes(inputStream);
			int index = ArraysUtil.indexOf(data, bytes);
			return index != -1;
		} catch (IOException ioex) {
			throw new FindFileException("Unable to read from input stream.", ioex);
		}
	}


	// ---------------------------------------------------------------- provider

	/**
	 * Provides input stream on demand. Input stream is not open until get().
	 */
	protected static class EntryData {

		private final File file;
		private final ZipFile zipFile;
		private final ZipEntry zipEntry;
		private final String name;

		EntryData(String name, ZipFile zipFile, ZipEntry zipEntry) {
			this.name = name;
			this.zipFile = zipFile;
			this.zipEntry = zipEntry;
			this.file = null;
			inputStream = null;
		}
		EntryData(String name, File file) {
			this.name = name;
			this.file = file;
			this.zipEntry = null;
			this.zipFile = null;
			inputStream = null;
		}

		private InputStream inputStream;

		/**
		 * Returns entry name.
		 */
		public String getName() {
			return name;
		}

		/**
		 * Returns <code>true</code> if archive.
		 */
		public boolean isArchive() {
			return zipFile != null;
		}

		/**
		 * Returns archive name or <code>null</code> if entry is not inside archived file.
		 */
		public String getArchiveName() {
			if (zipFile != null) {
				return zipFile.getName(); 
			}
			return null;
		}

		/**
		 * Opens zip entry or plain file and returns its input stream.
		 */
		public InputStream openInputStream() {
			if (zipFile != null) {
				try {
					inputStream = zipFile.getInputStream(zipEntry);
					return inputStream;
				} catch (IOException ioex) {
					throw new FindFileException("Unable to get input stream for zip file: '" + zipFile.getName()
							+ "', entry: '" + zipEntry.getName() + "'." , ioex);
				}
			}
			try {
				inputStream = new FileInputStream(file);
				return inputStream;
			} catch (FileNotFoundException fnfex) {
				throw new FindFileException("Unable to open file: " + file.getAbsolutePath(), fnfex);
			}
		}

		/**
		 * Closes input stream if opened.
		 */
		void closeInputStreamIfOpen() {
			if (inputStream == null) {
				return;
			}
			StreamUtil.close(inputStream);
			inputStream = null;
		}

		@Override
		public String toString() {
			return "EntryData{" + name + '\'' +'}';
		}
	}
}

{'fraction_non_alphanumeric': 0.15065693430656935, 'fraction_numerical': 0.0017518248175182481, 'mean_word_length': 2.8993880745695177, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2485976', 'n_tokens_mistral': 9307, 'n_tokens_neox': 8002, 'n_words': 2408}