public static void mergeParams(
      Map<String, ParamDefinition> params,
      Map<String, ParamDefinition> paramsToMerge,
      MergeContext context) {
    if (paramsToMerge == null) {
      return;
    }
    Stream.concat(params.keySet().stream(), paramsToMerge.keySet().stream())
        .forEach(
            name -> {
              ParamDefinition paramToMerge = paramsToMerge.get(name);
              if (paramToMerge == null) {
                return;
              }

              if (paramToMerge.getType() == ParamType.MAP && paramToMerge.isLiteral()) {
                Map<String, ParamDefinition> baseMap = mapValueOrEmpty(params, name);
                Map<String, ParamDefinition> toMergeMap = mapValueOrEmpty(paramsToMerge, name);
                mergeParams(
                    baseMap,
                    toMergeMap,
                    MergeContext.copyWithParentMode(
                        context, params.getOrDefault(name, paramToMerge).getMode()));
                params.put(
                    name,
                    buildMergedParamDefinition(
                        name, paramToMerge, params.get(name), context, baseMap));
              } else if (paramToMerge.getType() == ParamType.STRING_MAP
                  && paramToMerge.isLiteral()) {
                Map<String, String> baseMap = stringMapValueOrEmpty(params, name);
                Map<String, String> toMergeMap = stringMapValueOrEmpty(paramsToMerge, name);
                baseMap.putAll(toMergeMap);
                params.put(
                    name,
                    buildMergedParamDefinition(
                        name, paramToMerge, params.get(name), context, baseMap));
              } else {
                params.put(
                    name,
                    buildMergedParamDefinition(
                        name, paramToMerge, params.get(name), context, paramToMerge.getValue()));
              }
            });
  }
@Test
  public void testMergeNoOverwrite() throws JsonProcessingException {
    Map<String, ParamDefinition> allParams =
        parseParamDefMap(
            "{'tomerge1': {'type': 'STRING','value': 'hello', 'source': 'SYSTEM', 'validator': '@notEmpty'}}");
    Map<String, ParamDefinition> paramsToMerge =
        parseParamDefMap(
            "{'tomerge2': {'type': 'STRING', 'value': 'goodbye', 'validator': 'param != null'}}");
    ParamsMergeHelper.mergeParams(allParams, paramsToMerge, definitionContext);
    assertEquals(2, allParams.size());
    assertEquals("hello", allParams.get("tomerge1").asStringParamDef().getValue());
    assertEquals("goodbye", allParams.get("tomerge2").asStringParamDef().getValue());
    assertEquals(ParamSource.SYSTEM, allParams.get("tomerge1").asStringParamDef().getSource());
    assertEquals(ParamSource.DEFINITION, allParams.get("tomerge2").asStringParamDef().getSource());
    assertEquals(
        "@notEmpty", allParams.get("tomerge1").asStringParamDef().getValidator().getRule());
    assertEquals(
        "param != null", allParams.get("tomerge2").asStringParamDef().getValidator().getRule());
  }