@SuppressWarnings("deprecation")
  static Object[] buildArgs(final Object[] positionalArguments,
                                   final ResourceMethodDescriptor resourceMethod,
                                   final ServerResourceContext context,
                                   final DynamicRecordTemplate template,
                                   final ResourceMethodConfig resourceMethodConfig)
  {
    List<Parameter<?>> parameters = resourceMethod.getParameters();
    Object[] arguments = Arrays.copyOf(positionalArguments, parameters.size());

    fixUpComplexKeySingletonArraysInArguments(arguments);

    boolean attachmentsDesired = false;
    for (int i = positionalArguments.length; i < parameters.size(); ++i)
    {
      Parameter<?> param = parameters.get(i);
      try
      {
        if (param.getParamType() == Parameter.ParamType.KEY || param.getParamType() == Parameter.ParamType.ASSOC_KEY_PARAM)
        {
          Object value = context.getPathKeys().get(param.getName());
          if (value != null)
          {
            arguments[i] = value;
            continue;
          }
        }
        else if (param.getParamType() == Parameter.ParamType.CALLBACK)
        {
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.PARSEQ_CONTEXT_PARAM || param.getParamType() == Parameter.ParamType.PARSEQ_CONTEXT)
        {
          continue; // don't know what to fill in yet
        }
        else if (param.getParamType() == Parameter.ParamType.HEADER)
        {
          HeaderParam headerParam = param.getAnnotations().get(HeaderParam.class);
          String value = context.getRequestHeaders().get(headerParam.value());
          arguments[i] = value;
          continue;
        }
        //Since we have multiple different types of MaskTrees that can be passed into resource methods,
        //we must evaluate based on the param type (annotation used)
        else if (param.getParamType() == Parameter.ParamType.PROJECTION || param.getParamType() == Parameter.ParamType.PROJECTION_PARAM)
        {
          arguments[i] = context.getProjectionMask();
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.METADATA_PROJECTION_PARAM)
        {
          arguments[i] = context.getMetadataProjectionMask();
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.PAGING_PROJECTION_PARAM)
        {
          arguments[i] = context.getPagingProjectionMask();
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.CONTEXT || param.getParamType() == Parameter.ParamType.PAGING_CONTEXT_PARAM)
        {
          PagingContext ctx = RestUtils.getPagingContext(context, (PagingContext) param.getDefaultValue());
          arguments[i] = ctx;
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.PATH_KEYS || param.getParamType() == Parameter.ParamType.PATH_KEYS_PARAM)
        {
          arguments[i] = context.getPathKeys();
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.PATH_KEY_PARAM) {
          Object value = context.getPathKeys().get(param.getName());

          if (value != null)
          {
            arguments[i] = value;
            continue;
          }
        }
        else if (param.getParamType() == Parameter.ParamType.RESOURCE_CONTEXT || param.getParamType() == Parameter.ParamType.RESOURCE_CONTEXT_PARAM)
        {
          arguments[i] = context;
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.VALIDATOR_PARAM)
        {
          RestLiDataValidator validator = new RestLiDataValidator(resourceMethod.getResourceModel().getResourceClass().getAnnotations(),
                                                                  resourceMethod.getResourceModel().getValueClass(), resourceMethod.getMethodType());
          arguments[i] = validator;
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.RESTLI_ATTACHMENTS_PARAM)
        {
          arguments[i] = context.getRequestAttachmentReader();
          attachmentsDesired = true;
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.UNSTRUCTURED_DATA_WRITER_PARAM)
        {
          // The OutputStream is passed to the resource implementation in a synchronous call. Upon return of the
          // resource method, all the bytes would haven't written to the OutputStream. The EntityStream would have
          // contained all the bytes by the time data is requested. The ownership of the OutputStream is passed to
          // the ByteArrayOutputStreamWriter, which is responsible of closing the OutputStream if necessary.
          ByteArrayOutputStream out = new ByteArrayOutputStream();
          context.setResponseEntityStream(EntityStreams.newEntityStream(new ByteArrayOutputStreamWriter(out)));

          arguments[i] = new UnstructuredDataWriter(out, context);
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.UNSTRUCTURED_DATA_REACTIVE_READER_PARAM)
        {
          arguments[i] = new UnstructuredDataReactiveReader(context.getRequestEntityStream(), context.getRawRequest().getHeader(RestConstants.HEADER_CONTENT_TYPE));
          continue;
        }
        else if (param.getParamType() == Parameter.ParamType.POST)
        {
          // handle action parameters
          if (template != null)
          {
            DataMap data = template.data();
            if (data.containsKey(param.getName()))
            {
              arguments[i] = template.getValue(param);
              continue;
            }
          }
        }
        else if (param.getParamType() == Parameter.ParamType.QUERY)
        {
          Object value;
          if (DataTemplate.class.isAssignableFrom(param.getType()))
          {
            value = buildDataTemplateArgument(context.getStructuredParameter(param.getName()), param,
                resourceMethodConfig.shouldValidateQueryParams());
          }
          else
          {
            value = buildRegularArgument(context, param, resourceMethodConfig.shouldValidateQueryParams());
          }

          if (value != null)
          {
            arguments[i] = value;
            continue;
          }
        }
        else if (param.getParamType() == Parameter.ParamType.BATCH || param.getParamType() == Parameter.ParamType.RESOURCE_KEY)
        {
          // should not come to this routine since it should be handled by passing in positionalArguments
          throw new RoutingException("Parameter '" + param.getName() + "' should be passed in as a positional argument",
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
        else
        {
          // unknown param type
          throw new RoutingException(
              "Parameter '" + param.getName() + "' has an unknown parameter type '" + param.getParamType().name() + "'",
              HttpStatus.S_400_BAD_REQUEST.getCode());
        }
      }
      catch (TemplateRuntimeException e)
      {
        throw new RoutingException("Parameter '" + param.getName() + "' is invalid", HttpStatus.S_400_BAD_REQUEST.getCode());
      }

      try
      {
        // Handling null-valued parameters not provided in resource context or entity body
        // check if it is optional parameter
        if (param.isOptional() && param.hasDefaultValue())
        {
          arguments[i] = param.getDefaultValue();
        }
        else if (param.isOptional() && !param.getType().isPrimitive())
        {
          // optional primitive parameter must have default value or provided
          arguments[i] = null;
        }
        else
        {
          throw new RoutingException("Parameter '" + param.getName() + "' is required", HttpStatus.S_400_BAD_REQUEST.getCode());
        }
      }
      catch (ResourceConfigException e)
      {
        // Parameter default value format exception should result in server error code 500.
        throw new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR,
            "Parameter '" + param.getName() + "' default value is invalid", e);
      }
    }

    //Verify that if the resource method did not expect attachments, and attachments were present, that we drain all
    //incoming attachments and send back a bad request. We must take precaution here since simply ignoring the request
    //attachments is not correct behavior here. Ignoring other request level constructs such as headers or query parameters
    //that were not needed is safe, but not for request attachments.
    if (!attachmentsDesired && context.getRequestAttachmentReader() != null)
    {
      throw new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST,
                                       "Resource method endpoint invoked does not accept any request attachments.");
    }
    return arguments;
  }
@Test
  public void testBuildArgsHappyPath()
  {
    //test integer association key integer
    String param1Key = "param1";
    Parameter<Integer> param1 = new Parameter<>(param1Key, Integer.class, DataTemplateUtil.getSchema(Integer.class),
                                          false, null, Parameter.ParamType.ASSOC_KEY_PARAM, false, AnnotationSet.EMPTY);
    Integer param1Value = 123;

    //test regular string argument
    String param2Key = "param2";
    Parameter<String> param2 = new Parameter<>(param2Key, String.class, DataTemplateUtil.getSchema(String.class),
                                          true, null, Parameter.ParamType.QUERY, true, AnnotationSet.EMPTY);
    String param2Value = "param2Value";

    //test data template argument array with more than element
    String param3Key = "param3";
    Parameter<StringArray> param3 = new Parameter<>(param3Key, StringArray.class, DataTemplateUtil.getSchema(StringArray.class),
                                      true, null, Parameter.ParamType.QUERY, true, AnnotationSet.EMPTY);

    DataList param3Value = new DataList(Arrays.asList("param3a", "param3b"));
    StringArray param3Final = new StringArray(param3Value);

    //test data template argument array with only one element
    String param4Key = "param4";
    Parameter<StringArray> param4 = new Parameter<>(param4Key, StringArray.class, DataTemplateUtil.getSchema(StringArray.class),
                                          true, null, Parameter.ParamType.QUERY, true, AnnotationSet.EMPTY);
    String param4Value = "param4Value";
    StringArray param4Final = new StringArray(param4Value);

    // test record template
    String param5Key = "param5";
    Parameter<TestRecord> param5 = new Parameter<>(param5Key, TestRecord.class, DataTemplateUtil.getSchema(TestRecord.class),
                                                             true, null, Parameter.ParamType.QUERY, true, AnnotationSet.EMPTY);
    DataMap param5Value = new DataMap();
    param5Value.put("doubleField", "5.5");
    param5Value.put("floatField", "5");
    param5Value.put("intField", "5");
    param5Value.put("longField", "5");

    TestRecord param5Final = new TestRecord();
    param5Final.setDoubleField(5.5);
    param5Final.setFloatField(5F);
    param5Final.setIntField(5);
    param5Final.setLongField(5);

    // test record template array
    String param6Key = "param6";
    Parameter<TestRecordArray> param6 = new Parameter<>(param6Key, TestRecordArray.class, DataTemplateUtil.getSchema(TestRecordArray.class),
                                                                       true, null, Parameter.ParamType.QUERY, true, AnnotationSet.EMPTY);
    DataList param6Value = new DataList();
    DataMap testRecordDataMap1 = new DataMap();
    testRecordDataMap1.put("doubleField", "6.6");
    testRecordDataMap1.put("floatField", "6");
    testRecordDataMap1.put("intField", "6");
    testRecordDataMap1.put("longField", "6");
    DataMap testRecordDataMap2 = new DataMap();
    testRecordDataMap2.put("doubleField", "66.6");
    testRecordDataMap2.put("floatField", "66");
    testRecordDataMap2.put("intField", "66");
    testRecordDataMap2.put("longField", "66");
    param6Value.add(testRecordDataMap1);
    param6Value.add(testRecordDataMap2);

    TestRecordArray param6Final = new TestRecordArray();
    TestRecord testRecord1 = new TestRecord();
    testRecord1.setDoubleField(6.6);
    testRecord1.setFloatField(6);
    testRecord1.setIntField(6);
    testRecord1.setLongField(6);
    TestRecord testRecord2 = new TestRecord();
    testRecord2.setDoubleField(66.6);
    testRecord2.setFloatField(66);
    testRecord2.setIntField(66);
    testRecord2.setLongField(66);
    param6Final.add(testRecord1);
    param6Final.add(testRecord2);


    List<Parameter<?>> parameters = new ArrayList<>();
    parameters.add(param1);
    parameters.add(param2);
    parameters.add(param3);
    parameters.add(param4);
    parameters.add(param5);
    parameters.add(param6);
    Object[] positionalArguments = new Object[0];

    Capture<String> param1Capture = EasyMock.newCapture();
    Capture<String> param2Capture = EasyMock.newCapture();
    Capture<String> param3Capture = EasyMock.newCapture();
    Capture<String> param4Capture = EasyMock.newCapture();
    Capture<String> param5Capture = EasyMock.newCapture();
    Capture<String> param6Capture = EasyMock.newCapture();

    ServerResourceContext mockResourceContext = EasyMock.createMock(ServerResourceContext.class);
    EasyMock.expect(mockResourceContext.getRequestAttachmentReader()).andReturn(null);
    MutablePathKeys mockPathKeys = EasyMock.createMock(MutablePathKeys.class);
    ResourceMethodDescriptor mockResourceMethodDescriptor = getMockResourceMethod(parameters);

    ResourceMethodConfig mockResourceMethodConfig = EasyMock.createMock(ResourceMethodConfig.class);
    EasyMock.expect(mockResourceMethodConfig.shouldValidateResourceKeys()).andReturn(true).times(5);
    EasyMock.expect(mockResourceMethodConfig.shouldValidateQueryParams()).andReturn(false).times(5);
    EasyMock.replay(mockResourceMethodConfig);

    //easy mock for processing param1
    EasyMock.expect(mockPathKeys.get(EasyMock.capture(param1Capture))).andReturn(param1Value);
    EasyMock.expect(mockResourceContext.getPathKeys()).andReturn(mockPathKeys);
    //easy mock for processing param2
    EasyMock.expect(mockResourceContext.hasParameter(EasyMock.capture(param2Capture))).andReturn(true);
    EasyMock.expect(mockResourceContext.getParameter(EasyMock.capture(param2Capture))).andReturn(param2Value);
    //easy mock for processing param3
    EasyMock.expect(mockResourceContext.getStructuredParameter(EasyMock.capture(param3Capture))).andReturn(param3Value);
    //easy mock for processing param4
    EasyMock.expect(mockResourceContext.getStructuredParameter(EasyMock.capture(param4Capture))).andReturn(param4Value);
    //easy mock for processing param5
    EasyMock.expect(mockResourceContext.getStructuredParameter(EasyMock.capture(param5Capture))).andReturn(param5Value);
    //easy mock for processing param6
    EasyMock.expect(mockResourceContext.getStructuredParameter(EasyMock.capture(param6Capture))).andReturn(param6Value);
    EasyMock.replay(mockResourceContext, mockPathKeys);

    Object[] results = ArgumentBuilder.buildArgs(positionalArguments, mockResourceMethodDescriptor, mockResourceContext, null, mockResourceMethodConfig);

    EasyMock.verify(mockPathKeys, mockResourceContext);
    Assert.assertEquals(param1Capture.getValue(), param1Key);
    Assert.assertEquals(param2Capture.getValue(), param2Key);
    Assert.assertEquals(param3Capture.getValue(), param3Key);
    Assert.assertEquals(param4Capture.getValue(), param4Key);
    Assert.assertEquals(param5Capture.getValue(), param5Key);
    Assert.assertEquals(param6Capture.getValue(), param6Key);

    Assert.assertEquals(results[0], param1Value);
    Assert.assertEquals(results[1], param2Value);
    Assert.assertEquals(results[2], param3Final);
    Assert.assertEquals(results[3], param4Final);
    Assert.assertEquals(results[4], param5Final);
    Assert.assertEquals(results[5], param6Final);
  }