CallParameter.java
/*
** Module : CallParameter.java
** Abstract : Specification for a parameter, when adding it via a CALL:SET-PARAMETER or legacy
** ParameterList class.
**
** Copyright (c) 2019-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20190710 Extracted from Call.java.
** 002 ME 20201009 Save initial value for input parameters to avoid variable mutation between
** Call.setParameter and Call.invoke. Delay validation for input table/dataset
** handle until Call.invoke is executed.
** OM 20210309 Removed double display of error message number.
** CA 20210609 Fixed OO property and field references used as arguments (extent or not).
** GES 20210430 Reworked table and dataset parameter processing.
** CA 20220910 Fixed 'copyOutput' for DataSetParameter.
** OM 20221205 Implemented toString() for easier debugging.
** OM 20221216 Reworked table and dataset handle parameters.
** CA 20230207 Fixed TABLE-/DATASET-HANDLE case when the handle is unknown.
** 003 CA 20240215 Fixed issues using CALL property/field references as arguments, associated with a
** DATASET/TABLE-HANDLE parameter.
** 004 CA 20240331 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime, so logical
** type checks must include sub-classes, too.
** 005 CA 20240409 The parameter mode needs to be set at the dataset/table parameter explicitly.
** 006 DDF 20240408 Pass the type of the parameter and use it to wrap it directly to avoid generating
** a constructor for that type.
** 007 CA 20241030 Added immutable character constant support.
** 008 CA 20241107 Added character constant support.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.lang.reflect.*;
import java.util.EnumSet;
import java.util.logging.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.util.*;
/**
* A wrapper for call parameters.
*/
public class CallParameter
{
/** The original value passed to {@link Call#setParameter}. */
public final Object value;
/** The original input for value parameter passed to {@link Call#setParameter}. */
public final Object initialValue;
/** The call mode passed to {@link Call#setParameter}. */
public final CallMode mode;
/** The data type passed to {@link Call#setParameter}. */
public final String dataType;
/** The prepared argument which will be passed to the remote call. */
protected Object argument = null;
/**
* Create a new wrapper for a parameter which will be used for a native OS API call.
*
* @param dataType
* The parameter's data type.
* @param isClass
* Flag indicating that the datatype is a legacy OO class.
* @param mode
* The call mode.
* @param value
* The parameter's value.
*/
public CallParameter(String dataType, boolean isClass, CallMode mode, Object value)
{
this.mode = mode;
if ("TABLE-HANDLE".equalsIgnoreCase(dataType) && value instanceof handle)
{
TempTable tempTable = (TempTable) ((handle) value).getResource();
if (tempTable != null && tempTable.valid())
{
this.value = new TableParameter(tempTable.defaultBufferHandle(), mode.mode);
this.dataType = "TABLE";
this.initialValue = convertValue("TABLE", isClass, mode, this.value);
return;
}
}
else if ("DATASET-HANDLE".equalsIgnoreCase(dataType) && value instanceof handle)
{
DataSet dataSet = (DataSet) ((handle) value).getResource();
if (dataSet != null && dataSet.valid())
{
this.value = new DataSetParameter(dataSet, mode.mode);
this.dataType = "DATASET";
this.initialValue = convertValue("DATASET", isClass, mode, this.value);
return;
}
}
this.dataType = dataType;
this.value = value;
// save the current value for input/input-output so the argument have the right reference on invoke
this.initialValue = convertValue(dataType, isClass, mode, value);
}
/**
* Get the argument. It is assumed {@link #convertTo(Class)} was previously called.
*
* @return The {@link #argument}.
*/
public Object getArgument()
{
if (argument == null)
{
argument = convertTo();
}
return argument;
}
/**
* If this parameter is an OUTPUT or INPUT-OUTPUT non-table parameter, copy the value to
* the variable or field, as specified via {@link Call#setParameter SET-PARAMETER}.
*
* @return <code>true</code> if the value could be copied; <code>false</code> otherwise.
*/
public boolean copyOutput()
{
if (mode.input || argument instanceof TableParameter || argument instanceof DataSetParameter)
{
return true;
}
if (value instanceof PropertyReference)
{
PropertyReference pr = (PropertyReference) value;
if (pr.getType().isAssignableFrom(argument.getClass()))
{
pr.set((BaseDataType) argument);
}
else
{
pr.set(convertTo((BaseDataType) argument, pr.getType()));
}
return true;
}
else if (value instanceof FieldReference)
{
FieldReference fr = (FieldReference) value;
Buffer buf = (Buffer) fr.getParentBuffer().getDMOProxy();
if (!buf._available())
{
ErrorManager.recordOrShowError(10088,
"Record not available for dynamic output parameter",
false, false, false);
return false;
}
if (fr.getType().isAssignableFrom(argument.getClass()))
{
fr.set((BaseDataType) argument);
}
else
{
fr.set(convertTo((BaseDataType) argument, fr.getType()));
}
}
else if (value instanceof BaseDataType)
{
BaseDataType bdt = (BaseDataType) value;
if (bdt instanceof decimal || bdt instanceof int64)
{
if (bdt.getClass() == decimal.class)
{
bdt = new decimal()
{
@Override
boolean isAssignDirect()
{
return true;
}
};
}
else if (bdt.getClass() == int64.class)
{
bdt = new int64()
{
@Override
boolean isAssignDirect()
{
return true;
}
};
}
else if (bdt.getClass() == integer.class)
{
bdt = new integer()
{
@Override
boolean isAssignDirect()
{
return true;
}
};
}
else if (bdt.getClass() == recid.class)
{
bdt = new recid()
{
@Override
boolean isAssignDirect()
{
return true;
}
};
}
bdt.assign((BaseDataType) value);
}
// documentation states that if the variable is out of scope, then terminate with 10088
// actually, 4GL abends. we let it assign, to not add the scope-overhead to each and
// every variable
if (bdt.getClass().isAssignableFrom(argument.getClass()))
{
bdt.assign((BaseDataType) argument);
}
else
{
bdt.assign(convertTo((BaseDataType) argument, value.getClass()));
if (bdt.isAssignDirect() && CompareOps._isNotEqual(bdt, (BaseDataType) value))
{
try
{
// copy the data from the wrapped reference to the other, if they are unequal
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
bdt.writeExternal(out);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
((BaseDataType) value).readExternal(new ObjectInputStream(bais));
// force this to be registered as undo
((BaseDataType) value).checkUndoable(true);
}
catch (Exception e)
{
throw new IllegalStateException("Problem copying output data", e);
}
}
}
}
else
{
throw new IllegalStateException("Can not use " + value.getClass() + " for OUTPUT.");
}
// reset argument so it gets recreated from initial value on next call
if (mode.inputOutput)
argument = null;
return true;
}
/**
* Convert this parameter's {@link CallParameter#value value} to be usable as an argument.
* <p>
* Once converted, it will be cached in the {@link #argument} variable. The FWD base data type
* class is inferred from parameter data type.
*
* @return The {@link #argument}.
*/
public Object convertTo()
{
return convertTo(BaseDataType.fromTypeName(dataType));
}
/**
* Convert this parameter's {@link CallParameter#value value} to be usable as an argument.
* <p>
* Once converted, it will be cached in the {@link #argument} variable.
*
* @param cls The FWD base data type class for the parameter.
*
* @return The {@link #argument}.
*/
public Object convertTo(Class<?> cls)
{
boolean silent = ErrorManager.isSilent();
if (!silent)
{
// assume a silent mode, so that errors can be captured and re-raised
ErrorManager.setSilent(true);
}
boolean forceError = false;
try
{
if (argument != null)
{
return argument;
}
boolean input = mode.input || mode.inputOutput;
boolean output = mode.output || mode.inputOutput;
Object argVal = value;
Class<?> valueCls = argVal == null ? null : argVal.getClass();
if (valueCls == FieldReference.class)
{
valueCls = ((FieldReference) value).getType();
argVal = ((FieldReference) value).get();
}
else if (valueCls == PropertyReference.class)
{
valueCls = ((PropertyReference) value).getType();
argVal = ((PropertyReference) value).get();
}
if (handle.class.isAssignableFrom(valueCls))
{
if (((handle) argVal).isUnknown())
{
// in this case, leave the reference (property or field), which will need to be updated
}
else
{
WrappedResource res = (WrappedResource) ((handle) argVal).getResource();
if (!res.valid() && "DATASET-HANDLE".equalsIgnoreCase(dataType))
{
// this needs to 'fall through' and not handlded in this method.
forceError = true;
ErrorManager.setSilent(silent);
ErrorManager.recordOrThrowError(12314,
"Invalid DATASET-HANDLE parameter given",
false,
false);
return null;
}
// if a valid dataset is passed, then use that instance exclusively, and discard the
// field/property reference
// for a temp-table arg, then again we work with the handle directly, regardless if is valid
// or not
argVal = new handle(res);
}
}
if ("TABLE-HANDLE".equalsIgnoreCase(dataType) || "DATASET-HANDLE".equalsIgnoreCase(dataType))
{
if (argVal instanceof FieldReference)
{
argVal = new HandleFieldRef((FieldReference) argVal);
}
else if (argVal instanceof PropertyReference)
{
argVal = new HandlePropertyRef((PropertyReference) argVal);
}
}
// for table/dataset handle input/input-output validation is delayed till invoke
if ("TABLE-HANDLE".equalsIgnoreCase(dataType))
{
argument = new TableParameter((handle) argVal, mode.mode, input, output);
return argument;
}
else if ("DATASET-HANDLE".equalsIgnoreCase(dataType))
{
argument = new DataSetParameter((handle) argVal, mode.mode, input, output);
return argument;
}
else if (initialValue instanceof BaseDataType)
{
argument = TypeFactory.initInput((BaseDataType) initialValue);
}
else
{
argument = initialValue;
}
return argument;
}
catch (ErrorConditionException ece)
{
if (forceError)
{
throw ece;
}
ErrorManager.setSilent(silent);
boolean pendingError = ErrorManager.isPendingError();
ErrorManager.clearPending();
int[] nums =
{
ece.getProgressErrorCode(),
5729,
10059
};
String[] texts =
{
ece.getMessage(),
"Incompatible datatypes found during runtime conversion",
"Unable to convert SET-PARAMETER value to datatype passed"
};
if (!pendingError)
{
ErrorManager.recordOrShowError(nums, texts, false, false, false, true);
}
else
{
ErrorManager.recordOrThrowError(nums, texts, false, true);
}
return null;
}
finally
{
ErrorManager.setSilent(silent);
}
}
/**
* Make a copy of this parameter's {@link CallParameter#value value} for input/input-output parameters.
* <p>
* For input and input-output the value saved in the {@link #value} variable points to the value/reference
* used when the parameter was created although that might change in between when the {@link Call#invoke invoke}
* method is called.
*
* @param dataType
* The parameter's data type.
* @param isClass
* Flag indicating that the datatype is a legacy OO class.
* @param mode
* The call mode.
* @param value
* The parameter's value as set in ctor.
*
* @return The current {@link #value} reference for input/input-output.
*/
private Object convertValue(String dataType, boolean isClass, CallMode mode, Object value)
{
if (value instanceof AbstractExtentParameter)
{
// nothing to do here
return value;
}
if (mode.input || mode.inputOutput)
{
Class<? extends BaseDataType> cls = isClass ? object.class : BaseDataType.fromTypeName(dataType);
if (cls != null)
{
BaseDataType val = BaseDataType.generateUnknown(cls);
if (value instanceof BaseDataType)
{
val.assign((BaseDataType) value);
}
else if (value instanceof FieldReference)
{
FieldReference ref = (FieldReference) value;
if (ref.getExtent() != null && ref.getIndex() == -1)
{
// this is an array, the work is not here.
val = null;
}
else
{
val.assign(ref.get());
}
}
else if (value instanceof PropertyReference)
{
PropertyReference ref = (PropertyReference) value;
val.assign(ref.get());
}
else
{
boolean assigned = false;
try
{
Constructor<?> ctor = cls.getConstructor(value.getClass());
if (ctor != null)
{
val.assign((BaseDataType) ctor.newInstance(value));
assigned = true;
}
}
catch (Exception e)
{
// fallback
}
if (!assigned)
{
try
{
Constructor<?> ctor = cls.getConstructor(String.class);
if (ctor != null)
{
val.assign((BaseDataType) ctor.newInstance(value.toString()));
}
}
catch (Exception e)
{
val = null;
}
}
}
return val != null ? val : value;
}
}
// input and input-output need to be wrapped
if (value instanceof BaseDataType)
{
value = OutputParameter.wrap((BaseDataType) value, mode.inputOutput);
}
else if (value instanceof FieldReference)
{
FieldReference ref = (FieldReference) value;
if (ref.getExtent() != null && ref.getIndex() == -1)
{
// this is an array, the work is not here.
}
else
{
value = OutputParameter.wrap((FieldReference) value, mode.inputOutput, ref.getType());
}
}
else if (value instanceof PropertyReference)
{
value = OutputParameter.wrap((PropertyReference) value, mode.inputOutput);
}
if (mode.mode != null && mode.mode != ParameterOption.NONE)
{
if (value instanceof DataSetParameter)
{
((DataSetParameter) value).setParameterOptions(EnumSet.of(mode.mode));
}
else if (value instanceof TableParameter)
{
((TableParameter) value).setParameterOptions(EnumSet.of(mode.mode));
}
}
return value;
}
/**
* Convert the given BDT instance to the specified type.
* <p>
* This will follow explicit rules to convert from the variable's data-type (passed to
* {@link Call#setParameter} and the value received from the emulated CALL invoke.
* <p>
* At this time, the following data types were not explored and can't be converted unless
* there variable's and value's data-type match:
* <ul>
* <li>decimal - this was explored only for converting from character values. Even with
* this type, the conversion process is indeterminate in 4GL - if not all required bytes
* exist so they can be used, random values are used. With other data types and using
* certain values, 4GL even crashes.</li>
* <li>longchar - at the time of this writing, no rules could be extracted for converting
* a longchar to or from another data-type - consecutive runs of the same program
* gives different output.</li>
* <li>raw - was not explored</li>
* <li>rowid - was not explored</li>
* <li>handle - 4GL allows a handle to be initiated with a random value, even if that value
* is not a valid resource ID. Was not explored further, as this requires changes in
* how FWD can initialize a handle.</li>
* <li>datetimetz - could not determine rules to convert a BDT value to a datetimetz data
* type. There might be some constant date or time used as a reference, same as
* date uses the default date offset 2433405 (in days).
* </ul>
*
* @param val
* The instance configured as OUTPUT/INPUT-OUTPUT with the data type as specified
* by {@link Call#setParameter}, and saved in {@link #dataType}.
* @param type
* The data type of the actual instance passed to the {@link Call#setParameter}.
*
* @return The converted value.
*/
private BaseDataType convertTo(BaseDataType val, Class<?> type)
{
if (val.isUnknown() || val.getClass() == type)
{
return val;
}
Runnable logWarning = () ->
{
if (Call.LOG.isLoggable(Level.WARNING))
{
Call.LOG.log(Level.WARNING,
"Attention - converting " + val.getClass() + " to " + type +
" is not handled explicitly by FWD.");
}
};
long defDate = 2433405;
if (int64.class.isAssignableFrom(type))
{
int64 res = new int64();
if (val instanceof int64)
{
res.assign(val);
return res;
}
else if (val instanceof character)
{
String sval = val.toStringMessage();
// converting a char to int/int64 just takes ASCII code for each character
long rval = 0;
if (sval.length() <= 8)
{
// if the value is over 64 bit, it will overflow and 4GL uses 0
for (int i = 0; i < sval.length(); i++)
{
rval = (rval << 8) + sval.charAt(i);
}
}
res.assign(rval);
return res;
}
else if (val.getClass() == date.class)
{
res.assign(-2433405 + ((date) val).longValue());
return res;
}
else if (val.getClass() == datetime.class)
{
// compute the numeric representation of this datetime:
// - upper bytes: date(val) - defDate
// - lower bytes: mtime(val)
raw r = new raw();
r.setLength(8);
r.setLong(datetime.millisecondsSinceMidnight((datetime) val), 1);
r.setLong(((datetime) val).longValue() - defDate, 5);
byte[] bytes = r.getByteArray();
long v = 0;
for (int i = 0; i < bytes.length; i++)
{
int b = bytes[(bytes.length - 1 - i)] & 0xff;
v = v << 8;
v = v + b;
}
res.assign(v);
return res;
}
else if (val.getClass() == datetimetz.class)
{
// this can't convert
res.assign(0);
return res;
}
else if (val instanceof decimal)
{
if (CompareOps._isEqual(val, 0.0d))
{
res.assign(0);
return res;
}
// get the string representation of this decimal value and:
// - if the number of fractional and non-fractional digits is more than 14, then -1
// - leftmost bit is "0x8#", where "#" is the number of fractional digits in hex
// - concatenate the fractional and non-fractional digits
// - push them to the left, in pairs, i.e. 12345 becomes 5f3412 (use F if odd
// number of digits)
String sval = ((decimal) val).toStringMessage();
int dot = sval.indexOf('.');
String fract = dot < 0 ? "" : sval.substring(dot + 1);
String dec = dot < 0 ? sval : sval.substring(0, dot);
String all = dec + fract;
if (all.length() > 14)
{
res.assign(-1);
return res;
}
long v = 0xffffffffffffffffl;
v = (v << 8) + ((0x8 << 4) + fract.length());
while (!all.isEmpty())
{
String chars = all.length() == 1 ? (all + "F") : all.substring(0, 2);
v = (v << 8) + Long.parseLong(chars, 16);
all = all.length() == 1 ? all.substring(1) : all.substring(2);
}
res.assign(v);
return res;
}
}
else if (type == decimal.class)
{
decimal res = new decimal();
if (val instanceof character)
{
String sval = val.toStringMessage();
// this type is a little tricky; it fills the following, by walking the string from
// left to right:
// - first char is ignored
// - always negative
// - first populate 49 fractional digits
// - from what is left, populate 23 non-fractional digits
// - if the length of the string (without first char) is more than 36 chars
// (288 bits), then set it to 0
if (sval.length() > 37 || sval.isEmpty() || sval.length() == 1)
{
res.assign(0);
}
else
{
int maxFraction = 49;
String left = "-0";
String right = "";
for (int i = 1; i < sval.length(); i++)
{
String hex = Integer.toHexString(Byte.toUnsignedInt((byte) sval.charAt(i)));
if (hex.length() < 2)
{
hex = "0" + hex;
}
right = right + hex;
if (right.length() > maxFraction)
{
int diff = right.length() - maxFraction;
left = left + right.substring(0, diff);
right = right.substring(diff);
}
}
if (right.length() < maxFraction)
{
// here, we just pad the fractional part with 0 digits; in 4GL, if the
// fractional parts can't be computed (the string is less than 25 chars), it
// will populate it with random values
right = right + StringHelper.repeatChar('0', maxFraction - right.length());
}
// this 49 fraction is always forced to the internal representation; but this
// can't be used in 4GL unless you want to output this value - any arithmetic
// operation done on this value will abort the process. So, we just keep it
// internally, and still honor the variable's specified 'DECIMALS' clause.
res.setValue(left+right, maxFraction);
}
return res;
}
// TODO: other types
}
else if (type == date.class)
{
// the implicit date
date res = new date(defDate);
boolean skipZero = false;
raw r = new raw();
if (val instanceof character)
{
character ch = (character) val;
if (TextOps.length(ch).intValue() > 0)
{
r.setString(ch, 1, TextOps.byteLength(ch));
}
skipZero = true;
}
else if (val instanceof decimal)
{
if (CompareOps._isEqual(val, 0.0d))
{
return res;
}
else
{
byte[] bytes = Call.decimalBytes((decimal) val);
long v = 0xffffffffffffffffl;
for (int i = 0; i < bytes.length; i++)
{
v = (v << 8) + ((int) bytes[i] & 0xff);
}
res.assign(new int64(defDate + v));
return res;
}
}
else if (val instanceof int64)
{
r.setLong((int64) val, 1);
}
else if (val instanceof logical)
{
r.setByte(((logical) val).getValue() ? 1 : 0, 1);
}
else if (val instanceof datetimetz)
{
logWarning.run();
// TODO: log this, can't get a rule - return default date
return res;
}
else if (val instanceof datetime)
{
long idate = ((datetime) val).longValue();
if (idate == defDate)
{
res = date.plusDays(res, datetime.millisecondsSinceMidnight((datetime) val));
}
return res;
}
else
{
logWarning.run();
}
// from the given byte representation, read only at most 4 non-zero bytes;
// if after reading 4 non-zero bytes there are other bytes, then return implicit date
long[] bytes = new long[4];
int numBytes = 0;
for (int i = 1; i <= r.length().intValue(); i++)
{
long b = r.getByte(i).getValue();
if (skipZero && b == 0)
{
continue;
}
if (numBytes == bytes.length)
{
return res;
}
bytes[numBytes] = b;
numBytes = numBytes + 1;
}
// compute the days to be added to the implicit date
long days = 0;
for (int i = 0; i < numBytes; i++)
{
long b = skipZero ? bytes[i] << ((numBytes - i - 1) * 8)
: bytes[i] << (i * 8);
days = days + b;
}
res = date.plusDays(res, days);
return res;
}
else if (type == character.class || type == character.characterConstant.class)
{
int prefix00 = 0x80;
character res = new character();
boolean[] stripFF = new boolean[] { true };
java.util.function.Function<byte[], Integer> reverseBytes = bytes ->
{
int lastNonNull = bytes.length - 1;
while (lastNonNull >= 0 &&
(bytes[lastNonNull] == 0 ||
(stripFF[0] && bytes[lastNonNull] == (byte) 0xFF)))
{
lastNonNull = lastNonNull - 1;
}
if (lastNonNull < 0)
{
return 0;
}
int blength = lastNonNull + 1;
for (int i = 0; i < blength / 2; i++)
{
byte aux = bytes[i];
bytes[i] = bytes[blength - (i + 1)];
bytes[blength - (i + 1)] = aux;
}
return blength;
};
raw r = new raw();
boolean reverse = false;
if (val instanceof date)
{
r.setLong(((date) val).longValue() - defDate, 1);
if (val instanceof datetime)
{
byte[] bdate = r.getByteArray();
int dateNonNull = reverseBytes.apply(bdate);
r.setInt64(datetime.millisecondsSinceMidnight((datetime) val).longValue(), 1);
byte[] btime = r.getByteArray();
int timeNonNull = reverseBytes.apply(btime);
byte[] bytes;
if (val instanceof datetimetz)
{
// from high to low:
// - 6 bytes are mtime
// - 4 bytes are date
// - 2 bytes are the timezone (in minutes)
r.setLength(0);
r.setShort(((datetimetz) val).getTimeZoneOffset().intValue(), 1);
byte[] btz = r.getByteArray();
reverseBytes.apply(btz);
bytes = new byte[12];
System.arraycopy(btime, 0, bytes, 0, Math.min(btime.length, 6));
System.arraycopy(bdate, 0, bytes, 6, Math.min(bdate.length, 4));
System.arraycopy(btz, 0, bytes, 10, Math.min(btz.length, 2));
}
else
{
// 4 high-order bytes are the date, 4 low-order bytes are mtime
bytes = new byte[dateNonNull + 4];
System.arraycopy(bdate, 0, bytes, 0, dateNonNull);
System.arraycopy(btime, 0, bytes, bytes.length - timeNonNull, timeNonNull);
}
res.forceBytes(bytes);
return res;
}
else
{
reverse = true;
stripFF[0] = true;
}
}
else if (val instanceof decimal)
{
if (CompareOps._isEqual(val, 0.0d))
{
res.assign(character.EMPTY_STRING);
}
else
{
byte[] bytes = Call.decimalBytes((decimal) val);
res.forceBytes(bytes);
}
return res;
}
else if (val instanceof handle)
{
// handle is allowed, and it will try to interpret the value as a resource id
// it will assign the resource id, but it will not be valid if the resource
// doesn't exist
handle h = (handle) val;
Long resId = h.getResourceId();
if (resId != null)
{
r.setLong(resId, 1);
reverse = true;
}
else
{
// TODO: log this, a resource ID was not set for this handle
res.assign(h);
return res;
}
}
else if (val instanceof int64)
{
// int64, integer and recid
r.setLong((int64) val, 1);
stripFF[0] = false;
reverse = true;
}
else if (val instanceof logical)
{
r.setByte(((logical) val).booleanValue() ? 1 : 0, 1);
}
else if (val instanceof rowid)
{
res.assign(val);
return res;
}
else
{
logWarning.run();
res.assign(val);
return res;
}
byte[] bytes = r.getByteArray();
int blength;
if (reverse)
{
blength = reverseBytes.apply(bytes);
}
else
{
int lastNonNull = bytes.length - 1;
while (lastNonNull >= 0 && bytes[lastNonNull] == 0)
{
lastNonNull = lastNonNull - 1;
}
blength = lastNonNull < 0 ? 0 : (lastNonNull + 1);
}
if (blength == 0)
{
res.assign(character.EMPTY_STRING);
}
else
{
// TODO: this NULL prefix is not always added... for date/datetime, 0x7f is used
// as a limit, and for some cases is not even added.
if ((int) (bytes[0] & 0xFF) >= prefix00)
{
// force a null byte...
byte[] b2 = new byte[blength + 1];
System.arraycopy(bytes, 0, b2, 1, blength);
bytes = b2;
blength = bytes.length;
}
res.forceBytes(bytes, blength);
}
return res;
}
else if (type == logical.class || type == logical.logicalConstant.class)
{
logical res = new logical();
if (val instanceof handle)
{
Long resId = handle.resourceId(((handle) val).getResource());
res.assign(resId != null && resId != 0);
}
else if (val instanceof int64)
{
res.assign(CompareOps._isNotEqual(val, new integer(0)));
return res;
}
else if (val instanceof decimal)
{
res.assign(CompareOps._isNotEqual(val, new integer(0)));
return res;
}
else if (val instanceof character)
{
raw r = new raw();
r.setString((character) val, 1);
res.assign(r.getByte(1).intValue() != 0);
return res;
}
else if (val instanceof date)
{
if (val instanceof datetime)
{
res.assign(datetime.millisecondsSinceMidnight((datetime) val).longValue() != 0);
}
else
{
res.assign(((date) val).longValue() != defDate);
}
return res;
}
else
{
logWarning.run();
}
// TODO: other types
}
else if (type == datetime.class)
{
datetime res = new datetime();
// lower 32 bits, signed
// - if negative, defaults to 23:59:59.999 ??????
// - if positive, millis (modulo 24 * 60 * 60 * 1000)
// upper 32 bits - date
if (val instanceof character)
{
// lowest 4 bytes are taken to compute the mtime
String v = ((character) val).toJavaType();
byte[] vb = v.getBytes();
long t = 0;
for (int i = Math.max(0, vb.length - 4); i < vb.length; i++)
{
t = (t << 8) + (byte) (vb[i] & 0xFF);
}
res.setTime(t);
// from what remains, the date is computed
long d = 0;
for (int i = 0; i < vb.length - 4; i++)
{
d = (d << 8) + (byte) (vb[i] & 0xFF);
}
res.setDayNumber(defDate + d);
return res;
}
else if (val.getClass() == date.class)
{
long t = ((date) val).longValue() - defDate;
res.setTime(t);
res.setDayNumber(defDate);
return res;
}
else if (val.getClass() == datetimetz.class)
{
long t = ((datetimetz) val).getTimeZoneOffset().longValue();
res.setTime(t);
res.setDayNumber(defDate);
return res;
}
else if (val.getClass() == decimal.class)
{
byte[] bytes = Call.decimalBytes((decimal) val);
long t = 0;
for (int i = 0; i < bytes.length; i++)
{
t = (t << 8) + (byte) (bytes[i] & 0xff);
}
res.setTime(t);
res.setDayNumber(defDate - 1);
return res;
}
else if (val instanceof int64)
{
long l = ((int64) val).longValue();
res.setTime(l & 0xffffffffl);
res.setDayNumber(defDate + (l & 0xffffffff00000000l));
return res;
}
else
{
logWarning.run();
}
}
else if (type == datetimetz.class)
{
// TODO: find the rules
}
else
{
logWarning.run();
}
BaseDataType res = BaseDataType.generateUnknown(type);
res.assign(val);
return res;
}
/**
* Obtain a brief description of this object. That include: the passing mode, the parameter type, and the
* argument value (or its description).
*
* @return a short string representation of this object.
*/
@Override
public String toString()
{
return "CallParameter{" + mode + "(" + dataType + "):" + argument + "}";
}
}