/**
* jetbrick-template
* http://subchen.github.io/jetbrick-template/
*
* Copyright 2010-2014 Guoqiang Chen. All rights reserved.
* Email: subchen@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrick.template.utils.finder;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import jetbrick.template.utils.*;
/**
* 查找指定路径下面的所有匹配得文件.
*
* 子类需要实现自己的 visitXXX() 方法来搜集相关的内容.
*
* @since 1.2.0
* @author Guoqiang Chen
*/
public abstract class FileFinder {
/**
* 搜索文件系统.
*/
public void lookupFileSystem(File dir, boolean recursive) {
doLookupInFileSystem(dir, null, null, recursive);
}
/**
* 搜索所有的 Classpath 的文件.
*/
public void lookupClasspath() {
lookupClasspath((String[]) null, true);
}
/**
* 搜索指定 package 下面的文件.
*/
public void lookupClasspath(List<String> packageNames, boolean recursive) {
String[] pkgs = null;
if (packageNames != null) {
pkgs = packageNames.toArray(new String[packageNames.size()]);
}
lookupClasspath(pkgs, recursive);
}
/**
* 搜索指定 package 下面的文件.
*/
public void lookupClasspath(String[] packageNames, boolean recursive) {
ClassLoader loader = ClassLoaderUtils.getContextClassLoader();
if (packageNames == null || packageNames.length == 0) {
Collection<URL> urls = ClassLoaderUtils.getClasspathURLs(loader);
doGetClasspathResources(urls, null, recursive);
} else {
for (String pkg : packageNames) {
Collection<URL> urls = ClassLoaderUtils.getClasspathURLs(loader, pkg);
doGetClasspathResources(urls, pkg.replace('.', '/'), recursive);
}
}
}
/**
* 搜索 jar/zip.
*/
public void lookupZipFile(File zipFile, String entryName, boolean recursive) {
ZipFile zip = null;
try {
zip = new ZipFile(zipFile);
doLookupInZipFile(zip, entryName, recursive);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IoUtils.closeQuietly(zip);
}
}
private void doGetClasspathResources(Collection<URL> urls, String pkgdir, boolean recursive) {
for (URL url : urls) {
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
File file = URLUtils.toFileObject(url);
if (file.isDirectory()) {
doLookupInFileSystem(file, pkgdir, null, recursive);
} else {
String name = file.getName().toLowerCase();
if (name.endsWith(".jar") || name.endsWith(".zip")) {
doLookupInZipFile(url, pkgdir, recursive);
}
}
} else if ("jar".equals(protocol) || "zip".equals(protocol)) {
doLookupInZipFile(url, pkgdir, recursive);
} else if ("vfs".equals(protocol)) {
doLookupInVfsFile(url, pkgdir, recursive);
} else {
throw new IllegalStateException("Unsupported url format: " + url.toString());
}
}
}
private void doLookupInFileSystem(File dir, String pkgdir, String relativeName, boolean recursive) {
if (!dir.exists() || !dir.isDirectory()) {
return;
}
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
String name = (relativeName == null) ? file.getName() : relativeName + '/' + file.getName();
SystemFileEntry entry = new SystemFileEntry(file, pkgdir, name);
if (file.isDirectory()) {
if (visitSystemDirEntry(entry)) {
if (recursive) {
doLookupInFileSystem(file, pkgdir, name, true);
}
}
} else {
visitSystemFileEntry(entry);
}
}
}
private void doLookupInZipFile(URL url, String pkgdir, boolean recursive) {
ZipFile zip = null;
try {
if ("jar".equals(url.getProtocol())) {
zip = ((JarURLConnection) url.openConnection()).getJarFile();
} else {
File file = URLUtils.toFileObject(url);
if (!file.exists()) {
return;
}
zip = new ZipFile(file);
}
doLookupInZipFile(zip, pkgdir, recursive);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IoUtils.closeQuietly(zip);
}
}
private void doLookupInZipFile(ZipFile zip, String pkgdir, boolean recursive) {
if (pkgdir == null || pkgdir.length() == 0) {
pkgdir = null;
} else {
pkgdir = pkgdir + '/';
}
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
// 获取jar里的一个实体, 可以是目录和一些jar包里的其他文件 如META-INF等文件
ZipEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entry.isDirectory()) {
entryName = entryName.substring(0, entryName.length() - 1);
}
if (pkgdir == null) {
if (entry.isDirectory()) {
visitZipDirEntry(new ZipFileEntry(zip, entry, entryName));
} else {
visitZipFileEntry(new ZipFileEntry(zip, entry, entryName));
}
} else if (entryName.startsWith(pkgdir)) {
entryName = entryName.substring(pkgdir.length());
if (recursive || entryName.indexOf('/') == -1) {
if (entry.isDirectory()) {
visitZipDirEntry(new ZipFileEntry(zip, entry, entryName));
} else {
visitZipFileEntry(new ZipFileEntry(zip, entry, entryName));
}
}
}
}
}
private void doLookupInVfsFile(URL url, String pkgdir, boolean recursive) {
try {
URLConnection conn = url.openConnection();
if (conn.getClass().getName().equals("org.jboss.vfs.protocol.VirtualFileURLConnection")) {
String vfs = conn.getContent().toString(); // VirtualFile
File file = new File(vfs.substring(1, vfs.length() - 1));
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
doLookupInFileSystem(file, pkgdir, null, recursive);
} else {
String name = file.getName().toLowerCase();
if (name.endsWith(".jar") || name.endsWith(".zip")) {
ZipFile zip = new ZipFile(file);
try {
doLookupInZipFile(zip, pkgdir, recursive);
} finally {
IoUtils.closeQuietly(zip);
}
}
}
} else {
throw new IllegalStateException("Unsupported URL: " + url);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//----------------------------------------------------------------
// following visitXXX methods should be overrided by subclass.
//
protected boolean visitSystemDirEntry(SystemFileEntry dir) {
return visitDirEntry(dir);
}
protected void visitSystemFileEntry(SystemFileEntry file) {
visitFileEntry(file);
}
protected void visitZipDirEntry(ZipFileEntry dir) {
visitDirEntry(dir);
}
protected void visitZipFileEntry(ZipFileEntry file) {
visitFileEntry(file);
}
protected boolean visitDirEntry(FileEntry dir) {
return true;
}
protected void visitFileEntry(FileEntry file) {
}
//----------------------------------------------------------------
// innerclass.
//
public static interface FileEntry {
public boolean isDirectory();
public boolean isJavaClass();
public String getName();
public String getRelativePathName();
public String getQualifiedJavaName();
public long length();
public long lastModified();
public InputStream getInputStream() throws IOException;
}
public static class SystemFileEntry implements FileEntry {
private final File file;
private final String pkgdir;
private final String relativeName;
public SystemFileEntry(File file, String pkgdir, String relativeName) {
this.file = file;
this.pkgdir = pkgdir;
this.relativeName = relativeName;
}
public File getFile() {
return file;
}
@Override
public boolean isDirectory() {
return file.isDirectory();
}
@Override
public boolean isJavaClass() {
return !file.isDirectory() && file.getName().endsWith(".class");
}
@Override
public String getName() {
return file.getName();
}
@Override
public String getRelativePathName() {
return relativeName;
}
@Override
public String getQualifiedJavaName() {
String name;
if (pkgdir != null) {
name = pkgdir + '/' + relativeName;
} else {
name = relativeName;
}
if (file.isDirectory()) {
return name.replace('/', '.');
} else {
if (name.endsWith(".class")) {
return name.substring(0, name.length() - 6).replace('/', '.');
}
throw new IllegalStateException("FileEntry is not a Java Class: " + toString());
}
}
@Override
public long length() {
return file.length();
}
@Override
public long lastModified() {
return file.lastModified();
}
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(file);
}
@Override
public String toString() {
return file.toString();
}
}
public static class ZipFileEntry implements FileEntry {
private final ZipFile zip;
private final ZipEntry entry;
private final String relativeName;
public ZipFileEntry(ZipFile zip, ZipEntry entry, String relativeName) {
this.zip = zip;
this.entry = entry;
this.relativeName = relativeName;
}
public ZipFile getZipFile() {
return zip;
}
public ZipEntry getZipEntry() {
return entry;
}
@Override
public boolean isDirectory() {
return entry.isDirectory();
}
@Override
public boolean isJavaClass() {
return entry.getName().endsWith(".class");
}
@Override
public String getName() {
int ipos = relativeName.lastIndexOf('/');
return ipos != -1 ? relativeName.substring(ipos + 1) : relativeName;
}
@Override
public String getRelativePathName() {
return relativeName;
}
@Override
public String getQualifiedJavaName() {
String name = entry.getName();
if (entry.isDirectory()) {
return name.substring(0, name.length() - 1).replace('/', '.');
} else {
if (name.endsWith(".class")) {
return name.substring(0, name.length() - 6).replace('/', '.');
}
throw new IllegalStateException("FileEntry is not a Java Class: " + toString());
}
}
@Override
public long length() {
return entry.getSize();
}
@Override
public long lastModified() {
return entry.getTime();
}
@Override
public InputStream getInputStream() throws IOException {
return zip.getInputStream(entry);
}
@Override
public String toString() {
return entry.toString();
}
}
}