Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Common/columnencoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,7 @@ void ColumnEncoder::_addTypeToColumnNamesInOptionsRecursively(Json::Value & opti

ColumnEncoder::colsPlusTypes ColumnEncoder::encodeColumnNamesinOptions(Json::Value & options, bool preloadingData)
{
columnEncoder();
colsPlusTypes getTheseCols;

_addTypeToColumnNamesInOptionsRecursively(options, preloadingData, getTheseCols);
Expand Down
3 changes: 2 additions & 1 deletion Common/columnencoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ private: ColumnEncoder() { invalidateAll(); }
bool shouldEncode(const std::string & in);
bool shouldDecode(const std::string & in);
void setCurrentNames(const colTypeMap & names);
const colTypeMap& currentNames() const { return _dataSetTypes; }
void updateColumnTypesOnly(const colTypeMap & names);
void setCurrentNames(const std::vector<std::string> & names, bool generateTypesEncoding=true); ///< Do not use! Deprecated
void setCurrentColumnTypePerName(const colTypeMap & theMap); ///< Do not use! Deprecated
Expand All @@ -87,7 +88,7 @@ private: ColumnEncoder() { invalidateAll(); }
static std::string encodeAll(const std::string & text) { return replaceAll(text, encodingMap(), originalNames()); }

///Replace all occurences of encoded columnNames in a string by their decoded versions, regardless of word boundaries or parentheses.
static std::string decodeAll(const std::string & text) { return replaceAll(text, decodingMap(), encodedNames()); }
static std::string decodeAll(const std::string & text) { columnEncoder(); return replaceAll(text, decodingMap(), encodedNames()); }

///Replace all occurences of columnNames in a string by their encoded versions in all json-names and string-values, regardless of word boundaries or parentheses.
static void encodeJson(Json::Value & json, bool replaceNames = false, bool replaceStrict = false);
Expand Down
144 changes: 144 additions & 0 deletions Common/columnencodercontext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// Copyright (C) 2013-2025 University of Amsterdam
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

#include "columnencodercontext.h"

#include <stdexcept>

static Json::Value columnTypesToJson(const ColumnEncoder::colTypeMap & columnTypes)
{
Json::Value columns(Json::arrayValue);
for(const auto & nameType : columnTypes)
{
Json::Value column(Json::objectValue);
column["name"] = nameType.first;
column["type"] = columnTypeToString(nameType.second);
columns.append(column);
}

return columns;
}

static ColumnEncoder::colTypeMap columnTypesFromJson(const Json::Value & columns, const char * fieldName)
{
ColumnEncoder::colTypeMap columnTypes;

if(columns.isNull())
return columnTypes;
if(!columns.isArray())
throw std::runtime_error(std::string("Column encoder context field '") + fieldName + "' must be an array.");

for(const Json::Value & column : columns)
{
if(!column.isObject() || !column["name"].isString() || !column["type"].isString())
throw std::runtime_error(std::string("Column encoder context field '") + fieldName + "' must contain objects with string 'name' and 'type' fields.");

columnTypes[column["name"].asString()] = columnTypeFromString(column["type"].asString());
}

return columnTypes;
}

static Json::Value parsePayloadJson(const char * payloadJson)
{
if(!payloadJson)
throw std::runtime_error("Cannot decode column text from a null JSON payload.");

Json::Value payload;
Json::Reader reader;
if(!reader.parse(payloadJson, payload))
throw std::runtime_error("Could not parse column text JSON payload.");

return payload;
}

ColumnEncoderContext::ColumnEncoderContext(const ColumnEncoder::colTypeMap & columns, const ColumnEncoder::colTypeMap & extra)
: _columns(columns), _extra(extra), _supplied(true)
{
}

ColumnEncoderContext ColumnEncoderContext::fromJson(const Json::Value & context)
{
if(!context.isObject())
throw std::runtime_error("Column encoder context must be a JSON object.");

if(!context.isMember("version") || !context["version"].isInt())
throw std::runtime_error("Column encoder context must contain integer version 1.");
if(context["version"].asInt() != Version)
throw std::runtime_error("Unsupported column encoder context version.");

return ColumnEncoderContext(
columnTypesFromJson(context["columns"], "columns"),
columnTypesFromJson(context["extra"], "extra")
);
}

ColumnEncoderContext ColumnEncoderContext::fromJsonString(const char * contextJson)
{
if(!contextJson || std::string(contextJson).empty())
return ColumnEncoderContext();

Json::Value context;
Json::Reader reader;
if(!reader.parse(contextJson, context))
throw std::runtime_error("Could not parse column encoder context JSON.");

return fromJson(context);
}

Json::Value ColumnEncoderContext::toJson() const
{
Json::Value context(Json::objectValue);
context["version"] = Version;
context["columns"] = columnTypesToJson(_columns);
context["extra"] = columnTypesToJson(_extra);

return context;
}

ScopedColumnEncoderContext::ScopedColumnEncoderContext(const ColumnEncoderContext & context, ColumnEncoder & extraEncoder)
: _supplied(context.supplied()), _extraEncoder(extraEncoder)
{
if(!_supplied)
return;

_previousColumns = ColumnEncoder::columnEncoder()->currentNames();
_previousExtra = _extraEncoder.currentNames();

ColumnEncoder::columnEncoder()->setCurrentNames(context.columns());
_extraEncoder.setCurrentNames(context.extra());
}

ScopedColumnEncoderContext::~ScopedColumnEncoderContext()
{
if(!_supplied)
return;

ColumnEncoder::columnEncoder()->setCurrentNames(_previousColumns);
_extraEncoder.setCurrentNames(_previousExtra);
}

Json::Value decodeColumnJson(const char * payloadJson, const char * encoderContextJson, ColumnEncoder & extraEncoder, bool replaceNames)
{
Json::Value payload = parsePayloadJson(payloadJson);
ColumnEncoderContext context = ColumnEncoderContext::fromJsonString(encoderContextJson);
ScopedColumnEncoderContext scopedContext(context, extraEncoder);

ColumnEncoder::decodeJson(payload, replaceNames);

return payload;
}
61 changes: 61 additions & 0 deletions Common/columnencodercontext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Copyright (C) 2013-2025 University of Amsterdam
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

#ifndef COLUMNENCODERCONTEXT_H
#define COLUMNENCODERCONTEXT_H

#include "columnencoder.h"

class ColumnEncoderContext
{
public:
static constexpr int Version = 1;

ColumnEncoderContext() = default;
ColumnEncoderContext(const ColumnEncoder::colTypeMap & columns, const ColumnEncoder::colTypeMap & extra);

static ColumnEncoderContext fromJson(const Json::Value & context);
static ColumnEncoderContext fromJsonString(const char * contextJson);

Json::Value toJson() const;

const ColumnEncoder::colTypeMap& columns() const { return _columns; }
const ColumnEncoder::colTypeMap& extra() const { return _extra; }
bool supplied() const { return _supplied; }

private:
ColumnEncoder::colTypeMap _columns;
ColumnEncoder::colTypeMap _extra;
bool _supplied = false;
};

class ScopedColumnEncoderContext
{
public:
ScopedColumnEncoderContext(const ColumnEncoderContext & context, ColumnEncoder & extraEncoder);
~ScopedColumnEncoderContext();

private:
bool _supplied = false;
ColumnEncoder & _extraEncoder;
ColumnEncoder::colTypeMap _previousColumns;
ColumnEncoder::colTypeMap _previousExtra;
};

Json::Value decodeColumnJson(const char * payloadJson, const char * encoderContextJson, ColumnEncoder & extraEncoder, bool replaceNames = true);

#endif // COLUMNENCODERCONTEXT_H
1 change: 1 addition & 0 deletions CommonData/databridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "timers.h"

DataBridge::DataBridge(unsigned long sessionID, bool useMemory)
: _extraEncodings(new ColumnEncoder(ExtraOptionsPrefix))
{
JASPTIMER_START(TempFiles Attach);
TempFiles::attach(sessionID);
Expand Down
10 changes: 10 additions & 0 deletions CommonData/databridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@

#include "dataset.h"

#include <memory>

class ColumnEncoder;

class DataBridge
{
public:
Expand All @@ -44,6 +48,8 @@ class DataBridge
void provideSpecificFileName( const std::string & specificName, std::string & root, std::string & relativePath);
int dataSetRowCount() { return static_cast<int>(provideAndUpdateDataSet()->rowCount()); }
void updateOptionsAccordingToMeta(Json::Value & options);
ColumnEncoder * extraEncodings() { return _extraEncodings.get(); }
const ColumnEncoder * extraEncodings() const { return _extraEncodings.get(); }

protected:
bool isColumnNameOk(const std::string & columnName);
Expand All @@ -54,6 +60,10 @@ class DataBridge
DatabaseInterface * _db = nullptr;
int _analysisId = -1;
std::function<void()> _datasetProvidedCallback;

private:
static constexpr const char * ExtraOptionsPrefix = "JaspExtraOptions_";
std::unique_ptr<ColumnEncoder> _extraEncodings;
};

#endif // DATABRIDGE_H
10 changes: 4 additions & 6 deletions CommonData/rbridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ void rbridge_setDataBridge(DataBridge * dataBridge)
{
data_bridge = dataBridge;
rbridge_dataSet = nullptr;
extraEncodings = dataBridge ? dataBridge->extraEncodings() : nullptr;
}

void rbridge_clearDataBridge()
{
data_bridge = nullptr;
rbridge_dataSet = nullptr;
rbridge_setDataBridge(nullptr);
}

const std::string jaspBaseDistributionSamplersR =
Expand Down Expand Up @@ -102,14 +102,12 @@ const std::string jaspBaseTransformPowerR =
#include "jaspBase_transformPower.h"
;

void rbridge_init(DataBridge * dataBridge, sendFuncDef sendToDesktopFunction, pollMessagesFuncDef pollMessagesFunction, ColumnEncoder * extraEncoder, const char * resultFont, bool insideJasp)
void rbridge_init(DataBridge * dataBridge, sendFuncDef sendToDesktopFunction, pollMessagesFuncDef pollMessagesFunction, const char * resultFont, bool insideJasp)
{
JASPTIMER_SCOPE(rbridge_init);

Log::log() << "Setting DataBridge and extraEncodings." << std::endl;
rbridge_setDataBridge(dataBridge);

Log::log() << "Setting extraEncodings." << std::endl;
extraEncodings = extraEncoder;

Log::log() << "Collecting RBridgeCallBacks." << std::endl;
RBridgeCallBacks callbacks = {
Expand Down
2 changes: 1 addition & 1 deletion CommonData/rbridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ extern "C" {

void rbridge_setDataBridge(DataBridge * dataBridge);
void rbridge_clearDataBridge();
void rbridge_init(DataBridge * dataBridge, sendFuncDef sendToDesktopFunction, pollMessagesFuncDef pollMessagesFunction, ColumnEncoder * encoder, const char * resultFont, bool insideJasp = true);
void rbridge_init(DataBridge * dataBridge, sendFuncDef sendToDesktopFunction, pollMessagesFuncDef pollMessagesFunction, const char * resultFont, bool insideJasp = true);

void rbridge_memoryCleaning();

Expand Down
6 changes: 2 additions & 4 deletions Engine/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ Engine::Engine(int slaveNo, unsigned long parentPID)
JASPTIMER_SCOPE(Engine Constructor);
assert(_EngineInstance == NULL);
_EngineInstance = this;

_extraEncodings = new ColumnEncoder("JaspExtraOptions_");
}

void Engine::initialize()
Expand All @@ -88,7 +86,7 @@ void Engine::initialize()
std::string memoryName = "JASP-IPC-" + std::to_string(_parentPID);
_channel = new IPCChannel(memoryName, _engineNum, true);

rbridge_init(this, SendFunctionForJaspresults, PollMessagesFunctionForJaspResults, _extraEncodings, _resultFont.c_str());
rbridge_init(this, SendFunctionForJaspresults, PollMessagesFunctionForJaspResults, _resultFont.c_str());

Log::log() << "rbridge_init completed" << std::endl;

Expand Down Expand Up @@ -679,7 +677,7 @@ void Engine::receiveAnalysisMessage(const Json::Value & jsonRequest)

Log::log(false) << _analysisTitle << " with ID " << _analysisId << std::endl;

_extraEncodings->setCurrentNamesFromOptionsMeta(optionsEnc);
extraEncodings()->setCurrentNamesFromOptionsMeta(optionsEnc);

_analysisOptions = optionsEnc; //store unencoded
}
Expand Down
1 change: 0 additions & 1 deletion Engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ class Engine : public DataBridge
const int _engineNum;
const unsigned long _parentPID;
IPCChannel * _channel = nullptr;
ColumnEncoder * _extraEncodings = nullptr;
engineState _engineState = engineState::initializing,
_lastRequest = engineState::initializing;
Status _analysisStatus = Status::empty;
Expand Down
Loading
Loading