diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java index 63c036ea..a05e8334 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java @@ -18,14 +18,14 @@ package com.lyndir.masterpassword.impl; -import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; - import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteStreams; import com.lyndir.lhunath.opal.system.logging.Logger; import java.io.*; -import java.util.Locale; +import java.util.*; +import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -43,71 +43,161 @@ public final class Native { private static final String NATIVES_PATH = "lib"; @SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" }) - public static void load(final Class context, final String name) { + public static boolean load(final Class context, final String name) { // Try to load the library using the native system. try { System.loadLibrary( name ); - return; + return true; } catch (@SuppressWarnings("ErrorNotRethrown") final UnsatisfiedLinkError ignored) { } // Try to find and open a stream to the packaged library resource. - try { - String library = System.mapLibraryName( name ); - int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR ); - String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library; - String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib"; - String libraryResource = getLibraryResource( library ); - InputStream libraryStream = context.getResourceAsStream( libraryResource ); - if (libraryStream == null) - throw new IllegalStateException( - "Library: " + name + " (" + libraryResource + "), not found in class loader for: " + context ); + String library = System.mapLibraryName( name ); + int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR ); + String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library; + String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib"; - // Write the library resource to a temporary file. - File libraryFile = File.createTempFile( libraryName, libraryExtension ); - FileOutputStream libraryFileStream = new FileOutputStream( libraryFile ); + @Nullable + File libraryFile = null; + Set libraryResources = getLibraryResources( library ); + for (final String libraryResource : libraryResources) { try { - libraryFile.deleteOnExit(); - ByteStreams.copy( libraryStream, libraryFileStream ); - } - finally { - libraryFileStream.close(); - libraryStream.close(); - } + InputStream libraryStream = context.getResourceAsStream( libraryResource ); + if (libraryStream == null) { + logger.dbg( "No resource for library: %s", libraryResource ); + continue; + } - // Load the library from the temporary file. - System.load( libraryFile.getAbsolutePath() ); - } - catch (final IOException e) { - throw new IllegalStateException( "Couldn't extract library: " + name, e ); + // Write the library resource to a temporary file. + libraryFile = File.createTempFile( libraryName, libraryExtension ); + FileOutputStream libraryFileStream = new FileOutputStream( libraryFile ); + try { + libraryFile.deleteOnExit(); + ByteStreams.copy( libraryStream, libraryFileStream ); + } + finally { + libraryFileStream.close(); + libraryStream.close(); + } + + // Load the library from the temporary file. + System.load( libraryFile.getAbsolutePath() ); + return true; + } + catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError e) { + logger.dbg( e, "Couldn't load library: %s", libraryResource ); + + if (libraryFile != null) + if (libraryFile.exists() && !libraryFile.delete()) + logger.wrn( "Couldn't clean up library file: %s", libraryFile ); + libraryFile = null; + } } + + return false; } @Nonnull - private static String getLibraryResource(final String library) { - String system = ifNotNullElse( System.getProperty( "os.name" ), "linux" ).toLowerCase( Locale.ROOT ); - String architecture = ifNotNullElse( System.getProperty( "os.arch" ), "x86" ).toLowerCase( Locale.ROOT ); - + private static Set getLibraryResources(final String library) { // Standardize system naming in accordance with masterpassword-core. - if (system.contains( "windows" )) - system = "windows"; - else if (system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" )) - system = "macos"; - else - system = "linux"; + Sys system = Sys.findCurrent(); // Standardize architecture naming in accordance with masterpassword-core. - if (ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture )) - architecture = "arm"; - else if (architecture.startsWith( "arm" )) - architecture = "arm64"; - else if (ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture )) - architecture = "x86_64"; - else - architecture = "x86"; + Collection architectures = new LinkedHashSet<>(); + architectures.add( Arch.findCurrent() ); + architectures.addAll( Arrays.asList( Arch.values() ) ); - return Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, architecture, library ); + ImmutableSet.Builder resources = ImmutableSet.builder(); + for (final Arch arch : architectures) + resources.add( Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, arch, library ) ); + + return resources.build(); + } + + private enum Sys implements Predicate { + windows { + @Override + public boolean test(final String system) { + return system.contains( "windows" ); + } + }, + macos { + @Override + public boolean test(final String system) { + return system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" ); + } + }, + linux { + @Override + public boolean test(final String system) { + return system.contains( "linux" ); + } + }; + + @Nonnull + public static Sys findCurrent() { + return find( System.getProperty( "os.name" ) ); + } + + @Nonnull + public static Sys find(@Nullable String name) { + if (name != null) { + name = name.toLowerCase( Locale.ROOT ); + + for (final Sys sys : values()) + if (sys.test( name )) + return sys; + } + + return linux; + } + } + + + private enum Arch implements Predicate { + arm { + @Override + public boolean test(final String architecture) { + return ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture ); + } + }, + arm64 { + @Override + public boolean test(final String architecture) { + return architecture.startsWith( "arm" ) && !arm.test( architecture ); + } + }, + x86_64 { + @Override + public boolean test(final String architecture) { + return ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture ); + } + }, + x86 { + @Override + public boolean test(final String architecture) { + return ImmutableList.of( "x86", "i386", "i686" ).contains( architecture ); + } + }; + + @Nonnull + public static Arch findCurrent() { + return find( System.getProperty( "os.arch" ) ); + } + + @Nonnull + public static Arch find(@Nullable String name) { + if (name != null) { + name = name.toLowerCase( Locale.ROOT ); + + for (final Arch arch : values()) + if (arch.test( name )) + return arch; + } + + return x86; + } } }