11/*******************************************************************************
2- * Copyright (c) 2024 aquenos GmbH.
2+ * Copyright (c) 2024-2025 aquenos GmbH.
33 * All rights reserved. This program and the accompanying materials
44 * are made available under the terms of the Eclipse Public License v1.0
55 * which accompanies this distribution, and is available at
5252import org .epics .vtype .VStringArray ;
5353import org .epics .vtype .VType ;
5454import org .phoebus .core .vtypes .VTypeHelper ;
55+ import org .phoebus .pv .jackie .JackiePreferences ;
56+ import org .slf4j .Logger ;
57+ import org .slf4j .LoggerFactory ;
5558
5659import java .nio .ByteBuffer ;
5760import java .nio .DoubleBuffer ;
@@ -75,6 +78,14 @@ public final class ValueConverter {
7578 */
7679 public static final long OFFSET_EPICS_TO_UNIX_EPOCH_SECONDS = 631152000L ;
7780
81+ /**
82+ * Logger used by this class.
83+ *
84+ * The field is package private, so that unit-tests can inject a custom
85+ * implementation.
86+ */
87+ static Logger log = LoggerFactory .getLogger (ValueConverter .class );
88+
7889 private ValueConverter () {
7990 }
8091
@@ -316,6 +327,9 @@ public static VType channelAccessToVType(
316327 * <code>int</code>, and <code>short</code>, arrays of these primitive
317328 * types, {@link String}, and arrays of {@link String}.
318329 *
330+ * @param pv_name
331+ * name of the process variable for which the value is converted. This is
332+ * only used for logging purposes.
319333 * @param object
320334 * object to be converted.
321335 * @param charset
@@ -324,15 +338,20 @@ public static VType channelAccessToVType(
324338 * indicates whether a {@link String} or single element
325339 * <code>String[]</code> array should be converted to a
326340 * <code>DBR_CHAR</code> instead of a <code>DBR_STRING</code>.
341+ * @param long_conversion_mode
342+ * behavior when converting a {@link Long} value that does not fit into an
343+ * {@link Integer}.
327344 * @return
328345 * the converted value.
329346 * @throws IllegalArgumentException
330347 * if <code>object</code> cannot be converted.
331348 */
332349 public static ChannelAccessSimpleOnlyValue <?> objectToChannelAccessSimpleOnlyValue (
350+ String pv_name ,
333351 Object object ,
334352 Charset charset ,
335- boolean convert_string_as_long_string ) {
353+ boolean convert_string_as_long_string ,
354+ JackiePreferences .LongConversionMode long_conversion_mode ) {
336355 if (object instanceof VType vtype ) {
337356 var converted_object = VTypeHelper .toObject (vtype );
338357 // VTypeHelper.toObject returns null if it does not know how to
@@ -367,6 +386,110 @@ public static ChannelAccessSimpleOnlyValue<?> objectToChannelAccessSimpleOnlyVal
367386 if (object instanceof int [] value ) {
368387 return ChannelAccessValueFactory .createLong (value );
369388 }
389+ if (object instanceof Long value ) {
390+ if (value >= Integer .MIN_VALUE && value <= Integer .MAX_VALUE ) {
391+ return ChannelAccessValueFactory .createLong (
392+ new int []{value .intValue ()});
393+ }
394+ return switch (long_conversion_mode ) {
395+ case COERCE -> ChannelAccessValueFactory .createLong (
396+ new int []{coerceToInt (value )});
397+ case COERCE_AND_WARN -> {
398+ final var coerced_value = coerceToInt (value );
399+ log .warn (
400+ "Writing long {} as int {} for PV {}" ,
401+ value ,
402+ coerced_value ,
403+ pv_name );
404+ yield ChannelAccessValueFactory .createLong (
405+ new int []{coerced_value });
406+ }
407+ case CONVERT -> ChannelAccessValueFactory .createDouble (
408+ new double []{value .doubleValue ()});
409+ case CONVERT_AND_WARN -> {
410+ final var converted_value = value .doubleValue ();
411+ log .warn (
412+ "Writing long {} as double {} for PV {}" ,
413+ value ,
414+ converted_value ,
415+ pv_name );
416+ yield ChannelAccessValueFactory .createDouble (
417+ new double []{converted_value });
418+ }
419+ case FAIL -> throw new IllegalArgumentException (
420+ "Cannot write long value " + value
421+ + ". The value is outside the range of a "
422+ + "32-bit signed integer." );
423+ case TRUNCATE -> ChannelAccessValueFactory .createLong (
424+ new int []{value .intValue ()});
425+ case TRUNCATE_AND_WARN -> {
426+ final var truncated_value = value .intValue ();
427+ log .warn (
428+ "Writing long {} as int {} for PV {}" ,
429+ value ,
430+ truncated_value ,
431+ pv_name );
432+ yield ChannelAccessValueFactory .createLong (
433+ new int []{truncated_value });
434+ }
435+ };
436+ }
437+ if (object instanceof long [] value ) {
438+ boolean limit_exceeded = false ;
439+ for (long element : value ) {
440+ if (element < Integer .MIN_VALUE
441+ || element > Integer .MAX_VALUE ) {
442+ limit_exceeded = true ;
443+ break ;
444+ }
445+ }
446+ if (!limit_exceeded ) {
447+ return ChannelAccessValueFactory .createLong (
448+ longArrayToIntArray (value ));
449+ }
450+ return switch (long_conversion_mode ) {
451+ case COERCE -> ChannelAccessValueFactory .createLong (
452+ coerceToInt (value ));
453+ case COERCE_AND_WARN -> {
454+ final var coerced_value = coerceToInt (value );
455+ log .warn (
456+ "Writing long[] {} as int[] {} for PV {}" ,
457+ Arrays .toString (value ),
458+ Arrays .toString (coerced_value ),
459+ pv_name );
460+ yield ChannelAccessValueFactory .createLong (
461+ coerced_value );
462+ }
463+ case CONVERT -> ChannelAccessValueFactory .createDouble (
464+ longArrayToDoubleArray (value ));
465+ case CONVERT_AND_WARN -> {
466+ final var converted_value = longArrayToDoubleArray (value );
467+ log .warn (
468+ "Writing long[] {} as double[] {} for PV {}" ,
469+ Arrays .toString (value ),
470+ Arrays .toString (converted_value ),
471+ pv_name );
472+ yield ChannelAccessValueFactory .createDouble (
473+ converted_value );
474+ }
475+ case FAIL -> throw new IllegalArgumentException (
476+ "Cannot write long[] value " + Arrays .toString (value )
477+ + ". The value is outside the range of a "
478+ + "32-bit signed integer." );
479+ case TRUNCATE -> ChannelAccessValueFactory .createLong (
480+ longArrayToIntArray (value ));
481+ case TRUNCATE_AND_WARN -> {
482+ final var truncated_value = longArrayToIntArray (value );
483+ log .warn (
484+ "Writing long[] {} as int[] {} for PV {}" ,
485+ Arrays .toString (value ),
486+ Arrays .toString (truncated_value ),
487+ pv_name );
488+ yield ChannelAccessValueFactory .createLong (
489+ truncated_value );
490+ }
491+ };
492+ }
370493 if (object instanceof Short value ) {
371494 return ChannelAccessValueFactory .createShort (new short [] {value });
372495 }
@@ -389,7 +512,7 @@ public static ChannelAccessSimpleOnlyValue<?> objectToChannelAccessSimpleOnlyVal
389512 // conversion if the array has a single element.
390513 if (value .length == 1 && convert_string_as_long_string ) {
391514 return objectToChannelAccessSimpleOnlyValue (
392- value [0 ], charset , true );
515+ pv_name , value [0 ], charset , true , long_conversion_mode );
393516 }
394517 return ChannelAccessValueFactory .createString (
395518 Arrays .asList (value ), charset );
@@ -415,6 +538,21 @@ public int size() {
415538 };
416539 }
417540
541+ private static int coerceToInt (long value ) {
542+ if (value > Integer .MAX_VALUE ) {
543+ return Integer .MAX_VALUE ;
544+ } else if (value < Integer .MIN_VALUE ) {
545+ return Integer .MIN_VALUE ;
546+ } else {
547+ return (int ) value ;
548+ }
549+ }
550+
551+ private static int [] coerceToInt (long [] array ) {
552+ return Arrays .stream (array ).mapToInt (
553+ ValueConverter ::coerceToInt ).toArray ();
554+ }
555+
418556 private static Alarm convertAlarm (ChannelAccessTimeValue <?> time_value ) {
419557 AlarmSeverity severity = switch (time_value .getAlarmSeverity ()) {
420558 case NO_ALARM -> AlarmSeverity .NONE ;
@@ -539,6 +677,16 @@ public int size() {
539677 };
540678 }
541679
680+ private static double [] longArrayToDoubleArray (long [] array ) {
681+ return Arrays .stream (array ).mapToDouble (
682+ element -> (double ) element ).toArray ();
683+ }
684+
685+ private static int [] longArrayToIntArray (long [] array ) {
686+ return Arrays .stream (array ).mapToInt (
687+ element -> (int ) element ).toArray ();
688+ }
689+
542690 private static ListShort shortBufferToListShort (ShortBuffer buffer ) {
543691 return new ListShort () {
544692 @ Override
0 commit comments