/*******************************************************************************
* Copyright (c) 2008 Vlad Dumitrescu and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Vlad Dumitrescu
*******************************************************************************/
package org.erlide.backend.runtimeinfo;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.erlide.core.ErlangCore;
import org.erlide.jinterface.ErlLogger;
import org.erlide.utils.PreferencesUtils;
import org.osgi.service.prefs.BackingStoreException;
import com.ericsson.otp.erlang.RuntimeVersion;
import com.google.common.collect.Lists;
public final class RuntimeInfoManager implements IPreferenceChangeListener {
private RuntimeInfo erlideRuntime;
private final Map<String, RuntimeInfo> fRuntimes = new HashMap<String, RuntimeInfo>();
private String defaultRuntimeName = "";
private final List<RuntimeInfoListener> fListeners = new ArrayList<RuntimeInfoListener>();
public RuntimeInfoManager() {
getRootPreferenceNode().addPreferenceChangeListener(this);
load();
initializeRuntimesList();
setDefaultRuntimes();
}
public synchronized Collection<RuntimeInfo> getRuntimes() {
return new ArrayList<RuntimeInfo>(fRuntimes.values());
}
public synchronized void store() {
IEclipsePreferences root = getRootPreferenceNode();
try {
root.removePreferenceChangeListener(this);
root.removeNode();
root = getRootPreferenceNode();
for (final RuntimeInfo rt : fRuntimes.values()) {
final RuntimeInfoLoader rtl = new RuntimeInfoLoader(rt);
rtl.store(root);
}
if (defaultRuntimeName != null) {
root.put("default", defaultRuntimeName);
}
if (erlideRuntime != null) {
root.put("erlide", erlideRuntime.getName());
}
try {
root.flush();
} catch (final BackingStoreException e) {
ErlLogger.warn(e);
}
root.addPreferenceChangeListener(this);
} catch (final BackingStoreException e) {
ErlLogger.warn(e);
}
}
public synchronized void load() {
fRuntimes.clear();
loadDefaultPrefs();
IEclipsePreferences root = new DefaultScope()
.getNode(ErlangCore.PLUGIN_ID + "/runtimes");
loadPrefs(root);
root = getRootPreferenceNode();
loadPrefs(root);
}
private void loadDefaultPrefs() {
final IPreferencesService ps = Platform.getPreferencesService();
final String DEFAULT_ID = "org.erlide";
final String defName = ps.getString(DEFAULT_ID, "default_name", null,
null);
final RuntimeInfo runtime = getRuntime(defName);
if (defName != null && runtime == null) {
final RuntimeInfo rt = new RuntimeInfo();
rt.setName(defName);
final String path = ps.getString(DEFAULT_ID, "default_"
+ RuntimeInfoLoader.CODE_PATH, "", null);
rt.setCodePath(PreferencesUtils.unpackList(path));
rt.setOtpHome(ps.getString(DEFAULT_ID, "default_"
+ RuntimeInfoLoader.HOME_DIR, "", null));
rt.setArgs(ps.getString(DEFAULT_ID, "default_"
+ RuntimeInfoLoader.ARGS, "", null));
final String wd = ps.getString(DEFAULT_ID, "default_"
+ RuntimeInfoLoader.WORKING_DIR, "", null);
if (wd.length() != 0) {
rt.setWorkingDir(wd);
}
rt.setManaged(ps.getBoolean(DEFAULT_ID, "default_"
+ RuntimeInfoLoader.MANAGED, true, null));
addRuntime(rt);
}
defaultRuntimeName = defName;
}
private void loadPrefs(final IEclipsePreferences root) {
final String defrt = root.get("default", null);
if (defrt != null) {
defaultRuntimeName = defrt;
}
String[] children;
try {
children = root.childrenNames();
for (final String name : children) {
final RuntimeInfo rt = new RuntimeInfo();
final RuntimeInfoLoader rtl = new RuntimeInfoLoader(rt);
rtl.load(root.node(name));
fRuntimes.put(name, rt);
}
} catch (final BackingStoreException e) {
ErlLogger.warn(e);
}
if (getDefaultRuntime() == null) {
if (defaultRuntimeName == null && fRuntimes.size() > 0) {
defaultRuntimeName = fRuntimes.values().iterator().next()
.getName();
}
}
}
protected IEclipsePreferences getRootPreferenceNode() {
return new InstanceScope().getNode(ErlangCore.PLUGIN_ID + "/runtimes");
}
public synchronized void setRuntimes(final Collection<RuntimeInfo> elements) {
fRuntimes.clear();
for (final RuntimeInfo rt : elements) {
fRuntimes.put(rt.getName(), rt);
}
notifyListeners();
}
public synchronized void addRuntime(final RuntimeInfo rt) {
if (!fRuntimes.containsKey(rt.getName())) {
fRuntimes.put(rt.getName(), rt);
}
notifyListeners();
}
public synchronized Collection<String> getRuntimeNames() {
return fRuntimes.keySet();
}
public boolean hasRuntimeWithName(final String name) {
for (final RuntimeInfo vm : fRuntimes.values()) {
if (vm.getName().equals(name)) {
return true;
}
}
return false;
}
public RuntimeInfo getRuntime(final String name) {
final RuntimeInfo rt = fRuntimes.get(name);
return rt;
}
public synchronized void removeRuntime(final String name) {
fRuntimes.remove(name);
if (erlideRuntime.getName().equals(name)) {
erlideRuntime = fRuntimes.values().iterator().next();
}
if (defaultRuntimeName.equals(name)) {
defaultRuntimeName = fRuntimes.keySet().iterator().next();
}
notifyListeners();
}
public synchronized String getDefaultRuntimeName() {
return defaultRuntimeName;
}
public synchronized void setDefaultRuntime(final String name) {
defaultRuntimeName = name;
notifyListeners();
}
private synchronized void setErlideRuntime(final RuntimeInfo runtime) {
if (runtime != null) {
runtime.setNodeName("erlide");
}
final RuntimeInfo old = erlideRuntime;
if (old == null || !old.equals(runtime)) {
erlideRuntime = runtime;
notifyListeners();
// this creates infinite recursion!
// BackendManagerImpl.getDefault().getIdeBackend().stop();
}
}
public synchronized RuntimeInfo getErlideRuntime() {
return erlideRuntime;
}
public synchronized RuntimeInfo getDefaultRuntime() {
return getRuntime(getDefaultRuntimeName());
}
@Override
public void preferenceChange(final PreferenceChangeEvent event) {
if (event.getNode().absolutePath().contains("org.erlide")) {
load();
}
}
public void addListener(final RuntimeInfoListener listener) {
if (!fListeners.contains(listener)) {
fListeners.add(listener);
}
}
public void removeListener(final RuntimeInfoListener listener) {
fListeners.remove(listener);
}
private void notifyListeners() {
for (final RuntimeInfoListener listener : fListeners) {
listener.infoChanged();
}
}
/**
* Locate runtimes with this version or newer. If exact matches exists, they
* are first in the result list. A null or empty version returns all
* runtimes.
*/
public List<RuntimeInfo> locateVersion(final String version) {
final RuntimeVersion vsn = new RuntimeVersion(version, null);
return locateVersion(vsn);
}
public List<RuntimeInfo> locateVersion(final RuntimeVersion vsn) {
final List<RuntimeInfo> result = new ArrayList<RuntimeInfo>();
for (final RuntimeInfo info : getRuntimes()) {
final RuntimeVersion v = info.getVersion();
if (v.isReleaseCompatible(vsn)) {
result.add(info);
}
}
Collections.reverse(result);
// add even newer versions, but at the end
for (final RuntimeInfo info : getRuntimes()) {
final RuntimeVersion v = info.getVersion();
if (!result.contains(info) && v.compareTo(vsn) > 0) {
result.add(info);
}
}
return result;
}
public RuntimeInfo getRuntime(final RuntimeVersion runtimeVersion,
final String runtimeName) {
final List<RuntimeInfo> vsns = locateVersion(runtimeVersion);
if (vsns.size() == 0) {
return null;
} else if (vsns.size() == 1) {
return vsns.get(0);
} else {
for (final RuntimeInfo ri : vsns) {
if (ri.getName().equals(runtimeName)) {
return ri;
}
}
return vsns.get(0);
}
}
public String[][] getAllRuntimesVersions() {
final Collection<RuntimeInfo> rs = getRuntimes();
final String[][] runtimes = new String[rs.size()][2];
final Iterator<RuntimeInfo> it = rs.iterator();
for (int i = 0; i < rs.size(); i++) {
runtimes[i][0] = it.next().getVersion().asMinor().toString();
runtimes[i][1] = runtimes[i][0];
}
return runtimes;
}
/**
* If runtime is not set, try to locate one. The first one found as below is
* set as default. All "obvious" runtimes found are stored.
* <ul>
* <li>A system property <code>erlide.runtime</code> can be set to point to
* a location.</li>
* <li>A preference in the default scope
* <code>org.erlide.core/default_runtime</code> can be set to point to a
* location.</li>
* <li>Look for existing Erlang runtimes in a few obvious places and install
* them, choosing a suitable one as default.</li>
* </ul>
*
*/
public void initializeRuntimesList() {
final Collection<RuntimeInfo> found = guessRuntimeLocations();
for (final RuntimeInfo info : found) {
addRuntime(info);
}
}
private void setDefaultRuntimes() {
final List<RuntimeInfo> list = new ArrayList<RuntimeInfo>(getRuntimes());
Collections.sort(list, new Comparator<RuntimeInfo>() {
@Override
public int compare(final RuntimeInfo o1, final RuntimeInfo o2) {
final int x = o2.getVersion().compareTo(o1.getVersion());
if (x != 0) {
return x;
}
return o2.getName().compareTo(o1.getName());
}
});
if (list.size() > 0) {
final String firstName = list.get(0).getName();
if (defaultRuntimeName == null) {
setDefaultRuntime(firstName);
}
// the erlide backend is the most recent stable version
for (final RuntimeInfo info : list) {
if (info.getVersion().isStable()) {
setErlideRuntime(info);
break;
}
}
if (erlideRuntime == null) {
setErlideRuntime(getDefaultRuntime());
}
}
}
private Collection<RuntimeInfo> guessRuntimeLocations() {
final List<RuntimeInfo> result = Lists.newArrayList();
final String[] locations = {
System.getProperty("erlide.runtime"),
new DefaultScope().getNode("org.erlide.core").get(
"default_runtime", null), "c:/program files",
"c:/program files (x86)", "c:/programs", "c:/", "c:/apps",
System.getProperty("user.home"), "/usr", "/usr/lib",
"/usr/local", "/usr/local/lib",
"/Library/Frameworks/erlang/Versions" };
for (final String loc : locations) {
final Collection<File> roots = findRuntime(loc);
for (final File root : roots) {
final RuntimeInfo rt = new RuntimeInfo();
rt.setOtpHome(root.getPath());
rt.setName(root.getName());
final IWorkspaceRoot wroot = ResourcesPlugin.getWorkspace()
.getRoot();
final String location = wroot.getLocation().toPortableString();
rt.setWorkingDir(location);
result.add(rt);
}
}
return result;
}
private static Collection<File> findRuntime(final String loc) {
final Collection<File> result = new ArrayList<File>();
if (loc == null) {
return result;
}
final File folder = new File(loc);
if (!folder.exists()) {
return result;
}
final File[] candidates = folder.listFiles(new FileFilter() {
@Override
public boolean accept(final File pathname) {
final String path = pathname.getName();
return pathname.isDirectory()
&& (path.startsWith("otp") || path.startsWith("erl")
|| path.startsWith("Erl") || path
.startsWith("R"));
}
});
for (final File f : candidates) {
if (RuntimeInfo.validateLocation(f.getPath())) {
result.add(f);
}
}
return result;
}
}