blob: e11b1929f27ae2c02872038f9763633ad1732115 [file] [log] [blame]
/*
* Copyright (c) 2015-2017 Eike Stepper (Loehne, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.oomph.jreinfo;
import org.eclipse.oomph.extractor.lib.JREData;
import org.eclipse.oomph.internal.jreinfo.JREInfoPlugin;
import org.eclipse.oomph.util.IOUtil;
import org.eclipse.oomph.util.MonitorUtil;
import org.eclipse.oomph.util.OS;
import org.eclipse.oomph.util.PropertiesUtil;
import org.eclipse.oomph.util.StringUtil;
import org.eclipse.emf.common.util.URI;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.util.NLS;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Eike Stepper
*/
public final class JREManager
{
public static final OSType OS_TYPE = determineOSType();
public static final int BITNESS = determineBitness();
public static final boolean BITNESS_CHANGEABLE = BITNESS == 64 && OS.INSTANCE.is32BitAvailable();
public static final String JAVA_EXECUTABLE = OS_TYPE == OSType.Win ? "java.exe" : "java"; //$NON-NLS-1$ //$NON-NLS-2$
public static final String JAVA_COMPILER = OS_TYPE == OSType.Win ? "javac.exe" : "javac"; //$NON-NLS-1$ //$NON-NLS-2$
public static final JREManager INSTANCE = new JREManager();
public static final int CURRENT_LTS_VERSION = Integer.valueOf(17).intValue();
private static final String PROP_DEFAULT_JRE = "oomph.default.jres"; //$NON-NLS-1$
private final List<String> javaHomes = new ArrayList<>();
private final List<JRE.Descriptor> descriptors = new ArrayList<>();
private String systemJavaHome;
private JREManager()
{
try
{
loadJavaHomes();
for (File defaultJavaHome : getDefaultJavaHomes())
{
addExtraJavaHomes(defaultJavaHome.toString(), new NullProgressMonitor());
}
}
catch (Exception ex)
{
JREInfoPlugin.INSTANCE.log(ex);
}
}
private Set<File> getDefaultJavaHomes()
{
Set<File> result = new LinkedHashSet<>();
String defaultJREs = PropertiesUtil.getProperty(PROP_DEFAULT_JRE);
if (defaultJREs != null)
{
try
{
for (String defaultJRE : defaultJREs.split(File.pathSeparator))
{
result.add(new File(defaultJRE).getCanonicalFile());
}
}
catch (IOException ex)
{
//$FALL-THROUGH$
}
}
return result;
}
private void addExtraJavaHomes(List<String> extraJavaHomes, File folder, boolean root, Set<JRE> result, IProgressMonitor monitor)
{
JREInfoPlugin.checkCancelation(monitor);
String path = folder.getAbsolutePath();
File[] childFolders = folder.listFiles(new FileFilter()
{
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory();
}
});
try
{
int children = childFolders == null ? 0 : childFolders.length;
monitor.beginTask(root ? NLS.bind(Messages.JREManager_Seraching_task, path) : "", 1 + children); //$NON-NLS-1$
monitor.subTask(path);
if (!javaHomes.contains(path) && !extraJavaHomes.contains(path))
{
File executable = new File(folder, "bin/" + JAVA_EXECUTABLE); //$NON-NLS-1$
if (executable.isFile())
{
File canonicalFolder = folder.getCanonicalFile();
JRE info = InfoManager.INSTANCE.getInfo(canonicalFolder);
if (info != null && info.isValid())
{
extraJavaHomes.add(path);
result.add(new JRE(folder, info));
}
}
}
monitor.worked(1);
for (int i = 0; i < children; i++)
{
addExtraJavaHomes(extraJavaHomes, childFolders[i], false, result, MonitorUtil.create(monitor, 1));
}
}
catch (OperationCanceledException ex)
{
throw ex;
}
catch (Exception ex)
{
//$FALL-THROUGH$
}
finally
{
monitor.done();
}
}
public synchronized Set<JRE> addExtraJavaHomes(String rootFolder, IProgressMonitor monitor)
{
Set<JRE> result = new HashSet<>();
File folder = new File(rootFolder);
if (folder.isDirectory())
{
List<String> extraJavaHomes = loadExtraJavaHomes();
addExtraJavaHomes(extraJavaHomes, folder, true, result, monitor);
if (!result.isEmpty())
{
saveExtraJavaHomes(extraJavaHomes);
}
}
return result;
}
public synchronized void removeExtraJavaHomes(String... javaHomes)
{
List<String> extraJavaHomes = loadExtraJavaHomes();
if (extraJavaHomes.removeAll(Arrays.asList(javaHomes)))
{
saveExtraJavaHomes(extraJavaHomes);
}
}
public synchronized void refresh(boolean refreshInfos)
{
if (refreshInfos)
{
InfoManager.INSTANCE.refresh();
}
loadJavaHomes();
}
public String getDefaultJRE(int bitness, String javaVersion)
{
Set<File> defaultJavaHomes = getDefaultJavaHomes();
if (!defaultJavaHomes.isEmpty())
{
JREFilter jreFilter = new JREFilter(javaVersion, bitness, null);
Map<File, JRE> jres = getJREs(jreFilter);
for (File defaultJavaHome : defaultJavaHomes)
{
if (jres.containsKey(defaultJavaHome))
{
return defaultJavaHome.toString();
}
}
}
File defaultsFile = getDefaultsFile();
Map<String, String> properties = PropertiesUtil.getProperties(defaultsFile);
String javaHome = properties.get(getDefaultsKey(bitness, javaVersion));
if (javaHome == null)
{
javaHome = properties.get(getDefaultsKey(bitness, null));
}
return javaHome;
}
public synchronized void setDefaultJRE(int bitness, String javaVersion, String javaHome)
{
File defaultsFile = getDefaultsFile();
Map<String, String> properties = PropertiesUtil.getProperties(defaultsFile);
String oldVersionSpecificJavaHome = properties.put(getDefaultsKey(bitness, javaVersion), javaHome);
String oldJavaHome = properties.put(getDefaultsKey(bitness, null), javaHome);
if (!javaHome.equals(oldVersionSpecificJavaHome) || !javaHome.equals(oldJavaHome))
{
try
{
PropertiesUtil.saveProperties(defaultsFile, properties, true);
}
catch (Exception ex)
{
// Log and exception because failing to save defaults isn't catastrophic.
JREInfoPlugin.INSTANCE.log(ex);
}
}
}
public synchronized void setJREs(Collection<JRE.Descriptor> jreDescriptors)
{
descriptors.clear();
descriptors.addAll(jreDescriptors);
}
public Map<File, JRE> getJREs()
{
return getJREs(null);
}
public synchronized JRE getSystemJRE()
{
if (systemJavaHome == null)
{
if (OS.INSTANCE.isWin())
{
// The native launcher augments the system PATH.
// It optionally prefixes the PATH with folder entries based on the JVM it has decided to use.
// These entries always use '/' instead of '\' so we can recognize the entries added by the native launcher.
List<String> folders = new ArrayList<>();
String path = System.getenv("PATH"); //$NON-NLS-1$
for (String folder : path.split(File.pathSeparator))
{
systemJavaHome = ""; //$NON-NLS-1$
if (!StringUtil.isEmpty(folder))
{
if (folder.indexOf('/') == -1)
{
folders.add(folder);
}
else
{
// This tests if we are in debug mode.
// In that case, the JRE for the selected JRE of the launcher is at the front of the list, but it uses proper '\'
// So we ignore everything before the native launcher added entries.
boolean isDebug = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments().toString().indexOf("jdwp") >= 0; //$NON-NLS-1$
if (isDebug)
{
folders.clear();
}
}
}
}
// The native launcher also adds the current working directory at the end of the path.
// We should ignore this one too.
int size = folders.size();
if (size > 0)
{
folders.remove(size - 1);
}
for (String folder : folders)
{
JREData jreData = InfoManager.testJRE(new File(folder, JAVA_EXECUTABLE).toString());
if (jreData != null)
{
systemJavaHome = jreData.getJavaHome();
break;
}
}
}
else if (OS.INSTANCE.isLinux())
{
try
{
File javaExecuable = new File("/usr/bin/java").getCanonicalFile(); //$NON-NLS-1$
JREData jreData = InfoManager.testJRE(javaExecuable.toString());
if (jreData != null)
{
systemJavaHome = jreData.getJavaHome();
}
}
catch (IOException ex)
{
//$FALL-THROUGH$
}
}
}
if (!StringUtil.isEmpty(systemJavaHome))
{
return getJREs().get(new File(systemJavaHome));
}
return null;
}
public Map<File, JRE> getJREs(JREFilter filter)
{
Set<File> folders = getJavaHomes();
List<JRE> jres = getJREs(filter, folders);
LinkedHashMap<File, JRE> result = new LinkedHashMap<>();
for (JRE jre : jres)
{
result.put(jre.getJavaHome(), jre);
}
return result;
}
public JRE[] getJREs(JREFilter filter, boolean extra)
{
Set<File> folders = toFile(extra ? loadExtraJavaHomes() : javaHomes);
List<JRE> jres = getJREs(filter, folders);
return jres.toArray(new JRE[jres.size()]);
}
public JRE[] getAllJREs(JREFilter filter)
{
List<JRE> jres = new ArrayList<>();
for (JRE.Descriptor descriptor : descriptors)
{
JRE jre = new JRE(descriptor);
if (jre.isMatch(filter))
{
jres.add(jre);
}
}
Collections.sort(jres);
jres.addAll(0, getJREs(filter).values());
return jres.toArray(new JRE[jres.size()]);
}
private synchronized Set<File> getJavaHomes()
{
Set<File> all = new HashSet<>();
all.addAll(toFile(javaHomes));
all.addAll(toFile(loadExtraJavaHomes()));
return all;
}
private void loadJavaHomes()
{
javaHomes.clear();
File installerLocation = getInstallerLocation();
JREInfo info = JREInfo.getAll();
while (info != null)
{
// Ignore the JRE that is embedded in the installation itself.
if (installerLocation == null || !isAncestor(installerLocation, new File(info.javaHome)))
{
javaHomes.add(info.javaHome);
}
info = info.next;
}
}
private boolean isAncestor(File parent, File child)
{
for (File file = IOUtil.getCanonicalFile(child); file != null; file = file.getParentFile())
{
if (parent.equals(file))
{
return true;
}
}
return false;
}
private static List<String> loadExtraJavaHomes()
{
if (getCacheFile().isFile())
{
try
{
return IOUtil.readLines(getCacheFile(), "UTF-8"); //$NON-NLS-1$
}
catch (Exception ex)
{
JREInfoPlugin.INSTANCE.log(ex);
}
}
return new ArrayList<>();
}
private static void saveExtraJavaHomes(List<String> paths)
{
try
{
IOUtil.writeLines(getCacheFile(), "UTF-8", paths); //$NON-NLS-1$
}
catch (Exception ex)
{
JREInfoPlugin.INSTANCE.log(ex);
}
}
private static File getCacheFile()
{
return new File(JREInfoPlugin.INSTANCE.getUserLocation().append("extra.txt").toOSString()); //$NON-NLS-1$
}
private static File getDefaultsFile()
{
return new File(JREInfoPlugin.INSTANCE.getUserLocation().append("defaults.properties").toOSString()); //$NON-NLS-1$
}
private static String getDefaultsKey(int bitness, String javaVersion)
{
return Integer.toString(bitness) + "/" + sanitizeKey(javaVersion); //$NON-NLS-1$
}
private static String sanitizeKey(String javaVersion)
{
if (javaVersion == null)
{
return "*"; //$NON-NLS-1$
}
return javaVersion.replace(' ', '_').replace('/', '_').replace('\\', '_').replace('=', '_');
}
private static List<JRE> getJREs(JREFilter filter, Collection<File> javaHomes)
{
List<JRE> list = new ArrayList<>();
for (File javaHome : javaHomes)
{
try
{
File canonicalJavaHome = javaHome.getCanonicalFile();
JRE info = InfoManager.INSTANCE.getInfo(canonicalJavaHome);
if (info != null && info.isValid())
{
if (filter == null || info.isMatch(filter))
{
list.add(new JRE(javaHome, info));
}
}
}
catch (IOException ex)
{
JREInfoPlugin.INSTANCE
.log(new Status(IStatus.WARNING, JREInfoPlugin.INSTANCE.getSymbolicName(), NLS.bind(Messages.JREManager_Problem_message, javaHome), ex));
}
}
Collections.sort(list);
return list;
}
private static Set<File> toFile(Collection<String> paths)
{
Set<File> result = new HashSet<>();
for (String javaHome : paths)
{
result.add(new File(javaHome));
}
return result;
}
private static OSType determineOSType()
{
try
{
String os = Platform.getOS();
if (Platform.OS_WIN32.equals(os))
{
System.loadLibrary("jreinfo.dll"); //$NON-NLS-1$
return OSType.Win;
}
if (Platform.OS_MACOSX.equals(os))
{
return OSType.Mac;
}
if (Platform.OS_LINUX.equals(os))
{
return OSType.Linux;
}
}
catch (RuntimeException ex)
{
JREInfoPlugin.INSTANCE.log(ex);
}
return OSType.Unsupported;
}
private static int determineBitness()
{
try
{
return JREData.determineBitness();
}
catch (RuntimeException ex)
{
JREInfoPlugin.INSTANCE.log(ex);
}
return 32;
}
private static File getInstallerLocation()
{
try
{
String productID = PropertiesUtil.getProductID();
if ("org.eclipse.oomph.setup.installer.product".equals(productID)) //$NON-NLS-1$
{
Location location = Platform.getInstallLocation();
if (location != null)
{
URI result = URI.createURI(FileLocator.resolve(location.getURL()).toString());
if (result.isFile())
{
if (!result.hasTrailingPathSeparator())
{
result = result.appendSegment(""); //$NON-NLS-1$
}
return IOUtil.getCanonicalFile(new File(result.toFileString()));
}
}
}
}
catch (Throwable ex)
{
//$FALL-THROUGH$
}
return null;
}
}