/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* @author Jorge Bay Gondra
*/
'use strict';
const t = require('../../process/traversal');
const ts = require('../../process/traversal-strategy');
const Bytecode = require('../../process/bytecode');
const g = require('../graph');
const utils = require('../../utils');
const valueKey = '@value';
const typeKey = '@type';
/**
* @abstract
*/
class TypeSerializer {
serialize() {
throw new Error('serialize() method not implemented for ' + this.constructor.name);
}
deserialize() {
throw new Error('deserialize() method not implemented for ' + this.constructor.name);
}
canBeUsedFor() {
throw new Error('canBeUsedFor() method not implemented for ' + this.constructor.name);
}
}
class NumberSerializer extends TypeSerializer {
serialize(item) {
if (isNaN(item)) {
return {
[typeKey]: 'g:Double',
[valueKey]: 'NaN',
};
} else if (item === Number.POSITIVE_INFINITY) {
return {
[typeKey]: 'g:Double',
[valueKey]: 'Infinity',
};
} else if (item === Number.NEGATIVE_INFINITY) {
return {
[typeKey]: 'g:Double',
[valueKey]: '-Infinity',
};
}
return item;
}
deserialize(obj) {
const val = obj[valueKey];
if (val === 'NaN') {
return NaN;
} else if (val === 'Infinity') {
return Number.POSITIVE_INFINITY;
} else if (val === '-Infinity') {
return Number.NEGATIVE_INFINITY;
}
return parseFloat(val);
}
canBeUsedFor(value) {
return typeof value === 'number';
}
}
class DateSerializer extends TypeSerializer {
serialize(item) {
return {
[typeKey]: 'g:Date',
[valueKey]: item.getTime(),
};
}
deserialize(obj) {
return new Date(obj[valueKey]);
}
canBeUsedFor(value) {
return value instanceof Date;
}
}
class LongSerializer extends TypeSerializer {
serialize(item) {
return {
[typeKey]: 'g:Int64',
[valueKey]: item.value,
};
}
canBeUsedFor(value) {
return value instanceof utils.Long;
}
}
class BytecodeSerializer extends TypeSerializer {
serialize(item) {
let bytecode = item;
if (item instanceof t.Traversal) {
bytecode = item.getBytecode();
}
const result = {};
result[typeKey] = 'g:Bytecode';
const resultValue = (result[valueKey] = {});
const sources = this._serializeInstructions(bytecode.sourceInstructions);
if (sources) {
resultValue['source'] = sources;
}
const steps = this._serializeInstructions(bytecode.stepInstructions);
if (steps) {
resultValue['step'] = steps;
}
return result;
}
_serializeInstructions(instructions) {
if (instructions.length === 0) {
return null;
}
const result = new Array(instructions.length);
result[0] = instructions[0];
for (let i = 0; i < instructions.length; i++) {
result[i] = instructions[i].map((item) => this.writer.adaptObject(item));
}
return result;
}
canBeUsedFor(value) {
return value instanceof Bytecode || value instanceof t.Traversal;
}
}
class PSerializer extends TypeSerializer {
/** @param {P} item */
serialize(item) {
const result = {};
result[typeKey] = 'g:P';
const resultValue = (result[valueKey] = {
predicate: item.operator,
});
if (item.other === undefined || item.other === null) {
resultValue['value'] = this.writer.adaptObject(item.value);
} else {
resultValue['value'] = [this.writer.adaptObject(item.value), this.writer.adaptObject(item.other)];
}
return result;
}
canBeUsedFor(value) {
return value instanceof t.P;
}
}
class TextPSerializer extends TypeSerializer {
/** @param {TextP} item */
serialize(item) {
const result = {};
result[typeKey] = 'g:TextP';
const resultValue = (result[valueKey] = {
predicate: item.operator,
});
if (item.other === undefined || item.other === null) {
resultValue['value'] = this.writer.adaptObject(item.value);
} else {
resultValue['value'] = [this.writer.adaptObject(item.value), this.writer.adaptObject(item.other)];
}
return result;
}
canBeUsedFor(value) {
return value instanceof t.TextP;
}
}
class LambdaSerializer extends TypeSerializer {
/** @param {Function} item */
serialize(item) {
const lambdaDef = item();
// check if the language is specified otherwise assume gremlin-groovy.
const returnIsString = typeof lambdaDef === 'string';
const script = returnIsString ? lambdaDef : lambdaDef[0];
const lang = returnIsString ? 'gremlin-groovy' : lambdaDef[1];
// detect argument count
const argCount =
lang === 'gremlin-groovy' && script.includes('->')
? script.substring(0, script.indexOf('->')).includes(',')
? 2
: 1
: -1;
return {
[typeKey]: 'g:Lambda',
[valueKey]: {
arguments: argCount,
language: lang,
script: script,
},
};
}
canBeUsedFor(value) {
return typeof value === 'function';
}
}
class EnumSerializer extends TypeSerializer {
/** @param {EnumValue} item */
serialize(item) {
return {
[typeKey]: 'g:' + item.typeName,
[valueKey]: item.elementName,
};
}
canBeUsedFor(value) {
return value && value.typeName && value instanceof t.EnumValue;
}
}
class TraverserSerializer extends TypeSerializer {
/** @param {Traverser} item */
serialize(item) {
return {
[typeKey]: 'g:Traverser',
[valueKey]: {
value: this.writer.adaptObject(item.object),
bulk: this.writer.adaptObject(item.bulk),
},
};
}
deserialize(obj) {
const value = obj[valueKey];
return new t.Traverser(this.reader.read(value['value']), this.reader.read(value['bulk']));
}
canBeUsedFor(value) {
return value instanceof t.Traverser;
}
}
class TraversalStrategySerializer extends TypeSerializer {
/** @param {TraversalStrategy} item */
serialize(item) {
const conf = {};
for (const k in item.configuration) {
if (item.configuration.hasOwnProperty(k)) {
conf[k] = this.writer.adaptObject(item.configuration[k]);
}
}
return {
[typeKey]: 'g:' + item.constructor.name,
[valueKey]: conf,
};
}
canBeUsedFor(value) {
return value instanceof ts.TraversalStrategy;
}
}
class VertexSerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
return new g.Vertex(this.reader.read(value['id']), value['label'], this.reader.read(value['properties']));
}
/** @param {Vertex} item */
serialize(item) {
return {
[typeKey]: 'g:Vertex',
[valueKey]: {
id: this.writer.adaptObject(item.id),
label: item.label,
},
};
}
canBeUsedFor(value) {
return value instanceof g.Vertex;
}
}
class VertexPropertySerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
return new g.VertexProperty(
this.reader.read(value['id']),
value['label'],
this.reader.read(value['value']),
this.reader.read(value['properties']),
);
}
}
class PropertySerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
return new g.Property(value['key'], this.reader.read(value['value']));
}
}
class EdgeSerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
return new g.Edge(
this.reader.read(value['id']),
new g.Vertex(this.reader.read(value['outV']), this.reader.read(value['outVLabel'])),
value['label'],
new g.Vertex(this.reader.read(value['inV']), this.reader.read(value['inVLabel'])),
this.reader.read(value['properties']),
);
}
/** @param {Edge} item */
serialize(item) {
return {
[typeKey]: 'g:Edge',
[valueKey]: {
id: this.writer.adaptObject(item.id),
label: item.label,
outV: this.writer.adaptObject(item.outV.id),
outVLabel: item.outV.label,
inV: this.writer.adaptObject(item.inV.id),
inVLabel: item.inV.label,
},
};
}
canBeUsedFor(value) {
return value instanceof g.Edge;
}
}
class PathSerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
const objects = value['objects'].map((o) => this.reader.read(o));
return new g.Path(this.reader.read(value['labels']), objects);
}
}
class Path3Serializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
return new g.Path(this.reader.read(value['labels']), this.reader.read(value['objects']));
}
}
class TSerializer extends TypeSerializer {
deserialize(obj) {
return t.t[obj[valueKey]];
}
}
class DirectionSerializer extends TypeSerializer {
deserialize(obj) {
return t.direction[obj[valueKey].toLowerCase()];
}
}
class ArraySerializer extends TypeSerializer {
constructor(typeKey) {
super();
this.typeKey = typeKey;
}
deserialize(obj) {
const value = obj[valueKey];
if (!Array.isArray(value)) {
throw new Error('Expected Array, obtained: ' + value);
}
return value.map((x) => this.reader.read(x));
}
/** @param {Array} item */
serialize(item) {
return {
[typeKey]: this.typeKey,
[valueKey]: item.map((x) => this.writer.adaptObject(x)),
};
}
canBeUsedFor(value) {
return Array.isArray(value);
}
}
class BulkSetSerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
if (!Array.isArray(value)) {
throw new Error('Expected Array, obtained: ' + value);
}
// coerce the BulkSet to List. if the bulk exceeds the int space then we can't coerce to List anyway,
// so this query will be trouble. we'd need a legit BulkSet implementation here in js. this current
// implementation is here to replicate the previous functionality that existed on the server side in
// previous versions.
let result = [];
for (let ix = 0, iy = value.length; ix < iy; ix += 2) {
const pair = value.slice(ix, ix + 2);
result = result.concat(Array(this.reader.read(pair[1])).fill(this.reader.read(pair[0])));
}
return result;
}
}
class MapSerializer extends TypeSerializer {
deserialize(obj) {
const value = obj[valueKey];
if (!Array.isArray(value)) {
throw new Error('Expected Array, obtained: ' + value);
}
const result = new Map();
for (let i = 0; i < value.length; i += 2) {
result.set(this.reader.read(value[i]), this.reader.read(value[i + 1]));
}
return result;
}
/** @param {Map} map */
serialize(map) {
const arr = [];
map.forEach((v, k) => {
arr.push(this.writer.adaptObject(k));
arr.push(this.writer.adaptObject(v));
});
return {
[typeKey]: 'g:Map',
[valueKey]: arr,
};
}
canBeUsedFor(value) {
return value instanceof Map;
}
}
class ListSerializer extends ArraySerializer {
constructor() {
super('g:List');
}
}
class SetSerializer extends ArraySerializer {
constructor() {
super('g:Set');
}
}
module.exports = {
BulkSetSerializer,
BytecodeSerializer,
DateSerializer,
DirectionSerializer,
EdgeSerializer,
EnumSerializer,
LambdaSerializer,
ListSerializer,
LongSerializer,
MapSerializer,
NumberSerializer,
Path3Serializer,
PathSerializer,
PropertySerializer,
PSerializer,
TextPSerializer,
SetSerializer,
TSerializer,
TraverserSerializer,
TraversalStrategySerializer,
typeKey,
valueKey,
VertexPropertySerializer,
VertexSerializer,
};