ProjectExplorer: Add a SimpleRunControl implementation

Essentially a RunControl with and ApplicationLauncher member like it is
used directly or in disguise in the LocalApplicationRunControl, Nim and
Python. Extenting that to RemoteLinux/Qnx is possible, but left to the
next patch.

Change-Id: I91b3199d3d6a418fe4e5be7a5d61689c581a5121
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
hjk
2017-03-10 09:05:52 +01:00
parent a116529177
commit 30b4955ed7
9 changed files with 130 additions and 343 deletions

View File

@@ -29,7 +29,6 @@ HEADERS += \
project/nimcompilerbuildstepfactory.h \ project/nimcompilerbuildstepfactory.h \
project/nimcompilercleanstepfactory.h \ project/nimcompilercleanstepfactory.h \
project/nimbuildconfigurationwidget.h \ project/nimbuildconfigurationwidget.h \
project/nimruncontrol.h \
project/nimruncontrolfactory.h \ project/nimruncontrolfactory.h \
editor/nimeditorfactory.h \ editor/nimeditorfactory.h \
settings/nimcodestylesettingspage.h \ settings/nimcodestylesettingspage.h \
@@ -59,7 +58,6 @@ SOURCES += \
project/nimcompilerbuildstepfactory.cpp \ project/nimcompilerbuildstepfactory.cpp \
project/nimcompilercleanstepfactory.cpp \ project/nimcompilercleanstepfactory.cpp \
project/nimbuildconfigurationwidget.cpp \ project/nimbuildconfigurationwidget.cpp \
project/nimruncontrol.cpp \
project/nimruncontrolfactory.cpp \ project/nimruncontrolfactory.cpp \
editor/nimeditorfactory.cpp \ editor/nimeditorfactory.cpp \
settings/nimcodestylesettingspage.cpp \ settings/nimcodestylesettingspage.cpp \

View File

@@ -49,7 +49,6 @@ QtcPlugin {
"nimrunconfiguration.h", "nimrunconfiguration.cpp", "nimrunconfiguration.h", "nimrunconfiguration.cpp",
"nimrunconfigurationfactory.h", "nimrunconfigurationfactory.cpp", "nimrunconfigurationfactory.h", "nimrunconfigurationfactory.cpp",
"nimrunconfigurationwidget.h", "nimrunconfigurationwidget.cpp", "nimrunconfigurationwidget.h", "nimrunconfigurationwidget.cpp",
"nimruncontrol.h", "nimruncontrol.cpp",
"nimruncontrolfactory.h", "nimruncontrolfactory.cpp", "nimruncontrolfactory.h", "nimruncontrolfactory.cpp",
"nimtoolchain.h", "nimtoolchain.cpp", "nimtoolchain.h", "nimtoolchain.cpp",
"nimtoolchainfactory.h", "nimtoolchainfactory.cpp", "nimtoolchainfactory.h", "nimtoolchainfactory.cpp",

View File

@@ -1,92 +0,0 @@
/****************************************************************************
**
** Copyright (C) Filippo Cucchetto <filippocucchetto@gmail.com>
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "nimruncontrol.h"
#include "nimrunconfiguration.h"
#include <projectexplorer/runnables.h>
#include <utils/qtcprocess.h>
#include <utils/outputformat.h>
#include <QDir>
using namespace ProjectExplorer;
using namespace Utils;
namespace Nim {
NimRunControl::NimRunControl(NimRunConfiguration *rc, Core::Id mode)
: RunControl(rc, mode)
, m_runnable(rc->runnable().as<StandardRunnable>())
{
connect(&m_applicationLauncher, &ApplicationLauncher::appendMessage,
this, &NimRunControl::slotAppendMessage);
connect(&m_applicationLauncher, &ApplicationLauncher::processStarted,
this, &NimRunControl::processStarted);
connect(&m_applicationLauncher, &ApplicationLauncher::processExited,
this, &NimRunControl::processExited);
}
void NimRunControl::start()
{
reportApplicationStart();
m_applicationLauncher.start(m_runnable);
setApplicationProcessHandle(m_applicationLauncher.applicationPID());
bringApplicationToForeground();
}
ProjectExplorer::RunControl::StopResult NimRunControl::stop()
{
m_applicationLauncher.stop();
return StoppedSynchronously;
}
void NimRunControl::processStarted()
{
// Console processes only know their pid after being started
setApplicationProcessHandle(m_applicationLauncher.applicationPID());
}
void NimRunControl::processExited(int exitCode, QProcess::ExitStatus status)
{
setApplicationProcessHandle(ProcessHandle());
QString msg;
if (status == QProcess::CrashExit) {
msg = tr("%1 crashed")
.arg(QDir::toNativeSeparators(m_runnable.executable));
} else {
msg = tr("%1 exited with code %2")
.arg(QDir::toNativeSeparators(m_runnable.executable)).arg(exitCode);
}
appendMessage(msg + QLatin1Char('\n'), NormalMessageFormat);
reportApplicationStop();
}
void NimRunControl::slotAppendMessage(const QString &err, OutputFormat format)
{
appendMessage(err, format);
}
}

View File

@@ -1,56 +0,0 @@
/****************************************************************************
**
** Copyright (C) Filippo Cucchetto <filippocucchetto@gmail.com>
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <projectexplorer/runconfiguration.h>
#include <projectexplorer/runnables.h>
#include <QCoreApplication>
namespace Nim {
class NimRunConfiguration;
class NimRunControl : public ProjectExplorer::RunControl
{
Q_DECLARE_TR_FUNCTIONS(Nim::NimRunControl)
public:
NimRunControl(NimRunConfiguration *runConfiguration, Core::Id mode);
void start() override;
StopResult stop() override;
private:
void processStarted();
void processExited(int exitCode, QProcess::ExitStatus status);
void slotAppendMessage(const QString &err, Utils::OutputFormat isError);
ProjectExplorer::ApplicationLauncher m_applicationLauncher;
ProjectExplorer::StandardRunnable m_runnable;
};
}

View File

@@ -25,7 +25,6 @@
#include "nimruncontrolfactory.h" #include "nimruncontrolfactory.h"
#include "nimrunconfiguration.h" #include "nimrunconfiguration.h"
#include "nimruncontrol.h"
namespace Nim { namespace Nim {
@@ -39,7 +38,7 @@ ProjectExplorer::RunControl *NimRunControlFactory::create(ProjectExplorer::RunCo
{ {
Q_UNUSED(errorMessage) Q_UNUSED(errorMessage)
QTC_ASSERT(canRun(runConfiguration, mode), return 0); QTC_ASSERT(canRun(runConfiguration, mode), return 0);
return new NimRunControl(static_cast<NimRunConfiguration *>(runConfiguration), mode); return new ProjectExplorer::SimpleRunControl(runConfiguration, mode);
} }
} }

View File

@@ -25,7 +25,6 @@
#include "localapplicationruncontrol.h" #include "localapplicationruncontrol.h"
#include "runnables.h" #include "runnables.h"
#include "environmentaspect.h"
#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/kitinformation.h> #include <projectexplorer/kitinformation.h>
@@ -34,91 +33,11 @@
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QDir>
using namespace Utils; using namespace Utils;
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { namespace Internal {
class LocalApplicationRunControl : public RunControl
{
Q_OBJECT
public:
LocalApplicationRunControl(RunConfiguration *runConfiguration, Core::Id mode);
void start() override;
StopResult stop() override;
private:
void processStarted();
void processExited(int exitCode, QProcess::ExitStatus status);
ApplicationLauncher m_applicationLauncher;
};
LocalApplicationRunControl::LocalApplicationRunControl(RunConfiguration *rc, Core::Id mode)
: RunControl(rc, mode)
{
setRunnable(rc->runnable());
setIcon(Utils::Icons::RUN_SMALL_TOOLBAR);
connect(&m_applicationLauncher, &ApplicationLauncher::appendMessage,
this, static_cast<void(RunControl::*)(const QString &, Utils::OutputFormat)>(&RunControl::appendMessage));
connect(&m_applicationLauncher, &ApplicationLauncher::processStarted,
this, &LocalApplicationRunControl::processStarted);
connect(&m_applicationLauncher, &ApplicationLauncher::processExited,
this, &LocalApplicationRunControl::processExited);
}
void LocalApplicationRunControl::start()
{
QTC_ASSERT(runnable().is<StandardRunnable>(), return);
auto r = runnable().as<StandardRunnable>();
reportApplicationStart();
if (r.executable.isEmpty()) {
appendMessage(tr("No executable specified.") + QLatin1Char('\n'), Utils::ErrorMessageFormat);
reportApplicationStop();
} else if (!QFileInfo::exists(r.executable)) {
appendMessage(tr("Executable %1 does not exist.")
.arg(QDir::toNativeSeparators(r.executable)) + QLatin1Char('\n'),
Utils::ErrorMessageFormat);
reportApplicationStop();
} else {
QString msg = tr("Starting %1...").arg(QDir::toNativeSeparators(r.executable)) + QLatin1Char('\n');
appendMessage(msg, Utils::NormalMessageFormat);
m_applicationLauncher.start(r);
setApplicationProcessHandle(m_applicationLauncher.applicationPID());
}
}
LocalApplicationRunControl::StopResult LocalApplicationRunControl::stop()
{
m_applicationLauncher.stop();
return StoppedSynchronously;
}
void LocalApplicationRunControl::processStarted()
{
// Console processes only know their pid after being started
setApplicationProcessHandle(m_applicationLauncher.applicationPID());
bringApplicationToForeground();
}
void LocalApplicationRunControl::processExited(int exitCode, QProcess::ExitStatus status)
{
QString msg;
QString exe = runnable().as<StandardRunnable>().executable;
if (status == QProcess::CrashExit)
msg = tr("%1 crashed.").arg(QDir::toNativeSeparators(exe));
else
msg = tr("%1 exited with code %2").arg(QDir::toNativeSeparators(exe)).arg(exitCode);
appendMessage(msg + QLatin1Char('\n'), Utils::NormalMessageFormat);
reportApplicationStop();
}
// LocalApplicationRunControlFactory
static bool isLocal(RunConfiguration *runConfiguration) static bool isLocal(RunConfiguration *runConfiguration)
{ {
Target *target = runConfiguration ? runConfiguration->target() : nullptr; Target *target = runConfiguration ? runConfiguration->target() : nullptr;
@@ -142,10 +61,8 @@ bool LocalApplicationRunControlFactory::canRun(RunConfiguration *runConfiguratio
RunControl *LocalApplicationRunControlFactory::create(RunConfiguration *runConfiguration, Core::Id mode, QString *errorMessage) RunControl *LocalApplicationRunControlFactory::create(RunConfiguration *runConfiguration, Core::Id mode, QString *errorMessage)
{ {
Q_UNUSED(errorMessage) Q_UNUSED(errorMessage)
return new LocalApplicationRunControl(runConfiguration, mode); return new SimpleRunControl(runConfiguration, mode);
} }
} // namespace Internal } // namespace Internal
} // namespace ProjectExplorer } // namespace ProjectExplorer
#include "localapplicationruncontrol.moc"

View File

@@ -32,18 +32,22 @@
#include "buildconfiguration.h" #include "buildconfiguration.h"
#include "environmentaspect.h" #include "environmentaspect.h"
#include "kitinformation.h" #include "kitinformation.h"
#include "runnables.h"
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/outputformatter.h>
#include <utils/checkablemessagebox.h> #include <utils/checkablemessagebox.h>
#include <utils/outputformatter.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/icontext.h> #include <coreplugin/icontext.h>
#include <QTimer> #include <QDir>
#include <QPushButton> #include <QPushButton>
#include <QTimer>
#ifdef Q_OS_OSX #ifdef Q_OS_OSX
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
@@ -785,4 +789,89 @@ bool Runnable::canReUseOutputPane(const Runnable &other) const
return d ? d->canReUseOutputPane(other.d) : (other.d.get() == 0); return d ? d->canReUseOutputPane(other.d) : (other.d.get() == 0);
} }
// SimpleRunControlPrivate
namespace Internal {
class SimpleRunControlPrivate
{
public:
ApplicationLauncher m_launcher;
};
} // Internal
SimpleRunControl::SimpleRunControl(RunConfiguration *runConfiguration, Core::Id mode)
: RunControl(runConfiguration, mode), d(new Internal::SimpleRunControlPrivate)
{
setRunnable(runConfiguration->runnable());
setIcon(Utils::Icons::RUN_SMALL_TOOLBAR);
}
SimpleRunControl::~SimpleRunControl()
{
delete d;
}
ApplicationLauncher &SimpleRunControl::applicationLauncher()
{
return d->m_launcher;
}
void SimpleRunControl::start()
{
reportApplicationStart();
d->m_launcher.disconnect(this);
connect(&d->m_launcher, &ApplicationLauncher::appendMessage,
this, static_cast<void(RunControl::*)(const QString &, Utils::OutputFormat)>(&RunControl::appendMessage));
connect(&d->m_launcher, &ApplicationLauncher::processStarted,
this, &SimpleRunControl::onProcessStarted);
connect(&d->m_launcher, &ApplicationLauncher::processExited,
this, &SimpleRunControl::onProcessFinished);
QTC_ASSERT(runnable().is<StandardRunnable>(), return);
auto r = runnable().as<StandardRunnable>();
if (r.executable.isEmpty()) {
appendMessage(RunControl::tr("No executable specified.") + QLatin1Char('\n'), Utils::ErrorMessageFormat);
reportApplicationStop();
} else if (!QFileInfo::exists(r.executable)) {
appendMessage(RunControl::tr("Executable %1 does not exist.")
.arg(QDir::toNativeSeparators(r.executable)) + QLatin1Char('\n'),
Utils::ErrorMessageFormat);
reportApplicationStop();
} else {
QString msg = RunControl::tr("Starting %1...").arg(QDir::toNativeSeparators(r.executable)) + QLatin1Char('\n');
appendMessage(msg, Utils::NormalMessageFormat);
d->m_launcher.start(r);
setApplicationProcessHandle(d->m_launcher.applicationPID());
}
}
RunControl::StopResult SimpleRunControl::stop()
{
d->m_launcher.stop();
return StoppedSynchronously;
}
void SimpleRunControl::onProcessStarted()
{
// Console processes only know their pid after being started
setApplicationProcessHandle(d->m_launcher.applicationPID());
bringApplicationToForeground();
}
void SimpleRunControl::onProcessFinished(int exitCode, QProcess::ExitStatus status)
{
QString msg;
QString exe = runnable().as<StandardRunnable>().executable;
if (status == QProcess::CrashExit)
msg = tr("%1 crashed.").arg(QDir::toNativeSeparators(exe));
else
msg = tr("%1 exited with code %2").arg(QDir::toNativeSeparators(exe)).arg(exitCode);
appendMessage(msg + QLatin1Char('\n'), Utils::NormalMessageFormat);
reportApplicationStop();
}
} // namespace ProjectExplorer } // namespace ProjectExplorer

View File

@@ -51,7 +51,10 @@ class RunConfigWidget;
class RunControl; class RunControl;
class Target; class Target;
namespace Internal { class RunControlPrivate; } namespace Internal {
class RunControlPrivate;
class SimpleRunControlPrivate;
} // Internal
/** /**
* An interface for a hunk of global or per-project * An interface for a hunk of global or per-project
@@ -414,4 +417,21 @@ private:
Internal::RunControlPrivate *d; Internal::RunControlPrivate *d;
}; };
class PROJECTEXPLORER_EXPORT SimpleRunControl : public RunControl
{
public:
SimpleRunControl(RunConfiguration *runConfiguration, Core::Id mode);
~SimpleRunControl();
ApplicationLauncher &applicationLauncher();
void start() override;
StopResult stop() override;
virtual void onProcessStarted();
virtual void onProcessFinished(int exitCode, QProcess::ExitStatus status);
private:
Internal::SimpleRunControlPrivate *d;
};
} // namespace ProjectExplorer } // namespace ProjectExplorer

View File

@@ -197,6 +197,7 @@ public:
bool fromMap(const QVariantMap &map) override; bool fromMap(const QVariantMap &map) override;
bool isEnabled() const override { return m_enabled; } bool isEnabled() const override { return m_enabled; }
QString disabledReason() const override; QString disabledReason() const override;
Runnable runnable() const override;
bool supportsDebugger() const { return true; } bool supportsDebugger() const { return true; }
QString mainScript() const { return m_mainScript; } QString mainScript() const { return m_mainScript; }
@@ -215,28 +216,6 @@ private:
bool m_enabled; bool m_enabled;
}; };
class PythonRunControl : public RunControl
{
Q_OBJECT
public:
PythonRunControl(PythonRunConfiguration *runConfiguration, Core::Id mode);
void start() override;
StopResult stop() override;
private:
void processStarted();
void processExited(int exitCode, QProcess::ExitStatus status);
void slotAppendMessage(const QString &err, Utils::OutputFormat isError);
ApplicationLauncher m_applicationLauncher;
QString m_interpreter;
QString m_mainScript;
QString m_commandLineArguments;
Utils::Environment m_environment;
ApplicationLauncher::Mode m_runMode;
};
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
PythonRunConfiguration::PythonRunConfiguration(Target *parent, Core::Id id) : PythonRunConfiguration::PythonRunConfiguration(Target *parent, Core::Id id) :
@@ -307,6 +286,17 @@ QString PythonRunConfiguration::disabledReason() const
return QString(); return QString();
} }
Runnable PythonRunConfiguration::runnable() const
{
StandardRunnable r;
QtcProcess::addArg(&r.commandLineArguments, m_mainScript);
QtcProcess::addArgs(&r.commandLineArguments, extraAspect<ArgumentsAspect>()->arguments());
r.executable = m_interpreter;
r.runMode = extraAspect<TerminalAspect>()->runMode();
r.environment = extraAspect<EnvironmentAspect>()->environment();
return r;
}
QString PythonRunConfiguration::arguments() const QString PythonRunConfiguration::arguments() const
{ {
auto aspect = extraAspect<ArgumentsAspect>(); auto aspect = extraAspect<ArgumentsAspect>();
@@ -732,95 +722,18 @@ public:
bool PythonRunControlFactory::canRun(RunConfiguration *runConfiguration, Core::Id mode) const bool PythonRunControlFactory::canRun(RunConfiguration *runConfiguration, Core::Id mode) const
{ {
return mode == ProjectExplorer::Constants::NORMAL_RUN_MODE auto rc = dynamic_cast<PythonRunConfiguration *>(runConfiguration);
&& dynamic_cast<PythonRunConfiguration *>(runConfiguration); return mode == ProjectExplorer::Constants::NORMAL_RUN_MODE && rc && !rc->interpreter().isEmpty();
} }
RunControl *PythonRunControlFactory::create(RunConfiguration *runConfiguration, Core::Id mode, QString *errorMessage) RunControl *PythonRunControlFactory::create(RunConfiguration *runConfiguration, Core::Id mode, QString *errorMessage)
{ {
Q_UNUSED(errorMessage) Q_UNUSED(errorMessage)
QTC_ASSERT(canRun(runConfiguration, mode), return 0); QTC_ASSERT(canRun(runConfiguration, mode), return 0);
return new PythonRunControl(static_cast<PythonRunConfiguration *>(runConfiguration), mode); return new SimpleRunControl(runConfiguration, mode);
} }
// PythonRunControl // PythonRunConfigurationWidget
PythonRunControl::PythonRunControl(PythonRunConfiguration *rc, Core::Id mode)
: RunControl(rc, mode)
{
setIcon(Utils::Icons::RUN_SMALL_TOOLBAR);
m_interpreter = rc->interpreter();
m_mainScript = rc->mainScript();
m_runMode = rc->extraAspect<TerminalAspect>()->runMode();
m_commandLineArguments = rc->extraAspect<ArgumentsAspect>()->arguments();
m_environment = rc->extraAspect<EnvironmentAspect>()->environment();
connect(&m_applicationLauncher, &ApplicationLauncher::appendMessage,
this, &PythonRunControl::slotAppendMessage);
connect(&m_applicationLauncher, &ApplicationLauncher::processStarted,
this, &PythonRunControl::processStarted);
connect(&m_applicationLauncher, &ApplicationLauncher::processExited,
this, &PythonRunControl::processExited);
}
void PythonRunControl::start()
{
reportApplicationStart();
if (m_interpreter.isEmpty()) {
appendMessage(tr("No Python interpreter specified.") + '\n', Utils::ErrorMessageFormat);
reportApplicationStop();
} else if (!QFileInfo::exists(m_interpreter)) {
appendMessage(tr("Python interpreter %1 does not exist.").arg(QDir::toNativeSeparators(m_interpreter)) + '\n',
Utils::ErrorMessageFormat);
reportApplicationStop();
} else {
QString msg = tr("Starting %1...").arg(QDir::toNativeSeparators(m_interpreter)) + '\n';
appendMessage(msg, Utils::NormalMessageFormat);
StandardRunnable r;
QtcProcess::addArg(&r.commandLineArguments, m_mainScript);
QtcProcess::addArgs(&r.commandLineArguments, m_commandLineArguments);
r.executable = m_interpreter;
r.runMode = m_runMode;
r.environment = m_environment;
m_applicationLauncher.start(r);
setApplicationProcessHandle(m_applicationLauncher.applicationPID());
}
}
PythonRunControl::StopResult PythonRunControl::stop()
{
m_applicationLauncher.stop();
return StoppedSynchronously;
}
void PythonRunControl::slotAppendMessage(const QString &err, Utils::OutputFormat format)
{
appendMessage(err, format);
}
void PythonRunControl::processStarted()
{
// Console processes only know their pid after being started
setApplicationProcessHandle(m_applicationLauncher.applicationPID());
bringApplicationToForeground();
}
void PythonRunControl::processExited(int exitCode, QProcess::ExitStatus status)
{
QString msg;
if (status == QProcess::CrashExit) {
msg = tr("%1 crashed")
.arg(QDir::toNativeSeparators(m_interpreter));
} else {
msg = tr("%1 exited with code %2")
.arg(QDir::toNativeSeparators(m_interpreter)).arg(exitCode);
}
appendMessage(msg + '\n', Utils::NormalMessageFormat);
reportApplicationStop();
}
void PythonRunConfigurationWidget::setInterpreter(const QString &interpreter) void PythonRunConfigurationWidget::setInterpreter(const QString &interpreter)
{ {