/* * This file is part of the Illarion project. * * Copyright © 2015 - Illarion e.V. * * Illarion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Illarion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package illarion.download.launcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Collectors; import java.util.stream.Stream; /** * The sole purpose of this java executable iterator is to combat the inability of Apple Inc. to create a operating * system that has even remotely a proper design. * * @author Martin Karing <nitram@illarion.org> */ public class MacOsXJavaExecutableIterable extends AbstractJavaExecutableIterable { /** * The logger instance for this class. */ @Nonnull private static final Logger LOGGER = LoggerFactory.getLogger(MacOsXJavaExecutableIterable.class); /** * This is the iterator implementation that is created to walk over the possible locations for java on a Mac OS * operating system. */ private static class MacOsXJavaExecutableIterator extends AbstractJavaExecutableIterator { /** * The data source for most of the paths possible. */ private final MacOsXJavaExecutableIterable source; /** * The index that is used to track the current path set for Mac. */ private int currentIndex = -1; /** * The internal iterator supplying the possible alternative paths. */ private Iterator<Path> alternativePaths; /** * Create a new instance of this iterator. * * @param source the data source */ public MacOsXJavaExecutableIterator(@Nonnull MacOsXJavaExecutableIterable source) { super(source); this.source = source; } @Override public boolean hasNext() { if (super.hasNext()) { return true; } if ((currentIndex == -1) && (source.getJavaHomeDirectory() != null)) { return true; } if (currentIndex <= 0) { if (alternativePaths == null) { alternativePaths = getLibraryDirectoryCandidates().iterator(); } return alternativePaths.hasNext(); } return false; } @Nonnull @Override public Path next() { if (super.hasNext()) { return super.next(); } while (hasNext()) { currentIndex++; switch (currentIndex) { case 0: Path homePath = source.getJavaHomeDirectory(); if (homePath != null) { return homePath; } break; case 1: if (alternativePaths != null) { currentIndex--; return alternativePaths.next(); } default: throw new NoSuchElementException(); } } throw new NoSuchElementException(); } } /** * This flag is set to {@code true} once the java home directory was tried to be discovered. */ private boolean javaHomeDirectoryFetched; /** * The received path of the java home directory. */ @Nullable private Path javaHomeDirectory; /** * This function tries to receive the java home directory for map from /usr/libexec/java_home * * @return the located directory or {@code null} in case the locating failed */ @Nullable private Path getJavaHomeDirectory() { if (javaHomeDirectoryFetched) { return javaHomeDirectory; } javaHomeDirectoryFetched = true; LOGGER.warn("Platform independent locating tries for java executable failed. Entering MacOS mode."); try { ProcessBuilder processBuilder = new ProcessBuilder("/usr/libexec/java_home", "-v", "1.7"); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); //noinspection resource process.getOutputStream().close(); String firstLine; try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) { firstLine = reader.readLine(); } if ((firstLine == null) || (process.waitFor() != 0)) { LOGGER.error("Failed to locate valid java version."); return null; } Path resultPath = Paths.get(firstLine); if (Files.isDirectory(resultPath)) { LOGGER.warn("Java home directory located at: {}", resultPath); javaHomeDirectory = extendHomeToExecutable(resultPath); return resultPath; } } catch (@Nonnull InterruptedException | IOException e) { LOGGER.error("Error fetching java home directory.", e); } return null; } /** * This function tries to locate the Java executable within a directory. It performs a recursive search if the * executable is not at the default location. This is required because the MacOS installments of java have * sometimes a custom file structure. * * @param home the origin of the search * @return the path to the java executable or {@code null} in case the search failed */ @Nullable private static Path extendHomeToExecutable(@Nonnull Path home) { Path defaultPath = home.resolve("bin").resolve("java"); if (Files.isExecutable(defaultPath)) { return defaultPath; } try { Path[] resultFile = new Path[1]; Files.walkFileTree(home, new SimpleFileVisitor<Path>() { @Nonnull @Override public FileVisitResult visitFile(@Nonnull Path file, BasicFileAttributes attrs) throws IOException { if ("java".equals(file.getFileName().toString())) { resultFile[0] = file; return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } }); return resultFile[0]; } catch (IOException e) { LOGGER.error("Accessing the directory failed.", e); } return null; } /** * Get a list of possible candidates for the locations of java. This is a kind of last resort to locate the java * installation. If this one fails, all is over. * * @return the list of java installment candidates */ @Nonnull private static List<Path> getLibraryDirectoryCandidates() { Stream<Path> possiblePaths = Stream.empty(); try { Path systemJvmPath = Paths.get("/System/Library/Frameworks/JavaVM.framework/Versions/"); if (Files.isDirectory(systemJvmPath)) { Stream<Path> paths = Files.list(systemJvmPath).map(path -> path.resolve("Home")); possiblePaths = Stream.concat(possiblePaths, paths); } Path appletPluginPath = Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home"); if (Files.isDirectory(appletPluginPath)) { possiblePaths = Stream.concat(possiblePaths, Stream.of(appletPluginPath)); } Path libraryJvmPath = Paths.get("/Library/Java/JavaVirtualMachines/"); if (Files.isDirectory(libraryJvmPath)) { Stream<Path> paths = Files.list(libraryJvmPath) .map(path -> path.resolve("Contents").resolve("Home")); possiblePaths = Stream.concat(possiblePaths, paths); } } catch (IOException e) { LOGGER.error("Failed to resolve the directories.", e); } return possiblePaths.map(MacOsXJavaExecutableIterable::extendHomeToExecutable).collect(Collectors.toList()); } @Override public Iterator<Path> iterator() { return new MacOsXJavaExecutableIterator(this); } }