From 8a3a0f5a529a95c7511436051b63887dff158c50 Mon Sep 17 00:00:00 2001
From: Petr Mrázek <peterix@gmail.com>
Date: Thu, 8 May 2014 21:20:10 +0200
Subject: Reorganize logic code.

---
 logic/java/JavaChecker.cpp     | 124 +++++++++++++++++++++
 logic/java/JavaChecker.h       |  42 +++++++
 logic/java/JavaCheckerJob.cpp  |  47 ++++++++
 logic/java/JavaCheckerJob.h    | 100 +++++++++++++++++
 logic/java/JavaUtils.cpp       | 221 +++++++++++++++++++++++++++++++++++++
 logic/java/JavaUtils.h         |  43 ++++++++
 logic/java/JavaVersionList.cpp | 241 +++++++++++++++++++++++++++++++++++++++++
 logic/java/JavaVersionList.h   |  96 ++++++++++++++++
 8 files changed, 914 insertions(+)
 create mode 100644 logic/java/JavaChecker.cpp
 create mode 100644 logic/java/JavaChecker.h
 create mode 100644 logic/java/JavaCheckerJob.cpp
 create mode 100644 logic/java/JavaCheckerJob.h
 create mode 100644 logic/java/JavaUtils.cpp
 create mode 100644 logic/java/JavaUtils.h
 create mode 100644 logic/java/JavaVersionList.cpp
 create mode 100644 logic/java/JavaVersionList.h

(limited to 'logic/java')

diff --git a/logic/java/JavaChecker.cpp b/logic/java/JavaChecker.cpp
new file mode 100644
index 00000000..b87ee3d5
--- /dev/null
+++ b/logic/java/JavaChecker.cpp
@@ -0,0 +1,124 @@
+#include "JavaChecker.h"
+#include "MultiMC.h"
+#include <pathutils.h>
+#include <QFile>
+#include <QProcess>
+#include <QMap>
+#include <QTemporaryFile>
+
+JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
+{
+}
+
+void JavaChecker::performCheck()
+{
+	QString checkerJar = PathCombine(MMC->bin(), "jars", "JavaCheck.jar");
+
+	QStringList args = {"-jar", checkerJar};
+
+	process.reset(new QProcess());
+	process->setArguments(args);
+	process->setProgram(path);
+	process->setProcessChannelMode(QProcess::SeparateChannels);
+	QLOG_DEBUG() << "Running java checker!";
+	QLOG_DEBUG() << "Java: " + path;
+	QLOG_DEBUG() << "Args: {" + args.join("|") + "}";
+
+	connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this,
+			SLOT(finished(int, QProcess::ExitStatus)));
+	connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this,
+			SLOT(error(QProcess::ProcessError)));
+	connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
+	killTimer.setSingleShot(true);
+	killTimer.start(5000);
+	process->start();
+}
+
+void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
+{
+	killTimer.stop();
+	QProcessPtr _process;
+	_process.swap(process);
+
+	JavaCheckResult result;
+	{
+		result.path = path;
+		result.id = id;
+	}
+	QLOG_DEBUG() << "Java checker finished with status " << status << " exit code " << exitcode;
+
+	if (status == QProcess::CrashExit || exitcode == 1)
+	{
+		QLOG_DEBUG() << "Java checker failed!";
+		emit checkFinished(result);
+		return;
+	}
+
+	bool success = true;
+	QString p_stdout = _process->readAllStandardOutput();
+	QLOG_DEBUG() << p_stdout;
+
+	QMap<QString, QString> results;
+	QStringList lines = p_stdout.split("\n", QString::SkipEmptyParts);
+	for(QString line : lines)
+	{
+		line = line.trimmed();
+
+		auto parts = line.split('=', QString::SkipEmptyParts);
+		if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty())
+		{
+			success = false;
+		}
+		else
+		{
+			results.insert(parts[0], parts[1]);
+		}
+	}
+
+	if(!results.contains("os.arch") || !results.contains("java.version") || !success)
+	{
+		QLOG_DEBUG() << "Java checker failed - couldn't extract required information.";
+		emit checkFinished(result);
+		return;
+	}
+
+	auto os_arch = results["os.arch"];
+	auto java_version = results["java.version"];
+	bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
+
+
+	result.valid = true;
+	result.is_64bit = is_64;
+	result.mojangPlatform = is_64 ? "64" : "32";
+	result.realPlatform = os_arch;
+	result.javaVersion = java_version;
+	QLOG_DEBUG() << "Java checker succeeded.";
+	emit checkFinished(result);
+}
+
+void JavaChecker::error(QProcess::ProcessError err)
+{
+	if(err == QProcess::FailedToStart)
+	{
+		killTimer.stop();
+		QLOG_DEBUG() << "Java checker has failed to start.";
+		JavaCheckResult result;
+		{
+			result.path = path;
+			result.id = id;
+		}
+
+		emit checkFinished(result);
+		return;
+	}
+}
+
+void JavaChecker::timeout()
+{
+	// NO MERCY. NO ABUSE.
+	if(process)
+	{
+		QLOG_DEBUG() << "Java checker has been killed by timeout.";
+		process->kill();
+	}
+}
diff --git a/logic/java/JavaChecker.h b/logic/java/JavaChecker.h
new file mode 100644
index 00000000..e19895f7
--- /dev/null
+++ b/logic/java/JavaChecker.h
@@ -0,0 +1,42 @@
+#pragma once
+#include <QProcess>
+#include <QTimer>
+#include <memory>
+
+class JavaChecker;
+
+
+struct JavaCheckResult
+{
+	QString path;
+	QString mojangPlatform;
+	QString realPlatform;
+	QString javaVersion;
+	bool valid = false;
+	bool is_64bit = false;
+	int id;
+};
+
+typedef std::shared_ptr<QProcess> QProcessPtr;
+typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
+class JavaChecker : public QObject
+{
+	Q_OBJECT
+public:
+	explicit JavaChecker(QObject *parent = 0);
+	void performCheck();
+
+	QString path;
+	int id;
+
+signals:
+	void checkFinished(JavaCheckResult result);
+private:
+	QProcessPtr process;
+	QTimer killTimer;
+public
+slots:
+	void timeout();
+	void finished(int exitcode, QProcess::ExitStatus);
+	void error(QProcess::ProcessError);
+};
diff --git a/logic/java/JavaCheckerJob.cpp b/logic/java/JavaCheckerJob.cpp
new file mode 100644
index 00000000..b0aea758
--- /dev/null
+++ b/logic/java/JavaCheckerJob.cpp
@@ -0,0 +1,47 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JavaCheckerJob.h"
+#include "pathutils.h"
+#include "MultiMC.h"
+
+#include "logger/QsLog.h"
+
+void JavaCheckerJob::partFinished(JavaCheckResult result)
+{
+	num_finished++;
+	QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
+				<< javacheckers.size();
+	emit progress(num_finished, javacheckers.size());
+
+	javaresults.replace(result.id, result);
+
+	if (num_finished == javacheckers.size())
+	{
+		emit finished(javaresults);
+	}
+}
+
+void JavaCheckerJob::start()
+{
+	QLOG_INFO() << m_job_name.toLocal8Bit() << " started.";
+	m_running = true;
+	for (auto iter : javacheckers)
+	{
+		javaresults.append(JavaCheckResult());
+		connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
+		iter->performCheck();
+	}
+}
diff --git a/logic/java/JavaCheckerJob.h b/logic/java/JavaCheckerJob.h
new file mode 100644
index 00000000..132a92d4
--- /dev/null
+++ b/logic/java/JavaCheckerJob.h
@@ -0,0 +1,100 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QtNetwork>
+#include <QLabel>
+#include "JavaChecker.h"
+#include "logic/tasks/ProgressProvider.h"
+
+class JavaCheckerJob;
+typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
+
+class JavaCheckerJob : public ProgressProvider
+{
+	Q_OBJECT
+public:
+	explicit JavaCheckerJob(QString job_name) : ProgressProvider(), m_job_name(job_name) {};
+
+	bool addJavaCheckerAction(JavaCheckerPtr base)
+	{
+		javacheckers.append(base);
+		total_progress++;
+		// if this is already running, the action needs to be started right away!
+		if (isRunning())
+		{
+			emit progress(current_progress, total_progress);
+			connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
+
+			base->performCheck();
+		}
+		return true;
+	}
+
+	JavaCheckerPtr operator[](int index)
+	{
+		return javacheckers[index];
+	}
+	;
+	JavaCheckerPtr first()
+	{
+		if (javacheckers.size())
+			return javacheckers[0];
+		return JavaCheckerPtr();
+	}
+	int size() const
+	{
+		return javacheckers.size();
+	}
+	virtual void getProgress(qint64 &current, qint64 &total)
+	{
+		current = current_progress;
+		total = total_progress;
+	}
+	;
+	virtual QString getStatus() const
+	{
+		return m_job_name;
+	}
+	;
+	virtual bool isRunning() const
+	{
+		return m_running;
+	}
+	;
+
+signals:
+	void started();
+	void progress(int current, int total);
+	void finished(QList<JavaCheckResult>);
+public
+slots:
+	virtual void start();
+	// FIXME: implement
+	virtual void abort() {};
+private
+slots:
+	void partFinished(JavaCheckResult result);
+
+private:
+	QString m_job_name;
+	QList<JavaCheckerPtr> javacheckers;
+	QList<JavaCheckResult> javaresults;
+	qint64 current_progress = 0;
+	qint64 total_progress = 0;
+	int num_finished = 0;
+	bool m_running = false;
+};
diff --git a/logic/java/JavaUtils.cpp b/logic/java/JavaUtils.cpp
new file mode 100644
index 00000000..1be93a9b
--- /dev/null
+++ b/logic/java/JavaUtils.cpp
@@ -0,0 +1,221 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QStringList>
+#include <QString>
+#include <QDir>
+#include <QStringList>
+
+#include <setting.h>
+#include <pathutils.h>
+
+#include "MultiMC.h"
+
+#include "logger/QsLog.h"
+#include "logic/java/JavaUtils.h"
+#include "logic/java/JavaCheckerJob.h"
+#include "logic/java/JavaVersionList.h"
+
+JavaUtils::JavaUtils()
+{
+}
+
+JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
+{
+	JavaVersionPtr javaVersion(new JavaVersion());
+
+	javaVersion->id = id;
+	javaVersion->arch = arch;
+	javaVersion->path = path;
+
+	return javaVersion;
+}
+
+JavaVersionPtr JavaUtils::GetDefaultJava()
+{
+	JavaVersionPtr javaVersion(new JavaVersion());
+
+	javaVersion->id = "java";
+	javaVersion->arch = "unknown";
+	javaVersion->path = "java";
+
+	return javaVersion;
+}
+
+#if WINDOWS
+QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
+{
+	QList<JavaVersionPtr> javas;
+
+	QString archType = "unknown";
+	if (keyType == KEY_WOW64_64KEY)
+		archType = "64";
+	else if (keyType == KEY_WOW64_32KEY)
+		archType = "32";
+
+	HKEY jreKey;
+	if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0,
+					  KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS)
+	{
+		// Read the current type version from the registry.
+		// This will be used to find any key that contains the JavaHome value.
+		char *value = new char[0];
+		DWORD valueSz = 0;
+		if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
+			ERROR_MORE_DATA)
+		{
+			value = new char[valueSz];
+			RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
+		}
+
+		QString recommended = value;
+
+		TCHAR subKeyName[255];
+		DWORD subKeyNameSize, numSubKeys, retCode;
+
+		// Get the number of subkeys
+		RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
+						NULL, NULL);
+
+		// Iterate until RegEnumKeyEx fails
+		if (numSubKeys > 0)
+		{
+			for (int i = 0; i < numSubKeys; i++)
+			{
+				subKeyNameSize = 255;
+				retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
+									   NULL);
+				if (retCode == ERROR_SUCCESS)
+				{
+					// Now open the registry key for the version that we just got.
+					QString newKeyName = keyName + "\\" + subKeyName;
+
+					HKEY newKey;
+					if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
+									 KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
+					{
+						// Read the JavaHome value to find where Java is installed.
+						value = new char[0];
+						valueSz = 0;
+						if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
+											&valueSz) == ERROR_MORE_DATA)
+						{
+							value = new char[valueSz];
+							RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
+											&valueSz);
+
+							// Now, we construct the version object and add it to the list.
+							JavaVersionPtr javaVersion(new JavaVersion());
+
+							javaVersion->id = subKeyName;
+							javaVersion->arch = archType;
+							javaVersion->path =
+								QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe");
+							javas.append(javaVersion);
+						}
+
+						RegCloseKey(newKey);
+					}
+				}
+			}
+		}
+
+		RegCloseKey(jreKey);
+	}
+
+	return javas;
+}
+
+QList<QString> JavaUtils::FindJavaPaths()
+{
+	QList<JavaVersionPtr> java_candidates;
+
+	QList<JavaVersionPtr> JRE64s = this->FindJavaFromRegistryKey(
+		KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
+	QList<JavaVersionPtr> JDK64s = this->FindJavaFromRegistryKey(
+		KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
+	QList<JavaVersionPtr> JRE32s = this->FindJavaFromRegistryKey(
+		KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
+	QList<JavaVersionPtr> JDK32s = this->FindJavaFromRegistryKey(
+		KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
+
+	java_candidates.append(JRE64s);
+	java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/java.exe"));
+	java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/java.exe"));
+	java_candidates.append(JDK64s);
+	java_candidates.append(JRE32s);
+	java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/java.exe"));
+	java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/java.exe"));
+	java_candidates.append(JDK32s);
+	java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
+
+	QList<QString> candidates;
+	for(JavaVersionPtr java_candidate : java_candidates)
+	{
+		if(!candidates.contains(java_candidate->path))
+		{
+			candidates.append(java_candidate->path);
+		}
+	}
+
+	return candidates;
+}
+
+#elif OSX
+QList<QString> JavaUtils::FindJavaPaths()
+{
+	QList<QString> javas;
+	javas.append(this->GetDefaultJava()->path);
+	javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java");
+	javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
+	javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
+	QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
+	QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+	foreach (const QString &java, libraryJVMJavas) {
+		javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
+		javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
+	}
+	QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
+	QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+	foreach (const QString &java, systemLibraryJVMJavas) {
+		javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
+		javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
+	}
+	return javas;
+}
+
+#elif LINUX
+QList<QString> JavaUtils::FindJavaPaths()
+{
+	QLOG_INFO() << "Linux Java detection incomplete - defaulting to \"java\"";
+
+	QList<QString> javas;
+	javas.append(this->GetDefaultJava()->path);
+    javas.append("/opt/java/bin/java");
+    javas.append("/usr/bin/java");
+
+	return javas;
+}
+#else
+QList<QString> JavaUtils::FindJavaPaths()
+{
+	QLOG_INFO() << "Unknown operating system build - defaulting to \"java\"";
+
+	QList<QString> javas;
+	javas.append(this->GetDefaultJava()->path);
+
+	return javas;
+}
+#endif
diff --git a/logic/java/JavaUtils.h b/logic/java/JavaUtils.h
new file mode 100644
index 00000000..af92100f
--- /dev/null
+++ b/logic/java/JavaUtils.h
@@ -0,0 +1,43 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QStringList>
+#include <QWidget>
+
+#include <osutils.h>
+#include "JavaCheckerJob.h"
+#include "JavaChecker.h"
+#include "JavaVersionList.h"
+
+#if WINDOWS
+#include <windows.h>
+#endif
+
+class JavaUtils : public QObject
+{
+	Q_OBJECT
+public:
+	JavaUtils();
+
+	JavaVersionPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
+	QList<QString> FindJavaPaths();
+	JavaVersionPtr GetDefaultJava();
+
+#if WINDOWS
+	QList<JavaVersionPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
+#endif
+};
diff --git a/logic/java/JavaVersionList.cpp b/logic/java/JavaVersionList.cpp
new file mode 100644
index 00000000..dcb6ced6
--- /dev/null
+++ b/logic/java/JavaVersionList.cpp
@@ -0,0 +1,241 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QtNetwork>
+#include <QtXml>
+#include <QRegExp>
+
+#include "MultiMC.h"
+#include "logger/QsLog.h"
+
+#include "logic/java/JavaVersionList.h"
+#include "logic/java/JavaCheckerJob.h"
+#include "logic/java/JavaUtils.h"
+
+JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent)
+{
+}
+
+Task *JavaVersionList::getLoadTask()
+{
+	return new JavaListLoadTask(this);
+}
+
+const BaseVersionPtr JavaVersionList::at(int i) const
+{
+	return m_vlist.at(i);
+}
+
+bool JavaVersionList::isLoaded()
+{
+	return m_loaded;
+}
+
+int JavaVersionList::count() const
+{
+	return m_vlist.count();
+}
+
+int JavaVersionList::columnCount(const QModelIndex &parent) const
+{
+	return 3;
+}
+
+QVariant JavaVersionList::data(const QModelIndex &index, int role) const
+{
+	if (!index.isValid())
+		return QVariant();
+
+	if (index.row() > count())
+		return QVariant();
+
+	auto version = std::dynamic_pointer_cast<JavaVersion>(m_vlist[index.row()]);
+	switch (role)
+	{
+	case Qt::DisplayRole:
+		switch (index.column())
+		{
+		case 0:
+			return version->id;
+
+		case 1:
+			return version->arch;
+
+		case 2:
+			return version->path;
+
+		default:
+			return QVariant();
+		}
+
+	case Qt::ToolTipRole:
+		return version->descriptor();
+
+	case VersionPointerRole:
+		return qVariantFromValue(m_vlist[index.row()]);
+
+	default:
+		return QVariant();
+	}
+}
+
+QVariant JavaVersionList::headerData(int section, Qt::Orientation orientation, int role) const
+{
+	switch (role)
+	{
+	case Qt::DisplayRole:
+		switch (section)
+		{
+		case 0:
+			return "Version";
+
+		case 1:
+			return "Arch";
+
+		case 2:
+			return "Path";
+
+		default:
+			return QVariant();
+		}
+
+	case Qt::ToolTipRole:
+		switch (section)
+		{
+		case 0:
+			return "The name of the version.";
+
+		case 1:
+			return "The architecture this version is for.";
+
+		case 2:
+			return "Path to this Java version.";
+
+		default:
+			return QVariant();
+		}
+
+	default:
+		return QVariant();
+	}
+}
+
+BaseVersionPtr JavaVersionList::getTopRecommended() const
+{
+	auto first = m_vlist.first();
+	if(first != nullptr)
+	{
+		return first;
+	}
+	else
+	{
+		return BaseVersionPtr();
+	}
+}
+
+void JavaVersionList::updateListData(QList<BaseVersionPtr> versions)
+{
+	beginResetModel();
+	m_vlist = versions;
+	m_loaded = true;
+	endResetModel();
+	// NOW SORT!!
+	// sort();
+}
+
+void JavaVersionList::sort()
+{
+	// NO-OP for now
+}
+
+JavaListLoadTask::JavaListLoadTask(JavaVersionList *vlist) : Task()
+{
+	m_list = vlist;
+	m_currentRecommended = NULL;
+}
+
+JavaListLoadTask::~JavaListLoadTask()
+{
+}
+
+void JavaListLoadTask::executeTask()
+{
+	setStatus(tr("Detecting Java installations..."));
+
+	JavaUtils ju;
+	QList<QString> candidate_paths = ju.FindJavaPaths();
+
+	m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
+	connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
+	connect(m_job.get(), SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int)));
+
+	QLOG_DEBUG() << "Probing the following Java paths: ";
+	int id = 0;
+	for(QString candidate : candidate_paths)
+	{
+		QLOG_DEBUG() << " " << candidate;
+
+		auto candidate_checker = new JavaChecker();
+		candidate_checker->path = candidate;
+		candidate_checker->id = id;
+		m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
+
+		id++;
+	}
+
+	m_job->start();
+}
+
+void JavaListLoadTask::checkerProgress(int current, int total)
+{
+	float progress = (current * 100.0) / total;
+	this->setProgress((int) progress);
+}
+
+void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
+{
+	QList<JavaVersionPtr> candidates;
+
+	QLOG_DEBUG() << "Found the following valid Java installations:";
+	for(JavaCheckResult result : results)
+	{
+		if(result.valid)
+		{
+			JavaVersionPtr javaVersion(new JavaVersion());
+
+			javaVersion->id = result.javaVersion;
+			javaVersion->arch = result.mojangPlatform;
+			javaVersion->path = result.path;
+			candidates.append(javaVersion);
+
+			QLOG_DEBUG() << " " << javaVersion->id << javaVersion->arch << javaVersion->path;
+		}
+	}
+
+	QList<BaseVersionPtr> javas_bvp;
+	for (auto java : candidates)
+	{
+		//QLOG_INFO() << java->id << java->arch << " at " << java->path;
+		BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
+
+		if (bp_java)
+		{
+			javas_bvp.append(java);
+		}
+	}
+
+	m_list->updateListData(javas_bvp);
+	emitSucceeded();
+}
diff --git a/logic/java/JavaVersionList.h b/logic/java/JavaVersionList.h
new file mode 100644
index 00000000..a46f33a2
--- /dev/null
+++ b/logic/java/JavaVersionList.h
@@ -0,0 +1,96 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QAbstractListModel>
+
+#include "logic/BaseVersionList.h"
+#include "logic/tasks/Task.h"
+#include "logic/java/JavaCheckerJob.h"
+
+class JavaListLoadTask;
+
+struct JavaVersion : public BaseVersion
+{
+	virtual QString descriptor()
+	{
+		return id;
+	}
+
+	virtual QString name()
+	{
+		return id;
+	}
+
+	virtual QString typeString() const
+	{
+		return arch;
+	}
+
+	QString id;
+	QString arch;
+	QString path;
+};
+
+typedef std::shared_ptr<JavaVersion> JavaVersionPtr;
+
+class JavaVersionList : public BaseVersionList
+{
+	Q_OBJECT
+public:
+	explicit JavaVersionList(QObject *parent = 0);
+
+	virtual Task *getLoadTask();
+	virtual bool isLoaded();
+	virtual const BaseVersionPtr at(int i) const;
+	virtual int count() const;
+	virtual void sort();
+
+	virtual BaseVersionPtr getTopRecommended() const;
+
+	virtual QVariant data(const QModelIndex &index, int role) const;
+	virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+	virtual int columnCount(const QModelIndex &parent) const;
+
+public
+slots:
+	virtual void updateListData(QList<BaseVersionPtr> versions);
+
+protected:
+	QList<BaseVersionPtr> m_vlist;
+
+	bool m_loaded = false;
+};
+
+class JavaListLoadTask : public Task
+{
+	Q_OBJECT
+
+public:
+	explicit JavaListLoadTask(JavaVersionList *vlist);
+	~JavaListLoadTask();
+
+	virtual void executeTask();
+public slots:
+	void javaCheckerFinished(QList<JavaCheckResult> results);
+	void checkerProgress(int current, int total);
+
+protected:
+	std::shared_ptr<JavaCheckerJob> m_job;
+	JavaVersionList *m_list;
+	JavaVersion *m_currentRecommended;
+};
-- 
cgit