package org.jboss.windup.util.furnace;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.DirectoryWalker;
import org.jboss.forge.furnace.Furnace;
import org.jboss.forge.furnace.addons.Addon;
import org.jboss.forge.furnace.container.simple.lifecycle.SimpleContainer;
import org.jboss.forge.furnace.util.AddonFilters;
import org.jboss.forge.furnace.util.Predicate;
import org.jboss.windup.util.PathUtil;
/**
* Utility methods for searching all reachable resources provided by Furnace class loaders.
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
* @author <a href="mailto:ozizka@redhat.com">Ondrej Zizka</a>
*/
public class FurnaceClasspathScanner
{
private static final Logger LOG = Logger.getLogger(FurnaceClasspathScanner.class.getName());
private final Furnace furnace;
public FurnaceClasspathScanner()
{
furnace = SimpleContainer.getFurnace(FurnaceClasspathScanner.class.getClassLoader());
}
public List<URL> scan(String fileExtension)
{
return scan(new FileExtensionFilter(fileExtension));
}
/**
* Scans all Forge addons for files accepted by given filter, and return them as a map (from Addon to URL list)
*/
public Map<Addon, List<URL>> scanForAddonMap(Predicate<String> filter)
{
Map<Addon, List<URL>> result = new IdentityHashMap<>();
// For each Forge addon...
for (Addon addon : furnace.getAddonRegistry().getAddons(AddonFilters.allStarted()))
{
List<String> filteredResourcePaths = filterAddonResources(addon, filter);
List<URL> discoveredURLs = new ArrayList<>();
for (String filePath : filteredResourcePaths)
{
URL ruleFile = addon.getClassLoader().getResource(filePath);
if (ruleFile != null)
discoveredURLs.add(ruleFile);
}
result.put(addon, discoveredURLs);
}
return result;
}
/**
* Scans all Forge addons for files accepted by given filter.
*/
public List<URL> scan(Predicate<String> filter)
{
List<URL> discoveredURLs = new ArrayList<>(128);
// For each Forge addon...
for (Addon addon : furnace.getAddonRegistry().getAddons(AddonFilters.allStarted()))
{
List<String> filteredResourcePaths = filterAddonResources(addon, filter);
for (String filePath : filteredResourcePaths)
{
URL ruleFile = addon.getClassLoader().getResource(filePath);
if (ruleFile != null)
discoveredURLs.add(ruleFile);
}
}
return discoveredURLs;
}
/**
* Scans all Forge addons for classes accepted by given filter.
*
* TODO: Could be refactored - scan() is almost the same.
*/
public List<Class<?>> scanClasses(Predicate<String> filter)
{
List<Class<?>> discoveredClasses = new ArrayList<>(128);
// For each Forge addon...
for (Addon addon : furnace.getAddonRegistry().getAddons(AddonFilters.allStarted()))
{
List<String> discoveredFileNames = filterAddonResources(addon, filter);
// Then try to load the classes.
for (String discoveredFilename : discoveredFileNames)
{
String clsName = PathUtil.classFilePathToClassname(discoveredFilename);
try
{
Class<?> clazz = addon.getClassLoader().loadClass(clsName);
discoveredClasses.add(clazz);
}
catch (ClassNotFoundException ex)
{
LOG.log(Level.WARNING, "Failed to load class for name '" + clsName + "':\n" + ex.getMessage(), ex);
}
}
}
return discoveredClasses;
}
/**
* Returns a list of files in given addon passing given filter.
*/
public List<String> filterAddonResources(Addon addon, Predicate<String> filter)
{
List<String> discoveredFileNames = new ArrayList<>();
List<File> addonResources = addon.getRepository().getAddonResources(addon.getId());
for (File addonFile : addonResources)
{
if (addonFile.isDirectory())
handleDirectory(filter, addonFile, discoveredFileNames);
else
handleArchiveByFile(filter, addonFile, discoveredFileNames);
}
return discoveredFileNames;
}
/**
* Scans given archive for files passing given filter, adds the results into given list.
*/
private void handleArchiveByFile(Predicate<String> filter, File archive, List<String> discoveredFiles)
{
try
{
try (ZipFile zip = new ZipFile(archive))
{
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (filter.accept(name))
discoveredFiles.add(name);
}
}
}
catch (IOException e)
{
throw new RuntimeException("Error handling file " + archive, e);
}
}
/**
* Scans given directory for files passing given filter, adds the results into given list.
*/
private void handleDirectory(final Predicate<String> filter, final File rootDir, final List<String> discoveredFiles)
{
try
{
new DirectoryWalker<String>()
{
private Path startDir;
public void walk() throws IOException
{
this.startDir = rootDir.toPath();
this.walk(rootDir, discoveredFiles);
}
@Override
protected void handleFile(File file, int depth, Collection<String> discoveredFiles) throws IOException
{
String newPath = startDir.relativize(file.toPath()).toString();
if (filter.accept(newPath))
discoveredFiles.add(newPath);
}
}.walk();
}
catch (IOException ex)
{
LOG.log(Level.SEVERE, "Error reading Furnace addon directory", ex);
}
}
}