/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.magma.js.methods;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.obiba.magma.MagmaRuntimeException;
import org.obiba.magma.Value;
import org.obiba.magma.ValueSequence;
import org.obiba.magma.ValueType;
import org.obiba.magma.js.MagmaJsEvaluationRuntimeException;
import org.obiba.magma.js.ScriptableValue;
import org.obiba.magma.type.*;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
/**
* JavaScript methods that operate on {@link ValueSequence} objects wrapped in {@link ScriptableValue} objects.
*/
@SuppressWarnings({ "UnusedDeclaration", "StaticMethodOnlyUsedInOneClass" })
public class ValueSequenceMethods {
private ValueSequenceMethods() {
}
/**
* Returns whether the Value is a {@link ValueSequence}.
* <p/>
* <pre>
* $('SequenceVar').isSequence()
* </pre>
*/
public static ScriptableValue isSequence(Context ctx, Scriptable thisObj, @Nullable Object[] args,
@Nullable Function funObj) {
ScriptableValue sv = (ScriptableValue) thisObj;
return new ScriptableValue(thisObj, BooleanType.get().valueOf(sv.getValue().isSequence()));
}
/**
* Returns the first Value of the {@link ValueSequence}. Returns null if the operand is null or the ValueSequence
* contains no Values.
* <p/>
* <pre>
* $('SequenceVar').first()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence.
*/
public static ScriptableValue first(Context ctx, Scriptable thisObj, @Nullable Object[] args,
@Nullable Function funObj) {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return valueSequence.getSize() > 0 //
? new ScriptableValue(thisObj, valueSequence.get(0)) //
: new ScriptableValue(thisObj, valueSequence.getValueType().nullValue());
} else {
return sv;
}
}
/**
* Returns the first not null Value of the {@link ValueSequence}. Returns null if the operand is null or the ValueSequence
* contains no Values or if all Values are null.
* <p/>
* <pre>
* $('SequenceVar').firstNotNull()
* </pre>
*
* @param ctx
* @param thisObj
* @param args
* @param funObj
* @return
*/
public static ScriptableValue firstNotNull(Context ctx, Scriptable thisObj, @Nullable Object[] args,
@Nullable Function funObj) {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return valueSequence.getSize() > 0 //
? new ScriptableValue(thisObj, valueSequence.getValues().stream().filter(value -> !value.isNull())
.findFirst().orElse(valueSequence.getValueType().nullValue())) //
: new ScriptableValue(thisObj, valueSequence.getValueType().nullValue());
} else {
return sv;
}
}
/**
* Returns the last Value of the {@link ValueSequence}. Returns null if the operand is null or the ValueSequence
* contains no Values.
* <p/>
* <pre>
* $('SequenceVar').last()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence.
*/
public static ScriptableValue last(Context ctx, Scriptable thisObj, @Nullable Object[] args,
@Nullable Function funObj) throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return valueSequence.getSize() > 0 //
? new ScriptableValue(thisObj, valueSequence.get(valueSequence.getSize() - 1)) //
: new ScriptableValue(thisObj, valueSequence.getValueType().nullValue());
} else {
return sv;
}
}
/**
* Returns the size of the {@link ValueSequence}. Returns null if the operand is null, 1 if the value is not a sequence.
* <p/>
* <pre>
* $('SequenceVar').size()
* </pre>
*/
public static ScriptableValue size(Context ctx, Scriptable thisObj, @Nullable Object[] args,
@Nullable Function funObj) {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, IntegerType.get().nullValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return new ScriptableValue(thisObj, IntegerType.get().valueOf(valueSequence.getSize()));
} else {
return new ScriptableValue(thisObj, IntegerType.get().valueOf(1));
}
}
/**
* Returns the {@link Value} of the {@link ValueSequence} specified by the provided index. Returns null if the operand
* is null. Returns null if index is not an {@link Integer}. Returns null if the index is out of bounds.
* <p/>
* <pre>
* $('SequenceVar').valueAt(0)
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence.
*/
public static ScriptableValue valueAt(Context ctx, Scriptable thisObj, Object[] args, @Nullable Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValueType().nullValue());
}
Integer index = null;
if(args != null && args.length > 0) {
try {
index = ((Number) IntegerType.get().valueOf(getRawValue(args[0])).getValue()).intValue();
} catch(MagmaRuntimeException e) {
// ignore, will be handled after
}
}
if (index == null) {
return new ScriptableValue(thisObj, sv.getValueType().nullValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return valueSequence.getSize() > index && index >= 0 //
? new ScriptableValue(thisObj, valueSequence.get(index)) //
: new ScriptableValue(thisObj, valueSequence.getValueType().nullValue());
} else {
return index == 0 ? sv : new ScriptableValue(thisObj, sv.getValueType().nullValue());
}
}
/**
* Get the position of the given value in the sequence. Return -1 value if not found.
* <p/>
* <pre>
* $('SequenceVar').indexOf(0)
* </pre>
*
* @param ctx
* @param thisObj
* @param args
* @param funObj
* @return
* @throws MagmaJsEvaluationRuntimeException
*/
public static ScriptableValue indexOf(Context ctx, Scriptable thisObj, Object[] args, @Nullable Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
int index = -1;
if (args != null && args.length > 0) {
Value testValue = sv.getValueType().valueOf(getRawValue(args[0]));
index = sv.indexOf(testValue);
}
return new ScriptableValue(thisObj, IntegerType.get().valueOf(index));
}
private static Object getRawValue(Object object) {
Object raw = object;
if (raw instanceof ScriptableValue) {
raw = ((ScriptableValue)raw).getValue();
}
if (raw instanceof Value) {
Value value = (Value)raw;
raw = value.isNull() ? null : value.getValue();
}
return raw;
}
/**
* Get the last position of the given value in the sequence. Return -1 value if not found.
* <p/>
* <pre>
* $('SequenceVar').lastIndexOf(0)
* </pre>
*
* @param ctx
* @param thisObj
* @param args
* @param funObj
* @return
* @throws MagmaJsEvaluationRuntimeException
*/
public static ScriptableValue lastIndexOf(Context ctx, Scriptable thisObj, Object[] args, @Nullable Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
int index = -1;
if (args != null && args.length > 0) {
Value testValue = sv.getValueType().valueOf(getRawValue(args[0]));
index = sv.lastIndexOf(testValue);
}
return new ScriptableValue(thisObj, IntegerType.get().valueOf(index));
}
/**
* Returns the {@link ValueSequence} sorted in natural order (default behavior), or using a custom sorting algorithm
* (javascript function) if specified. Note that some {@link ValueType}s such as {@link BinaryType} and
* {@link LocaleType} do not have a natural sort order and {@code ValueSequence}s of those types will not be modified
* when using the default behavior.
* <p/>
* <pre>
* $('SequenceVar').sort()
* $('SequenceVar').sort(function(first, second) {
* return first.value() - second.value()
* })
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence.
*/
public static ScriptableValue sort(final Context ctx, Scriptable thisObj, @Nullable Object[] args,
@Nullable Function funObj) throws MagmaJsEvaluationRuntimeException {
final ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
ValueSequence sortedValueSequence = null;
if(args != null && args.length > 0 && args[0] instanceof Function) {
// Sort using a custom Comparator (javascript function)
final Callable func = (Callable) args[0];
sortedValueSequence = valueSequence.sort(new Comparator<Value>() {
@Override
public int compare(Value o1, Value o2) {
return ((Number) func.call(ctx, sv.getParentScope(), sv,
new ScriptableValue[] { new ScriptableValue(sv, o1), new ScriptableValue(sv, o2) })).intValue();
}
});
} else {
// Sort based on natural order
sortedValueSequence = valueSequence.sort();
}
return new ScriptableValue(thisObj, sortedValueSequence);
} else {
// Sorting a single value produces that value.
return sv;
}
}
/**
* Returns the average of the {@link Value}s contained in the {@link ValueSequence}. Returns null if the operand is
* null or the ValueSequence is empty or contains at least one null value or a non-numeric value.
* <p/>
* <pre>
* $('SequenceVar').avg()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence of numeric values.
*/
public static ScriptableValue avg(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, DecimalType.get().nullValue());
}
if(!sv.getValueType().isNumeric()) {
throw new MagmaJsEvaluationRuntimeException(
"Operand to avg() method must be numeric, but was invoked for '" + sv.getValueType().getName() + "'");
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return new ScriptableValue(thisObj, DecimalType.get().valueOf(NumericMethods.average(valueSequence)),
sv.getUnit());
} else {
// Average of a single value is the value itself.
return sv;
}
}
/**
* Returns the standard deviation of the {@link Value}s contained in the {@link ValueSequence}. Returns null if the
* operand is null or the ValueSequence is empty or a non-numeric value.
* <p/>
* <pre>
* $('SequenceVar').stddev()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence of numeric values.
*/
public static ScriptableValue stddev(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, DecimalType.get().nullValue());
}
if(!sv.getValueType().isNumeric()) {
throw new MagmaJsEvaluationRuntimeException(
"Operand to stddev() method must be numeric, but was invoked for '" + sv.getValueType().getName() + "'");
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return new ScriptableValue(thisObj, DecimalType.get().valueOf(NumericMethods.stddev(valueSequence)),
sv.getUnit());
} else {
// standard deviation of a single value is 0
return new ScriptableValue(thisObj, DecimalType.get().valueOf(0), sv.getUnit());
}
}
/**
* Returns the sum of the {@link Value}s contained in the {@link ValueSequence}. Returns null if the operand is null
* or the ValueSequence is empty. This method throws an exception if the operand
* is non-numeric.
* <p/>
* <pre>
* $('SequenceVar').sum()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence of numeric values.
*/
public static ScriptableValue sum(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(!sv.getValueType().isNumeric()) {
throw new MagmaJsEvaluationRuntimeException(
"Operand to sum() method must be numeric, but was invoked for '" + sv.getValueType().getName() + "'");
}
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValueType().nullValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return new ScriptableValue(thisObj, sv.getValueType().valueOf(NumericMethods.sum(valueSequence)), sv.getUnit());
} else {
return sv;
}
}
/**
* Returns the minimum of the {@link Value}s contained in the {@link ValueSequence}. Returns null if the operand is null
* or the ValueSequence is empty. This method throws an exception if the operand
* is non-numeric.
* <p/>
* <pre>
* $('SequenceVar').min()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence of numeric values.
*/
public static ScriptableValue min(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(!sv.getValueType().isNumeric()) {
throw new MagmaJsEvaluationRuntimeException(
"Operand to min() method must be numeric, but was invoked for '" + sv.getValueType().getName() + "'");
}
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValueType().nullValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return new ScriptableValue(thisObj, sv.getValueType().valueOf(NumericMethods.min(valueSequence)), sv.getUnit());
} else {
return sv;
}
}
/**
* Returns the maximum of the {@link Value}s contained in the {@link ValueSequence}. Returns null if the operand is null
* or the ValueSequence is empty. This method throws an exception if the operand
* is non-numeric.
* <p/>
* <pre>
* $('SequenceVar').max()
* </pre>
*
* @throws MagmaJsEvaluationRuntimeException if operand does not contain a ValueSequence of numeric values.
*/
public static ScriptableValue max(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(!sv.getValueType().isNumeric()) {
throw new MagmaJsEvaluationRuntimeException(
"Operand to max() method must be numeric, but was invoked for '" + sv.getValueType().getName() + "'");
}
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, sv.getValueType().nullValue());
}
if(sv.getValue().isSequence()) {
ValueSequence valueSequence = sv.getValue().asSequence();
return new ScriptableValue(thisObj, sv.getValueType().valueOf(NumericMethods.max(valueSequence)), sv.getUnit());
} else {
return sv;
}
}
/**
* Push a value to a value to produce a value sequence. Also accepts a value sequence as input, in which case, both sequences
* are concatenated to produce a single one (it does not produce a sequence of sequence).
*
* <p>If the value being added is not of the same type as the sequence, it will be converted to the
* sequence's type. If the conversion fails, an exception is thrown.</p>
*
* <p>If the sequence is null, this method returns a null sequence. If the sequence is empty, this method returns a
* new sequence containing the parameter(s). If the parameter is null, a null value is appended.</p>
*
* <pre>
* // Add a value to a sequence, then compute the average of the resulting sequence
* $('BloodPressure:Measure.RES_PULSE').push($('StandingHeight:FIRST_RES_PULSE')).avg();
*
* // Aadd several values to a value (or a value sequence)
* $('VARX').push(1, 2, 3)
* </pre>
*
* @param ctx
* @param thisObj
* @param args
* @param funObj
* @return
* @throws MagmaJsEvaluationRuntimeException
*/
public static ScriptableValue push(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
ValueType targetType = sv.getValueType();
Iterable<Value> sequence;
Value svValue = sv.getValue();
if(svValue.isNull()) {
return new ScriptableValue(thisObj, targetType.nullSequence());
}
sequence = svValue.isSequence() ? svValue.asSequence().getValue() : ImmutableList.of(svValue);
for(Object argument : args) {
Value value = argument instanceof ScriptableValue //
? ((ScriptableValue) argument).getValue() //
: targetType.valueOf(argument);
if(value.getValueType() != targetType) {
value = targetType.convert(value);
}
if(value.isNull()) {
sequence = Iterables.concat(sequence, ImmutableList.of(targetType.nullValue()));
} else {
sequence = value.isSequence() //
? Iterables.concat(sequence, value.asSequence().getValue()) //
: Iterables.concat(sequence, ImmutableList.of(value));
}
}
return new ScriptableValue(thisObj, targetType.sequenceOf(Lists.newArrayList(sequence)));
}
/**
* Append a value to a value to produce a value sequence. Also accepts a value sequence as input, in which case, both sequences
* are concatenated to produce a single one (it does not produce a sequence of sequence).
*
* <p>If the value being added is not of the same type as the sequence, it will be converted to the
* sequence's type. If the conversion fails, an exception is thrown.</p>
*
* <p>If the sequence is null or empty, this method returns a
* new sequence containing the parameter(s). If the parameter is null, a null value is appended.</p>
*
* <pre>
* // Append a value to a sequence, then compute the average of the resulting sequence
* $('BloodPressure:Measure.RES_PULSE').append($('StandingHeight:FIRST_RES_PULSE')).avg();
*
* // Append several values to a value (or a value sequence)
* $('VARX').append(1, 2, 3)
* </pre>
*
* @param ctx
* @param thisObj
* @param args
* @param funObj
* @return
* @throws MagmaJsEvaluationRuntimeException
*/
public static ScriptableValue append(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
return pendValue(thisObj, args, true);
}
/**
* Prepend a value to a value to produce a value sequence. Also accepts a value sequence as input, in which case, both sequences
* are concatenated to produce a single one (it does not produce a sequence of sequence).
*
* <p>If the value being added is not of the same type as the sequence, it will be converted to the
* sequence's type. If the conversion fails, an exception is thrown.</p>
*
* <p>If the sequence is null or empty, this method returns a
* new sequence containing the parameter(s). If the parameter is null, a null value is appended.</p>
*
* <pre>
* // Prepend a value to a sequence, then compute the average of the resulting sequence
* $('BloodPressure:Measure.RES_PULSE').prepend($('StandingHeight:FIRST_RES_PULSE')).avg();
*
* // Prepend several values to a value (or a value sequence)
* $('VARX').prepend(1, 2, 3)
* </pre>
*
* @param ctx
* @param thisObj
* @param args
* @param funObj
* @return
* @throws MagmaJsEvaluationRuntimeException
*/
public static ScriptableValue prepend(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
return pendValue(thisObj, args, false);
}
private static ScriptableValue pendValue(Scriptable thisObj, Object[] args, boolean append) {
ScriptableValue sv = (ScriptableValue) thisObj;
ValueType targetType = sv.getValueType();
Iterable<Value> sequence;
Value svValue = sv.getValue();
sequence = svValue.isSequence() ?
svValue.isNull() ? Lists.newArrayList() : svValue.asSequence().getValue() :
ImmutableList.of(svValue);
List<Value> pendSequence = Lists.newArrayList();
for (Object argument : args) {
Value value = argument instanceof ScriptableValue //
? ((ScriptableValue) argument).getValue() //
: targetType.valueOf(argument);
if (value.getValueType() != targetType) {
value = targetType.convert(value);
}
if (value.isSequence()) {
if (!value.isNull()) value.asSequence().getValue().forEach(pendSequence::add);
} else {
pendSequence.add(value);
}
}
sequence = append ?
Iterables.concat(sequence, pendSequence) :
Iterables.concat(pendSequence, sequence);
return new ScriptableValue(thisObj, targetType.sequenceOf(Lists.newArrayList(sequence)));
}
/**
* Returns a sequence of values, where each value is the transformation of a tuple of values, the i-th tuple contains
* the i-th element from each of the argument sequences. The returned list length is the length of the longest
* argument sequence (shortest argument sequence values are null). Not sequential arguments have their value repeated
* in each tuple.
*
* <pre>
* // returns "a1, b2, c3"
* $('SequenceVarAZ').zip($('SequenceVar19'), function(o1,o2) {
* return o1.concat(o2);
* })
* // returns "afoo1, bfoo2, cfoo3"
* $('SequenceVarAZ').zip($('FooVar'), $('SequenceVar19'), function(o1,o2,o3) {
* return o1.concat(o2,o3);
* })
* </pre>
*
* @return an instance of {@code ScriptableValue}
*/
@SuppressWarnings({ "OverlyLongMethod", "PMD.NcssMethodCount" })
public static ScriptableValue zip(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(args == null || args.length == 0) {
return sv;
}
// Extract values, the transformation function and the max sequence length
List<Object> values = new ArrayList<>();
values.add(sv.getValue());
int length = getValueSize(sv.getValue());
Function func = null;
for(Object arg : args) {
if(arg instanceof ScriptableValue) {
Value value = ((ScriptableValue) arg).getValue();
values.add(value);
length = Math.max(length, getValueSize(value));
} else if(arg instanceof Function) {
func = (Function) arg;
} else if(arg != null) {
values.add(arg);
length = Math.max(length, 1);
}
}
if(func == null) {
throw new IllegalArgumentException("Zip requires a transform function.");
}
if(length == 0) {
return new ScriptableValue(sv, sv.getValueType().nullSequence());
}
// Transform value tuples to build a value sequence
ValueType rValueType = null;
Collection<Value> rvalues = new ArrayList<>();
for(int i = 0; i < length; i++) {
Value fvalue = asValue(func.call(ctx, sv.getParentScope(), sv, getTupleAsArguments(sv, values, i)));
rValueType = fvalue.getValueType();
rvalues.add(fvalue);
}
//noinspection ConstantConditions
return new ScriptableValue(sv, rValueType.sequenceOf(rvalues));
}
private static int getValueSize(Value value) {
int size = value.isNull() ? 0 : 1;
if(!value.isNull() && value.isSequence()) {
size = value.asSequence().getSize();
}
return size;
}
private static Value asValue(Object obj) {
return obj instanceof ScriptableValue //
? ((ScriptableValue) obj).getValue() //
: ValueType.Factory.newValue((Serializable) obj);
}
private static Object[] getTupleAsArguments(ScriptableValue sv, List<Object> values, int i) {
Object[] objects = new Object[values.size()];
for(int j = 0; j < values.size(); j++) {
Object obj = values.get(j);
if(obj instanceof Value) {
Value value = (Value) obj;
if(value.isSequence()) {
value = getValueAt(value.asSequence(), i);
}
objects[j] = new ScriptableValue(sv, value);
} else {
objects[j] = obj;
}
}
return objects;
}
private static Value getValueAt(ValueSequence seq, int i) {
return seq.isNull() || i >= seq.getSize() ? seq.getValueType().nullValue() : seq.get(i);
}
/**
* Joins the text representation of the values in the sequence, using the provided delimiter, prefix and suffix. A
* null (resp. empty sequence) will return a null (resp. empty) text value.
* <p/>
* <pre>
* // returns "1, 2, 3"
* $('SequenceVar').join(', ')
* // returns "[1, 2, 3]"
* $('SequenceVar').join(', ','[',']')
* // returns "123"
* $('SequenceVar').join()
* </pre>
*
* @return an instance of {@code ScriptableValue}
*/
public static ScriptableValue join(Context ctx, Scriptable thisObj, Object[] args, Function funObj)
throws MagmaJsEvaluationRuntimeException {
ScriptableValue sv = (ScriptableValue) thisObj;
if(sv.getValue().isNull()) {
return new ScriptableValue(thisObj, TextType.get().nullValue());
}
String delimiter = getArgumentAsString(args, 0);
String prefix = getArgumentAsString(args, 1);
String suffix = getArgumentAsString(args, 2);
return sv.getValue().isSequence() //
? joinValueSequence(sv, delimiter, prefix, suffix) //
: joinValue(sv, delimiter, prefix, suffix);
}
private static ScriptableValue joinValueSequence(ScriptableValue sv, String delimiter, String prefix, String suffix) {
ValueSequence valueSequence = sv.getValue().asSequence();
String rval = "";
if(valueSequence.getSize() > 0) {
StringBuilder buffer = new StringBuilder(prefix);
for(int i = 0; i < valueSequence.getSize(); i++) {
buffer.append(valueSequence.get(i));
if(i < valueSequence.getSize() - 1) {
buffer.append(delimiter);
}
}
buffer.append(suffix);
rval = buffer.toString();
}
return new ScriptableValue(sv, TextType.get().valueOf(rval));
}
private static ScriptableValue joinValue(ScriptableValue sv, String delimiter, String prefix, String suffix) {
Value value = sv.getValue();
if(value.isNull()) {
return new ScriptableValue(sv, TextType.get().nullValue());
} else {
String rval = sv.toString();
if(rval != null && !rval.isEmpty()) {
rval = prefix + rval + suffix;
}
return new ScriptableValue(sv, TextType.get().valueOf(rval));
}
}
private static String getArgumentAsString(Object[] args, int idx) {
return args == null || args.length <= idx || args[idx] == null ? "" : args[idx].toString();
}
}