more robust way of extracting dlls / bwta data from classpath resources

this is important, as if you want to run the TestBot in the bwmirror project
directly from an IDE, the dlls and bwta data files are not being extracted
from a JAR file and the extraction would fail. this method is a fair bit more
robust and will automatically handle those differences.

in addition, using System.load instead of System.loadLibrary to load
the bwapi_bridge native library. this allows us to stop doing that hacky
reflection thing to reset the java.library.path property and just pass in
an absolute path to bwapi_bridge.dll
This commit is contained in:
gered 2017-04-11 18:37:30 -04:00
parent aae9960a7c
commit 2bff907f93
2 changed files with 210 additions and 169 deletions

View file

@ -8,6 +8,7 @@ import java.io.File;
import java.lang.Exception; import java.lang.Exception;
import java.lang.UnsupportedOperationException; import java.lang.UnsupportedOperationException;
import java.util.*; import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.*; import java.util.zip.*;
/** /**
@ -56,55 +57,79 @@ public class Mirror {
private static final boolean EXTRACT_JAR = true; private static final boolean EXTRACT_JAR = true;
private static final String VERSION = "2_5"; private static void extractResourceFile(String resourceFilename, String outputFilename) throws Exception {
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceFilename);
if (in == null)
throw new FileNotFoundException("Resource file not found: " + resourceFilename);
FileOutputStream out;
try {
out = new FileOutputStream(outputFilename);
} catch (Exception e) {
throw new FileNotFoundException("Could not open output file: " + outputFilename);
}
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer, 0, buffer.length)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.close();
in.close();
}
private static boolean extractAndLoadNativeLibraries() {
try {
System.out.println("Extracting bwapi_bridge.dll");
extractResourceFile("bwapi_bridge.dll", "./bwapi_bridge.dll");
System.out.println("Extracting libgmp-10.dll");
extractResourceFile("libgmp-10.dll", "./libgmp-10.dll");
System.out.println("Extracting libmpfr-4.dll");
extractResourceFile("libmpfr-4.dll", "./libmpfr-4.dll");
System.out.println("Loading native library bwapi_bridge.dll");
System.load(new File("./bwapi_bridge.dll").getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
private static boolean extractBwtaDataFiles() {
try {
Collection<String> bwtaFilenames = ResourceList.getResources(Pattern.compile("bwapi\\-data/BWTA2/[a-zA-Z0-9]+\\.bwta"));
System.out.println("Creating ./bwapi-data/BWTA2 directory");
new File("./bwapi-data/BWTA2").mkdirs();
System.out.println("Extracting " + bwtaFilenames.size() + " BWTA2 files:");
for (String filename : bwtaFilenames) {
System.out.println(filename);
String outputFilename = "./" + filename;
extractResourceFile(filename, outputFilename);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
static { static {
String arch = System.getProperty("os.arch"); String arch = System.getProperty("os.arch");
String dllNames[] = {"bwapi_bridge" + VERSION, "libgmp-10", "libmpfr-4"};
if(!arch.equals("x86")){ if(!arch.equals("x86")){
throw new UnsupportedOperationException("BWMirror API supports only x86 architecture."); throw new UnsupportedOperationException("BWMirror API supports only x86 architecture.");
} }
try {
if (EXTRACT_JAR) {
System.setProperty("java.library.path", ".");
java.lang.reflect.Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(null, null);
String path = Mirror.class.getProtectionDomain().getCodeSource().getLocation().getPath(); if (!extractAndLoadNativeLibraries())
String decodedPath = java.net.URLDecoder.decode(path, "UTF-8"); System.exit(1);
if (!extractBwtaDataFiles())
JarResources jar = null; System.exit(1);
for (String dllName : dllNames) {
String dllNameExt = dllName + ".dll";
if (!new File(dllNameExt).exists()) {
if (null == jar) {
jar = new JarResources(decodedPath);
}
byte[] correctDllData = jar.getResource(dllNameExt);
// prevents the creation of zero byte files
if (null != correctDllData) {
FileOutputStream funnyStream = new FileOutputStream(dllNameExt);
funnyStream.write(correctDllData);
funnyStream.close();
}
}
}
}
} catch (Exception e) {
System.err.println("Failed to extract native libraries.\n" + e);
}
System.loadLibrary(dllNames[0]);
File dataDir = new File("bwapi-data/BWTA");
if(!dataDir.exists()){
try {
dataDir.mkdirs();
} catch (Exception e) {
System.err.println("Unable to create /bwapi-data/BWTA folder, BWTA analysis will not be saved.");
}
}
} }
public Game getGame() { public Game getGame() {
@ -138,131 +163,4 @@ public class Mirror {
/*public*/ private interface FrameCallback { /*public*/ private interface FrameCallback {
public void update(); public void update();
} }
@SuppressWarnings({"unchecked"})
private static class JarResources {
// external debug flag
public boolean debugOn = false;
// jar resource mapping tables
private Hashtable htSizes = new Hashtable();
private Hashtable htJarContents = new Hashtable();
// a jar file
private String jarFileName;
/**
* creates a javabot.JarResources. It extracts all resources from a Jar
* into an internal hashtable, keyed by resource names.
*
* @param jarFileName a jar or zip file
*/
public JarResources(String jarFileName) {
this.jarFileName = jarFileName;
init();
}
/**
* Extracts a jar resource as a blob.
*
* @param name a resource name.
*/
public byte[] getResource(String name) {
return (byte[]) htJarContents.get(name);
}
/**
* initializes internal hash tables with Jar file resources.
*/
private void init() {
try {
// extracts just sizes only.
ZipFile zf = new ZipFile(jarFileName);
Enumeration e = zf.entries();
while (e.hasMoreElements()) {
ZipEntry ze = (ZipEntry) e.nextElement();
if (debugOn) {
System.out.println(dumpZipEntry(ze));
}
htSizes.put(ze.getName(), new Integer((int) ze.getSize()));
}
zf.close();
// extract resources and put them into the hashtable.
FileInputStream fis = new FileInputStream(jarFileName);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream zis = new ZipInputStream(bis);
ZipEntry ze = null;
while ((ze = zis.getNextEntry()) != null) {
if (ze.isDirectory()) {
continue;
}
if (debugOn) {
System.out.println(
"ze.getName()=" + ze.getName() + "," + "getSize()=" + ze.getSize()
);
}
int size = (int) ze.getSize();
// -1 means unknown size.
if (size == -1) {
size = ((Integer) htSizes.get(ze.getName())).intValue();
}
byte[] b = new byte[(int) size];
int rb = 0;
int chunk = 0;
while (((int) size - rb) > 0) {
chunk = zis.read(b, rb, (int) size - rb);
if (chunk == -1) {
break;
}
rb += chunk;
}
// add to internal resource hashtable
htJarContents.put(ze.getName(), b);
if (debugOn) {
System.out.println(
ze.getName() + " rb=" + rb +
",size=" + size +
",csize=" + ze.getCompressedSize()
);
}
}
} catch (NullPointerException e) {
System.out.println("done.");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dumps a zip entry into a string.
*
* @param ze a ZipEntry
*/
private String dumpZipEntry(ZipEntry ze) {
StringBuffer sb = new StringBuffer();
if (ze.isDirectory()) {
sb.append("d ");
} else {
sb.append("f ");
}
if (ze.getMethod() == ZipEntry.STORED) {
sb.append("stored ");
} else {
sb.append("defalted ");
}
sb.append(ze.getName());
sb.append("\t");
sb.append("" + ze.getSize());
if (ze.getMethod() == ZipEntry.DEFLATED) {
sb.append("/" + ze.getCompressedSize());
}
return (sb.toString());
}
}
} }

View file

@ -0,0 +1,143 @@
package bwapi;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* Get a list of all resources that exist on the classpath that match
* a given java.util.regex.Pattern.
*
* All elements listed in the system property "java.class.path" will be searched
* and whether it is a JAR file or directory will be handled automatically.
*
* Resources that are listed with this class can be loaded using a ClassLoader.
* e.g. Thread.currentThread().getContextClassLoader().getResourceAsStream(...)
*/
public class ResourceList {
/**
* Returns a list of all resources within the classpath matching the given pattern.
*
* NOTE: If your pattern includes paths, you should use forward slashes between
* directory and filenames instead of backslashes, even when running on Windows.
* Internally, paths will be converted to forward slashes before the pattern is
* matched for consistency reasons.
*
* @param pattern the pattern to match
* @return the resources in the order they are found
*/
public static Collection<String> getResources(final Pattern pattern) {
final ArrayList<String> retval = new ArrayList<>();
final String classPath = System.getProperty("java.class.path", ".");
final String[] classPathElements = classPath.split(File.pathSeparator);
for (final String element : classPathElements) {
retval.addAll(getResources(element, pattern));
}
return retval;
}
private static Collection<String> getResources(final String element,
final Pattern pattern) {
final ArrayList<String> retval = new ArrayList<>();
final File file = new File(element);
if (file.isDirectory()) {
String baseDirectory;
try {
baseDirectory = file.getCanonicalPath() + File.separator;
} catch (final IOException e) {
throw new java.lang.Error(e);
}
retval.addAll(getResourcesFromDirectory(file, baseDirectory, pattern));
} else {
retval.addAll(getResourcesFromJarFile(file, pattern));
}
return retval;
}
private static Collection<String> getResourcesFromJarFile(final File file, final Pattern pattern) {
final ArrayList<String> retval = new ArrayList<>();
ZipFile zf;
try {
zf = new ZipFile(file);
} catch (final ZipException e) {
throw new java.lang.Error(e);
} catch (final IOException e) {
throw new java.lang.Error(e);
}
final Enumeration e = zf.entries();
while (e.hasMoreElements()) {
final ZipEntry ze = (ZipEntry) e.nextElement();
final String fileName = ze.getName();
final boolean accept = pattern.matcher(fileName).matches();
if (accept) {
retval.add(fileName);
}
}
try {
zf.close();
} catch (final IOException e1) {
throw new java.lang.Error(e1);
}
return retval;
}
private static Collection<String> getResourcesFromDirectory(final File directory, final String baseDirectory, final Pattern pattern) {
final ArrayList<String> retval = new ArrayList<>();
final File[] fileList = directory.listFiles();
for (final File file : fileList) {
if (file.isDirectory()) {
retval.addAll(getResourcesFromDirectory(file, baseDirectory, pattern));
} else {
try {
// the below call to file.getCanonicalPath will return an absolute path.
// since JAR file paths will all be relative, we trim off the absolute path's
// base directory prefix before doing the regex match.
//
// also convert the remaining sub-path to use forward slashes. this is a convenience thing
// as the _ONLY_ time a path being matched in this entire class will have backslashes
// is in this method (when checking local directories only) and ONLY on Windows.
// checking JAR file paths (getResourcesFromJarFile) will have forward slashes, even
// on Windows...
// so let's just force forward slashes everywhere to be consistent, shall we?
final String fileName = file.getCanonicalPath().substring(baseDirectory.length()).replaceAll("\\\\", "/");
final boolean accept = pattern.matcher(fileName).matches();
if (accept) {
// also note that we are adding the slash-converted sub-path to the list of files, NOT
// the absolute path initially obtained! this is so that this path can be passed
// directly into a call to getResourceAsStream() or similar
retval.add(fileName);
}
} catch (final IOException e) {
throw new java.lang.Error(e);
}
}
}
return retval;
}
/**
* list the resources that match args[0]
*
* @param args args[0] is the pattern to match, or list all resources if
* there are no args
*/
public static void main(final String[] args) {
Pattern pattern;
if (args.length < 1) {
pattern = Pattern.compile(".*");
} else {
pattern = Pattern.compile(args[0]);
}
final Collection<String> list = ResourceList.getResources(pattern);
for (final String name : list) {
System.out.println(name);
}
}
}