org.liblouis.Louis Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of liblouis-java Show documentation
Show all versions of liblouis-java Show documentation
JNA based Java bindings to liblouis, an open-source braille translator and back-translator.
package org.liblouis;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.BasicFileAttributeView;
import static java.nio.file.Files.walkFileTree;
import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.NoSuchFileException;
import java.nio.file.SimpleFileVisitor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sun.jna.Callback;
import com.sun.jna.DefaultTypeMapper;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.StringArray;
import com.sun.jna.FromNativeContext;
import com.sun.jna.ToNativeContext;
import com.sun.jna.TypeConverter;
import com.sun.jna.TypeMapper;
import org.slf4j.LoggerFactory;
public class Louis {
private static File libraryPath = null;
/**
* To have any effect, this method needs to be called before any other method in Louis or
* Translator is called. If no library path is provided, the binaries embedded inside this JAR
* will be used.
*/
public static void setLibraryPath(File path) {
libraryPath = path;
}
private static Lou_TableResolver lou_tableResolver = null;
private static TableResolver tableResolver = null;
private static boolean tableResolverIsRegistered = false;
private static boolean tablesAreIndexed = false;
// table names are generated for provided URLs without a name
private final static Map generatedTableNames = new HashMap();
// non-file URLs returned by resolver are read and stored to temporary files
private final static Map tablesStoredToFile = new HashMap();
private final static Map tablesStoredToFileInv = new HashMap();
static synchronized String getTableNameForURL(URL table) {
int i = 1;
for (;;) {
String name = "" + i;
if (!generatedTableNames.containsKey(name)) {
generatedTableNames.put(name, table);
return name;
}
i++;
}
}
public static synchronized void setTableResolver(final TableResolver tableResolver) {
Louis.tableResolver = tableResolver;
lou_tableResolver = new Lou_TableResolver() {
public File[] invoke(String table, File base) {
File[] ret = _invoke(table, base);
if (ret == null)
log(Logger.Level.ERROR, "Cannot resolve table '%s'", table);
return ret;
}
private File[] _invoke(String table, File base) {
URL baseURL;
if (base == null)
baseURL = null;
else {
try {
base = base.getCanonicalFile(); }
catch (IOException e) {
throw new RuntimeException(e); }
baseURL = tablesStoredToFileInv.get(base);
if (baseURL == null)
baseURL = asURL(base);
}
URL tableURL;
if (base == null && generatedTableNames.containsKey(table)) {
tableURL = generatedTableNames.get(table);
} else {
tableURL = tableResolver.resolve(table, baseURL);
if (tableURL == null)
return null; // table cannot be resolved
}
File tableFile;
if (tablesStoredToFile.containsKey(tableURL)) {
tableFile = tablesStoredToFile.get(tableURL);
} else {
if (tableURL.toString().startsWith("file:")) {
tableFile = asFile(tableURL);
if (!tableFile.exists())
return null; // table cannot be resolved
} else {
// save to temporary file
InputStream in = null;
try {
in = tableURL.openStream();
tableFile = File.createTempFile("liblouis-java-", ".tbl");
tableFile.delete();
logger.trace("Copying " + tableURL + " to " + tableFile);
Files.copy(in, tableFile.toPath());
tableFile.deleteOnExit();
} catch (IOException e) {
return null; // table cannot be resolved
} finally {
if (in != null) try { in.close(); } catch (IOException e) {}
}
try {
tableFile = tableFile.getCanonicalFile(); }
catch (IOException e) {
throw new RuntimeException(e); }
tablesStoredToFile.put(tableURL, tableFile);
tablesStoredToFileInv.put(tableFile, tableURL);
}
}
return new File[]{tableFile};
}
};
tableResolverIsRegistered = false;
}
private static Logger logCallback = null;
private static Lou_LogCallback lou_logCallback = null;
private static boolean loggerIsRegistered = false;
public static synchronized void setLogger(final Logger logger) {
logCallback = logger;
lou_logCallback = new Lou_LogCallback() {
public void invoke(int level, String message) {
logger.log(Logger.Level.from(level), message);
}
};
loggerIsRegistered = false;
}
public static void setLogLevel(Logger.Level level) {
getLibrary().lou_setLogLevel(level.value());
}
static void log(Logger.Level level, String format, Object... args) {
Slf4jLogger.INSTANCE.log(level, format, args);
LouisLibrary lib = getLibrary();
if (logCallback != null && !(logCallback instanceof Slf4jLogger))
if (args.length > 0) {
String[] message = new String[1 + args.length];
message[0] = format;
for (int i = 0; i < args.length; i++)
message[1 + i] = args[i].toString();
lib._lou_logMessage(level.value(), message);
} else
lib._lou_logMessage(level.value(), format);
}
/**
* Get the version number of Liblouis.
*/
public static String getVersion() {
return getLibrary().lou_version();
}
/**
* Get a list of all the top-level tables.
*/
public static synchronized Set listTables() {
Set tables = new HashSet();
LouisLibrary lib = getLibrary();
lazyIndexTables(lib);
for (String t : lib.lou_listTables())
tables.add(new Table(t));
return Collections.unmodifiableSet(tables);
}
static synchronized String findTable(String query) {
LouisLibrary lib = getLibrary();
lazyIndexTables(lib);
return lib.lou_findTable(query);
}
private static void lazyIndexTables(LouisLibrary lib) {
if (!tablesAreIndexed) {
Set allFiles = tableResolver.list();
logger.debug("Indexing tables");
lib.lou_indexTables(allFiles.toArray(new String[allFiles.size()]));
tablesAreIndexed = true;
}
}
private static LouisLibrary INSTANCE;
static synchronized LouisLibrary getLibrary() {
if (INSTANCE == null) {
// look for binaries inside this JAR first (by default this is done only as a last resort in JNA)
if (libraryPath == null) {
try {
libraryPath = Native.extractFromResourcePath(
Platform.isWindows() ? "liblouis" : "louis", // otherwise we have to rename the DLL files which
// is not so easy to do in Maven
Louis.class.getClassLoader()); }
catch (IOException e) {}
}
try {
String name = (libraryPath != null) ? libraryPath.getCanonicalPath() : "louis";
LouisLibrary unsynced = (LouisLibrary)Native.loadLibrary(name, LouisLibrary.class);
INSTANCE = (LouisLibrary)Native.synchronizedLibrary(unsynced);
logger.debug("Loaded " + name + ": Liblouis v" + INSTANCE.lou_version() + " (UCS" + INSTANCE.lou_charSize() + ")");
} catch (IOException e) {
throw new RuntimeException("Could not load liblouis", e);
} finally {
// delete the binary if it was extracted by JNA
if (libraryPath != null && libraryPath.getName().startsWith("jna"))
if (!libraryPath.delete())
// mark for later removal by JNA
try {
new File(libraryPath.getParentFile(), libraryPath.getName() + ".x").createNewFile(); }
catch (IOException e) { /* ignore */ }
}
}
if (tableResolver == null) {
// default table resolver implementation that looks for tables inside this JAR and falls back to the file system
setTableResolver(new TableResolver() {
private final Map tables;
private final Set tablePaths; {
tables = new HashMap();
for (String table : listResources("org/liblouis/resource-files/tables"))
tables.put(table, Louis.class.getResource("resource-files/tables/" + table));
tablePaths = Collections.unmodifiableSet(tables.keySet());
logger.debug("Using default tables");
logger.trace("Table files: " + tablePaths);
}
private final Map aggregatorTables = new HashMap();
public URL resolve(String table, URL base) {
// if we are resolving an include rule from a generated aggregator table, resolve without base
if (aggregatorTables.containsValue(base))
base = null;
if (base == null || tables.containsValue(base)) {
if (tables.containsKey(table))
return tables.get(table);
}
// if it is a comma separated table list, create a single file that includes all the sub-tables
if (base == null && table.contains(",")) {
if (aggregatorTables.containsKey(table))
return aggregatorTables.get(table);
StringBuilder b = new StringBuilder();
for (String s : table.split(","))
b.append("include ").append(s.replaceAll("\\\\", "\\\\\\\\")).append('\n');
InputStream in = new ByteArrayInputStream(b.toString().getBytes(StandardCharsets.UTF_8));
try {
File f = File.createTempFile("liblouis-java-", ".tbl");
f.delete();
Files.copy(in, f.toPath());
f.deleteOnExit();
URL u = asURL(f);
aggregatorTables.put(table, u);
return u;
} catch (IOException e) {
throw new RuntimeException(e); // should not happen
}
}
// try file system
if (base != null && base.toString().startsWith("file:")) {
File f = new File(asFile(base), table);
if (f.exists())
return asURL(f);
} else {
File f = new File(table);
if (f.exists())
return asURL(f);
}
return null; // table cannot be resolved
}
public Set list() {
return tablePaths;
}
}
);
}
if (logCallback == null)
// pass on log messages to SLF4J
setLogger(Slf4jLogger.INSTANCE);
if (!tableResolverIsRegistered) {
INSTANCE.lou_registerTableResolver(lou_tableResolver);
tableResolverIsRegistered = true;
tablesAreIndexed = false;
}
if (!loggerIsRegistered && lou_logCallback != null) {
INSTANCE.lou_registerLogCallback(lou_logCallback);
loggerIsRegistered = true;
}
return INSTANCE;
}
interface LouisLibrary extends Library {
public int lou_translate(String tableList, WideString inbuf, IntByReference inlen,
WideString outbuf, IntByReference outlen, short[] typeform, byte[] spacing,
int[] outputPos, int[] inputPos, IntByReference cursorPos, int mode);
public int lou_backTranslate(String tableList, WideString inbuf, IntByReference inlen,
WideString outbuf, IntByReference outlen, short[] typeform, byte[] spacing,
int[] outputPos, int[] inputPos, IntByReference cursorPos, int mode);
public int lou_hyphenate(String tableList, WideString inbuf, int inlen, byte[] hyphens, int mode);
public int lou_dotsToChar(String tableList, WideString inbuf, WideString outbuf, int length, int mode);
public int lou_charSize();
public String lou_version();
public Pointer lou_getTable(String tableList);
/**
* Note that keeping resolver from being garbage collection is the
* responsibility of the caller.
*/
public void lou_registerTableResolver(Lou_TableResolver resolver);
/**
* Note that keeping logger from being garbage collection is the
* responsibility of the caller.
*/
public void lou_registerLogCallback(Lou_LogCallback logger);
public void lou_setLogLevel(int level);
public int lou_indexTables(String[] tables);
public String lou_findTable(String query);
public String lou_getTableInfo(String table, String key);
public String[] lou_listTables();
public void _lou_logMessage(int level, String... format);
}
interface Lou_TableResolver extends Callback {
public File[] invoke(String tableList, File base);
public TypeMapper TYPE_MAPPER = FileTypeMapper.INSTANCE;
}
interface Lou_LogCallback extends Callback {
public void invoke(int level, String message);
}
static class FileTypeMapper extends DefaultTypeMapper {
static final FileTypeMapper INSTANCE = new FileTypeMapper();
private FileTypeMapper() {
TypeConverter converter = new TypeConverter() {
public Class> nativeType() {
return String.class;
}
public Object toNative(Object file, ToNativeContext context) {
if (file == null)
return null;
if (file instanceof File[]) {
File[] files = ((File[])file);
String[] paths = new String[files.length];
for (int i = 0; i < files.length; i++)
paths[i] = (String)toNative(files[i], context);
return new StringArray(paths); }
try { return ((File)file).getCanonicalPath(); }
catch (IOException e) { return null; }
}
public Object fromNative(Object file, FromNativeContext context) {
if (file == null)
return null;
return new File(((Pointer)file).getString(0));
}
};
addToNativeConverter(File.class, converter);
addToNativeConverter(File[].class, converter);
addFromNativeConverter(File.class, converter);
}
}
private static File asFile(URL url) throws IllegalArgumentException {
try {
if (!"file".equals(url.getProtocol()))
throw new RuntimeException("expected file URL");
return new File(new URI("file", url.getPath(), null));
} catch (URISyntaxException e) {
throw new RuntimeException(e); // should not happen
}
}
@SuppressWarnings("deprecation")
static URL asURL(File file) {
try {
file = file.getCanonicalFile();
return new URL(URLDecoder.decode(file.toURI().toString().replace("+", "%2B")));
} catch (MalformedURLException e) {
throw new RuntimeException(e); // should not happen
} catch (IOException e) {
throw new RuntimeException(e); // should not happen
}
}
private static Iterable listResources(final String directory) {
File jarFile = asFile(Louis.class.getProtectionDomain().getCodeSource().getLocation());
if (!jarFile.exists())
throw new RuntimeException();
else if (jarFile.isDirectory()) {
File d = new File(jarFile, directory);
if (!d.exists())
throw new RuntimeException("directory does not exist");
else if (!d.isDirectory())
throw new RuntimeException("is not a directory");
else {
List resources = new ArrayList();
for (File f : d.listFiles())
resources.add(f.getName() + (f.isDirectory() ? "/" : ""));
return resources; }}
else {
FileSystem fs; {
try {
fs = FileSystems.newFileSystem(URI.create("jar:" + jarFile.toURI()),
Collections.emptyMap()); }
catch (IOException e) {
throw new RuntimeException(e); }}
try {
Path d = fs.getPath("/" + directory);
BasicFileAttributes a; {
try {
a = Files.getFileAttributeView(d, BasicFileAttributeView.class).readAttributes(); }
catch (NoSuchFileException e) {
throw new RuntimeException("directory does not exist"); }
catch (FileSystemNotFoundException e) {
throw new RuntimeException(e); }
catch (IOException e) {
throw new RuntimeException(e); }}
if (!a.isDirectory())
throw new RuntimeException("is not a directory");
final List resources = new ArrayList();
try {
walkFileTree(d, EnumSet.noneOf(FileVisitOption.class), 1, new SimpleFileVisitor() {
public FileVisitResult visitFile(Path f, BasicFileAttributes _) throws IOException {
resources.add(""+f.getFileName());
return FileVisitResult.CONTINUE; }}); }
catch (NoSuchFileException e) {
throw new RuntimeException(e); }
catch (IOException e) {
throw new RuntimeException(e); }
return resources; }
finally {
try {
fs.close(); }
catch (IOException e) {
throw new RuntimeException(e); }
}
}
}
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Louis.class);
private static class Slf4jLogger implements Logger {
static Slf4jLogger INSTANCE = new Slf4jLogger();
public void log(Level level, String message) {
switch (level) {
case DEBUG: logger.debug(message); break;
case INFO: logger.info(message); break;
case WARN: logger.warn(message); break;
case FATAL:
message = "FATAL: " + message;
case ERROR: logger.error(message); break;
}
}
void log(Level level, String format, Object... args) {
log(level, String.format(format, args));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy