# ComParameter.java

    /*
    ** Module   : ComParameter.java
    ** Abstract : Holds details for a COM parameter specified at a COM object method call.
    **
    ** Copyright (c) 2017-2019, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 CA  20171025 Created initial version.
    ** 002 CA  20171128 Added support for EXTENT parameters.
    ** 003 CA  20190530 Fixed unknown input value.
    */
    /*
    ** 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.comauto;

    import java.io.*;
    import java.lang.reflect.*;
    import java.util.function.*;

    import com.goldencode.p2j.util.*;

    /**
     * Holds details for a COM parameter specified at a COM object method call.
     */
    public class ComParameter
    implements Externalizable
    {
       /** The data type for an AS clause. */
       @SuppressWarnings("javadoc")
       public static enum DataType
       {
          SHORT,
          FLOAT,
          CURRENCY,
          UNSIGNED_BYTE,
          ERROR_CODE,
          IUNKNOWN;
       };

       /** The parameter's mode, 'I', 'O' or 'U'. */
       private char mode;

       /** The parameter's value. */
       private Object value;

       /** Flag matching {@code true} if {@code BY-POINTER} option is used. */
       private boolean byPointer;

       /** Flag matching {@code true} if {@code BY-VARIANT-POINTER} option is used. */
       private boolean byVariantPointer;

       /** Parameter's {@code AS data-type} option, or {@code null} if not used. */
       private DataType dataType;

       /** Used only by the native side, a string representation of this data type, in FWD terms. */
       private String fwdDataType;

       /** Flag identifying if the {@link #value} is an extent variable. */
       private boolean extent;

       /**
        * In case of {@link #extent}, the current var's length (can be zero for uninitialized dynamic
        * extent).
        */
       private int length;

       /**
        * The reference replacement in case of output/input-output dynamic extent values.
        */
       private final Consumer<BaseDataType[]> refReplacement;

       /**
        * Default c'tor.
        */
       public ComParameter()
       {
          // no-op
          refReplacement = null;
       }

       /**
        * Create a copy of the specified parameter.
        *
        * @param    aparam
        *           Parameter to copy.
        */
       ComParameter(ComParameter aparam)
       {
          this.mode = aparam.mode;
          this.value = aparam.value;
          this.dataType = aparam.dataType;
          this.byPointer = aparam.byPointer;
          this.byVariantPointer = aparam.byVariantPointer;
          this.fwdDataType = aparam.fwdDataType;
          this.extent = aparam.extent;
          this.length = aparam.length;
          this.refReplacement = aparam.refReplacement;
       }

       /**
        * Create a new instance with the specified details.
        *
        * @param    mode
        *           The parameter's mode, 'I', 'O' or 'U'.
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    byPointer
        *           Flag matching {@code true} if {@code BY-POINTER} option is used.
        * @param    byVariantPointer
        *           Flag matching {@code true} if {@code BY-VARIANT-POINTER} option is used.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        */
       private ComParameter(char     mode,
                            Object   value,
                            DataType dataType,
                            boolean  byPointer,
                            boolean  byVariantPointer,
                            Consumer<BaseDataType[]> ref)
       {
          this.mode = mode;
          this.value = value;
          this.byPointer = byPointer;
          this.byVariantPointer = byVariantPointer;
          this.dataType = dataType;
          this.extent = value == null ? false : value.getClass().isArray();
          this.length = extent ? Array.getLength(value) : 0;
          this.refReplacement = ref;
       }

       /**
        * Create an INPUT parameter with an unknown value.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input()
       {
          return new ComParameter('I', new unknown(), null, false, false, null);
       }

       /**
        * Create an INPUT parameter with the specified value.
        *
        * @param    value
        *           The parameter's value.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value)
       {
          return new ComParameter('I', value, null, false, false, null);
       }

       /**
        * Create an OUTPUT parameter with the specified value.
        *
        * @param    value
        *           The parameter's value.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value)
       {
          return new ComParameter('O', value, null, false, false, null);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified value.
        *
        * @param    value
        *           The parameter's value.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value)
       {
          return new ComParameter('U', value, null, false, false, null);
       }

       /**
        * Create an INPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value, DataType dataType)
       {
          return new ComParameter('I', value, dataType, false, false, null);
       }

       /**
        * Create an OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value, DataType dataType)
       {
          return new ComParameter('O', value, dataType, false, false, null);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value, DataType dataType)
       {
          return new ComParameter('U', value, dataType, false, false, null);
       }

       /**
        * Create an INPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value, DataType dataType, boolean isPointer)
       {
          return new ComParameter('I', value, dataType, isPointer, !isPointer, null);
       }

       /**
        * Create an OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value, DataType dataType, boolean isPointer)
       {
          return new ComParameter('O', value, dataType, isPointer, !isPointer, null);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value, DataType dataType, boolean isPointer)
       {
          return new ComParameter('U', value, dataType, isPointer, !isPointer, null);
       }

       /**
        * Create an INPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value, boolean isPointer)
       {
          return new ComParameter('I', value, null, isPointer, !isPointer, null);
       }

       /**
        * Create an OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value, boolean isPointer)
       {
          return new ComParameter('O', value, null, isPointer, !isPointer, null);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value, boolean isPointer)
       {
          return new ComParameter('U', value, null, isPointer, !isPointer, null);
       }

       /**
        * Create an INPUT parameter with the specified value.
        *
        * @param    value
        *           The parameter's value.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('I', value, null, false, false, ref);
       }

       /**
        * Create an OUTPUT parameter with the specified value.
        *
        * @param    value
        *           The parameter's value.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('O', value, null, false, false, ref);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified value.
        *
        * @param    value
        *           The parameter's value.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('U', value, null, false, false, ref);
       }

       /**
        * Create an INPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value, DataType dataType, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('I', value, dataType, false, false, ref);
       }

       /**
        * Create an OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value, DataType dataType, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('O', value, dataType, false, false, ref);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value,
                                              DataType dataType,
                                              Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('U', value, dataType, false, false, ref);
       }

       /**
        * Create an INPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value,
                                        DataType dataType,
                                        boolean isPointer,
                                        Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('I', value, dataType, isPointer, !isPointer, ref);
       }

       /**
        * Create an OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value,
                                         DataType dataType,
                                         boolean isPointer,
                                         Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('O', value, dataType, isPointer, !isPointer, ref);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Parameter's {@code AS data-type} option.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value,
                                              DataType dataType,
                                              boolean isPointer,
                                              Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('U', value, dataType, isPointer, !isPointer, ref);
       }

       /**
        * Create an INPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter input(Object value, boolean isPointer, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('I', value, null, isPointer, !isPointer, ref);
       }

       /**
        * Create an OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter output(Object value, boolean isPointer, Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('O', value, null, isPointer, !isPointer, ref);
       }

       /**
        * Create an INPUT-OUTPUT parameter with the specified details.
        *
        * @param    value
        *           The parameter's value.
        * @param    isPointer
        *           {@code true} represents {@code BY-POINTER} option, {@code false} represents
        *           {@code BY-VARIANT-POINTER} option.
        * @param    ref
        *           The reference replacement for dynamic extent variables.
        *
        * @return   The created {@link ComParameter} instance.
        */
       public static ComParameter inputOutput(Object value,
                                              boolean isPointer,
                                              Consumer<BaseDataType[]> ref)
       {
          return new ComParameter('U', value, null, isPointer, !isPointer, ref);
       }

       /**
        * Check if this is an OUTPUT parameter.
        *
        * @return   See above.
        */
       public boolean isOutput()
       {
          return 'O' == mode;
       }

       /**
        * Check if this is an INPUT-OUTPUT parameter.
        *
        * @return   See above.
        */
       public boolean isInputOutput()
       {
          return 'U' == mode;
       }

       /**
        * Check the {@link #byPointer} flag.
        *
        * @return   {@code true} if the {@code BY-POINTER} option is used.
        */
       public boolean isByPointer()
       {
          return byPointer;
       }

       /**
        * Set the {@link #byPointer} flag.
        *
        * @param    byPointer
        *           {@code true} if the {@code BY-POINTER} option is used.
        */
       public void setByPointer(boolean byPointer)
       {
          this.byPointer = byPointer;
       }

       /**
        * Check the {@link #byVariantPointer} flag.
        *
        * @return   {@code true} if the {@code BY-VARIANT-POINTER} option is used.
        */
       public boolean isByVariantPointer()
       {
          return byVariantPointer;
       }

       /**
        * Set the {@link #byVariantPointer} flag.
        *
        * @param    byVariantPointer
        *           {@code true} if the {@code BY-VARIANT-POINTER} option is used.
        */
       public void setByVariantPointer(boolean byVariantPointer)
       {
          this.byVariantPointer = byVariantPointer;
       }

       /**
        * Get the {@link #dataType} state.
        *
        * @return   {@code null} if no {@code AS data-type} option
        */
       public DataType getDataType()
       {
          return dataType;
       }

       /**
        * Set the parameter's {@link #dataType}.
        *
        * @param    dataType
        *           The parameter's {@code AS data-type} option or {@code null} if not in use.
        */
       public void setDataType(DataType dataType)
       {
          this.dataType = dataType;
       }

       /**
        * Get the parameter's {@link #value}.
        *
        * @return   See above.
        */
       public Object getValue()
       {
          return value;
       }

       /**
        * Set the parameter's {@link #value}.
        *
        * @param    value
        *           The parameter's value.
        */
       public void setValue(Object value)
       {
          this.value = value;
       }

       /**
        * Get the parameter's {@link #mode}.
        *
        * @return   See above.
        */
       public char getMode()
       {
          return mode;
       }

       /**
        * Set the parameter's {@link #mode}.   Only 'I', 'U' or 'O' characters are accepted.
        *
        * @param    mode
        *           The parameter's mode.
        *
        * @throws   IllegalArgumentException
        *           If {@code mode} is not one of 'I', 'U' or 'O' characters.
        */
       public void setMode(char mode)
       {
          if (!(mode == 'I' || mode == 'O' || mode == 'U'))
          {
             throw new IllegalArgumentException(mode + " can not be used for ComParameter!");
          }

          this.mode = mode;
       }

       /**
        * Get the parameter's {@link #fwdDataType}.
        *
        * @return   See above.
        */
       public String getFwdDataType()
       {
          return fwdDataType;
       }

       /**
        * Set the parameter's {@link #fwdDataType}.
        *
        * @param    fwdDataType
        *           The FWD data-type name.
        *
        * @see com.goldencode.p2j.util.BaseDataType#getTypeName() for possible types.
        */
       public void setFwdDataType(String fwdDataType)
       {
          this.fwdDataType = fwdDataType;
       }

       /**
        * Check if this parameter is an extent parameter.
        *
        * @return   See above.
        */
       public boolean isExtent()
       {
          return extent;
       }

       /**
        * Mark this parameter as an extent.
        *
        * @param    extent
        *           <code>true</code> if this is an extent variable.
        */
       public void setExtent(boolean extent)
       {
          this.extent = extent;
       }

       /**
        * Get the parameter's length.  Used only if {@link #extent} is true.
        *
        * @return   See above.
        */
       public int getLength()
       {
          return length;
       }

       /**
        * Get the reference replacement lambda expression.
        *
        * @return   See above.
        */
       public Consumer<BaseDataType[]> getRefReplacement()
       {
          return refReplacement;
       }

       /**
        * Set the length of an {@link #extent} parameter.
        *
        * @param    length
        *           The array length.
        */
       public void setLength(int length)
       {
          this.length = length;
       }

       /**
        * Replacement for the default object writing method. The latest state is written to the
        * output destination.
        *
        * @param    out
        *           The output destination to which fields will be saved.
        *
        * @throws   IOException
        *           In case of I/O errors.
        */
       @Override
       public void writeExternal(ObjectOutput out)
       throws IOException
       {
          out.writeChar(mode);
          out.writeObject(value);
          out.writeBoolean(byPointer);
          out.writeBoolean(byVariantPointer);
          out.writeInt(dataType != null ? dataType.ordinal() : -1);
          out.writeObject(fwdDataType);
          out.writeBoolean(extent);
          out.writeInt(length);
       }

       /**
        * Replacement for the default object reading method. The latest state is read from the
        * input source.
        *
        * @param    in
        *           The input source from which fields will be restored.
        *
        * @throws   IOException
        *           In case of I/O errors.
        * @throws   ClassNotFoundException
        *           If payload can't be instantiated.
        */
       @Override
       public void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          mode = in.readChar();
          value = in.readObject();
          byPointer = in.readBoolean();
          byVariantPointer = in.readBoolean();
          int dtOrdinal = in.readInt();
          if (dtOrdinal != -1)
          {
             dataType = DataType.values()[dtOrdinal];
          }
          fwdDataType = (String) in.readObject();
          extent = in.readBoolean();
          length = in.readInt();
       }
    }