//check the loaded object support (i.e. config) to see if this is a config item
private DeferredParameter loadObjectInstanceImpl(Object param, Map<Object, DeferredParameter> existing,
            Class<?> expectedType, boolean relaxedValidation) {
        //null is easy
        if (param == null) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext creator, MethodCreator method, ResultHandle array) {
                    return method.loadNull();
                }
            };
        }
        //check the loaded object support (i.e. config) to see if this is a config item
        DeferredParameter loadedObject = findLoaded(param);
        if (loadedObject != null) {
            return loadedObject;
        }
        //Handle empty collections as returned by the Collections object
        loadedObject = handleCollectionsObjects(param, existing, relaxedValidation);
        if (loadedObject != null) {
            return loadedObject;
        }
        //create the appropriate DeferredParameter, a lot of these a fairly simple constant values,
        //but some are quite complex when dealing with objects and collections
        if (substitutions.containsKey(param.getClass()) || substitutions.containsKey(expectedType)) {
            //check for substitution types, if present we invoke recursively on the substitution
            SubstitutionHolder holder = substitutions.get(param.getClass());
            if (holder == null) {
                holder = substitutions.get(expectedType);
            }
            try {
                ObjectSubstitution substitution = holder.sub.newInstance();
                Object res = substitution.serialize(param);
                DeferredParameter serialized = loadObjectInstance(res, existing, holder.to, relaxedValidation);
                SubstitutionHolder finalHolder = holder;
                return new DeferredArrayStoreParameter() {
                    @Override
                    void doPrepare(MethodContext context) {
                        serialized.prepare(context);
                        super.doPrepare(context);
                    }
                    @Override
                    ResultHandle createValue(MethodContext creator, MethodCreator method, ResultHandle array) {
                        ResultHandle subInstance = method.newInstance(MethodDescriptor.ofConstructor(finalHolder.sub));
                        return method.invokeInterfaceMethod(
                                ofMethod(ObjectSubstitution.class, "deserialize", Object.class, Object.class), subInstance,
                                creator.loadDeferred(serialized));
                    }
                };
            } catch (Exception e) {
                throw new RuntimeException("Failed to substitute " + param, e);
            }
        } else if (param instanceof Optional) {
            Optional val = (Optional) param;
            if (val.isPresent()) {
                DeferredParameter res = loadObjectInstance(val.get(), existing, Object.class, relaxedValidation);
                return new DeferredArrayStoreParameter() {
                    @Override
                    void doPrepare(MethodContext context) {
                        res.prepare(context);
                        super.doPrepare(context);
                    }
                    @Override
                    ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {
                        return method.invokeStaticMethod(ofMethod(Optional.class, "of", Optional.class, Object.class),
                                context.loadDeferred(res));
                    }
                };
            } else {
                return new DeferredArrayStoreParameter() {
                    @Override
                    ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {
                        return method.invokeStaticMethod(ofMethod(Optional.class, "empty", Optional.class));
                    }
                };
            }
        } else if (param instanceof String) {
            if (((String) param).length() > 65535) {
                throw new RuntimeException("String too large to record: " + param);
            }
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((String) param);
                }
            };
        } else if (param instanceof Integer) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Integer.class, "valueOf", Integer.class, int.class),
                            method.load((Integer) param));
                }
            };
        } else if (param instanceof Boolean) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Boolean.class, "valueOf", Boolean.class, boolean.class),
                            method.load((Boolean) param));
                }
            };
        } else if (param instanceof URL) {
            String url = ((URL) param).toExternalForm();
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    AssignableResultHandle value = method.createVariable(URL.class);
                    try (TryBlock et = method.tryBlock()) {
                        et.assign(value, et.newInstance(MethodDescriptor.ofConstructor(URL.class, String.class), et.load(url)));
                        try (CatchBlockCreator malformed = et.addCatch(MalformedURLException.class)) {
                            malformed.throwException(RuntimeException.class, "Malformed URL", malformed.getCaughtException());
                        }
                    }
                    return value;
                }
            };
        } else if (param instanceof Enum) {
            Enum e = (Enum) param;
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    ResultHandle nm = method.load(e.name());
                    return method.invokeStaticMethod(
                            ofMethod(e.getDeclaringClass(), "valueOf", e.getDeclaringClass(), String.class),
                            nm);
                }
            };
        } else if (param instanceof ReturnedProxy) {
            //if this is a proxy we just grab the value from the StartupContext
            ReturnedProxy rp = (ReturnedProxy) param;
            if (!rp.__static$$init() && staticInit) {
                throw new RuntimeException("Invalid proxy passed to recorder. " + rp
                        + " was created in a runtime recorder method, while this recorder is for a static init method. The object will not have been created at the time this method is run.");
            }
            String proxyId = rp.__returned$proxy$key();
            //because this is the result of a method invocation that may not have happened at param deserialization time
            //we just load it from the startup context
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeVirtualMethod(ofMethod(StartupContext.class, "getValue", Object.class, String.class),
                            method.getMethodParam(0), method.load(proxyId));
                }
            };
        } else if (param instanceof Duration) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Duration.class, "parse", Duration.class, CharSequence.class),
                            method.load(param.toString()));
                }
            };
        } else if (param instanceof Class<?>) {
            if (!((Class) param).isPrimitive()) {
                // Only try to load the class by name if it is not a primitive class
                String name = classProxies.get(param);
                if (name == null) {
                    name = ((Class) param).getName();
                }
                String finalName = name;
                return new DeferredParameter() {
                    @Override
                    ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                        ResultHandle currentThread = method
                                .invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class));
                        ResultHandle tccl = method.invokeVirtualMethod(
                                ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class),
                                currentThread);
                        return method.invokeStaticMethod(
                                ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class),
                                method.load(finalName), method.load(true), tccl);
                    }
                };
            } else {
                // Else load the primitive type by reference; double.class => Class var9 = Double.TYPE;
                return new DeferredParameter() {
                    @Override
                    ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                        return method.loadClass((Class) param);
                    }
                };
            }
        } else if (expectedType == boolean.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((boolean) param);
                }
            };
        } else if (expectedType == Boolean.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Boolean.class, "valueOf", Boolean.class, boolean.class),
                            method.load((boolean) param));
                }
            };
        } else if (expectedType == int.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((int) param);
                }
            };
        } else if (expectedType == Integer.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Integer.class, "valueOf", Integer.class, int.class),
                            method.load((int) param));
                }
            };
        } else if (expectedType == short.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((short) param);
                }
            };
        } else if (expectedType == Short.class || param instanceof Short) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Short.class, "valueOf", Short.class, short.class),
                            method.load((short) param));
                }
            };
        } else if (expectedType == byte.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((byte) param);
                }
            };
        } else if (expectedType == Byte.class || param instanceof Byte) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Byte.class, "valueOf", Byte.class, byte.class),
                            method.load((byte) param));
                }
            };
        } else if (expectedType == char.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((char) param);
                }
            };
        } else if (expectedType == Character.class || param instanceof Character) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Character.class, "valueOf", Character.class, char.class),
                            method.load((char) param));
                }
            };
        } else if (expectedType == long.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((long) param);
                }
            };
        } else if (expectedType == Long.class || param instanceof Long) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Long.class, "valueOf", Long.class, long.class),
                            method.load((long) param));
                }
            };
        } else if (expectedType == float.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((float) param);
                }
            };
        } else if (expectedType == Float.class || param instanceof Float) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Float.class, "valueOf", Float.class, float.class),
                            method.load((float) param));
                }
            };
        } else if (expectedType == double.class) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((double) param);
                }
            };
        } else if (expectedType == Double.class || param instanceof Double) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.invokeStaticMethod(ofMethod(Double.class, "valueOf", Double.class, double.class),
                            method.load((double) param));
                }
            };
        } else if (expectedType.isArray()) {
            int length = Array.getLength(param);
            DeferredParameter[] components = new DeferredParameter[length];
            for (int i = 0; i < length; ++i) {
                DeferredParameter component = loadObjectInstance(Array.get(param, i), existing,
                        expectedType.getComponentType(), relaxedValidation);
                components[i] = component;
            }
            return new DeferredArrayStoreParameter() {
                @Override
                void doPrepare(MethodContext context) {
                    for (int i = 0; i < length; ++i) {
                        components[i].prepare(context);
                    }
                    super.doPrepare(context);
                }
                @Override
                ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {
                    //TODO large arrays can still generate a fair bit of bytecode, and there appears to be a gizmo issue that prevents casting to an array
                    //fix this later
                    ResultHandle out = method.newArray(expectedType.getComponentType(), length);
                    for (int i = 0; i < length; ++i) {
                        method.writeArrayValue(out, i, context.loadDeferred(components[i]));
                    }
                    return out;
                }
            };
        } else if (param instanceof AnnotationProxy) {
            // new com.foo.MyAnnotation_Proxy_AnnotationLiteral("foo")
            AnnotationProxy annotationProxy = (AnnotationProxy) param;
            List<MethodInfo> constructorParams = annotationProxy.getAnnotationClass().methods().stream()
                    .filter(m -> !m.name().equals("<clinit>") && !m.name().equals("<init>")).collect(Collectors.toList());
            Map<String, AnnotationValue> annotationValues = annotationProxy.getAnnotationInstance().values().stream()
                    .collect(Collectors.toMap(AnnotationValue::name, Function.identity()));
            DeferredParameter[] constructorParamsHandles = new DeferredParameter[constructorParams.size()];
            for (ListIterator<MethodInfo> iterator = constructorParams.listIterator(); iterator.hasNext();) {
                MethodInfo valueMethod = iterator.next();
                Object explicitValue = annotationProxy.getValues().get(valueMethod.name());
                if (explicitValue != null) {
                    constructorParamsHandles[iterator.previousIndex()] = loadObjectInstance(explicitValue, existing,
                            explicitValue.getClass(), relaxedValidation);
                } else {
                    AnnotationValue value = annotationValues.get(valueMethod.name());
                    if (value == null) {
                        // method.invokeInterfaceMethod(MAP_PUT, valuesHandle, method.load(entry.getKey()), loadObjectInstance(method, entry.getValue(),
                        // returnValueResults, entry.getValue().getClass()));
                        Object defaultValue = annotationProxy.getDefaultValues().get(valueMethod.name());
                        if (defaultValue != null) {
                            constructorParamsHandles[iterator.previousIndex()] = loadObjectInstance(defaultValue, existing,
                                    defaultValue.getClass(), relaxedValidation);
                            continue;
                        }
                        if (value == null) {
                            value = valueMethod.defaultValue();
                        }
                    }
                    if (value == null) {
                        throw new NullPointerException("Value not set for " + param);
                    }
                    DeferredParameter retValue = loadValue(value, annotationProxy.getAnnotationClass(), valueMethod);
                    constructorParamsHandles[iterator.previousIndex()] = retValue;
                }
            }
            return new DeferredArrayStoreParameter() {
                @Override
                ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {
                    MethodDescriptor constructor = MethodDescriptor.ofConstructor(annotationProxy.getAnnotationLiteralType(),
                            constructorParams.stream().map(m -> m.returnType().name().toString()).toArray());
                    ResultHandle[] args = new ResultHandle[constructorParamsHandles.length];
                    for (int i = 0; i < constructorParamsHandles.length; i++) {
                        DeferredParameter deferredParameter = constructorParamsHandles[i];
                        if (deferredParameter instanceof DeferredArrayStoreParameter) {
                            DeferredArrayStoreParameter arrayParam = (DeferredArrayStoreParameter) deferredParameter;
                            arrayParam.doPrepare(context);
                        }
                        args[i] = context.loadDeferred(deferredParameter);
                    }
                    return method.newInstance(constructor, args);
                }
            };
        } else {
            return loadComplexObject(param, existing, expectedType, relaxedValidation);
        }
    }
};
}
//check the loaded object support (i.e. config) to see if this is a config item
DeferredParameter loadedObject = findLoaded(param);
if (loadedObject != null) {
Class<?> expectedType, boolean relaxedValidation) {
//null is easy
if (param == null) {
return new DeferredParameter() {
@Override
ResultHandle doLoad(MethodContext creator, MethodCreator method, ResultHandle array) {
return method.loadNull();
}
};
}
//check the loaded object support (i.e. config) to see if this is a config item
DeferredParameter loadedObject = findLoaded(param);
if (loadedObject != null) {
return loadedObject;
}
//Handle empty collections as returned by the Collections object
loadedObject = handleCollectionsObjects(param, existing, relaxedValidation);
if (loadedObject != null) {
return loadedObject;
}
//create the appropriate DeferredParameter, a lot of these a fairly simple constant values,
private DeferredParameter loadObjectInstanceImpl(Object param, Map<Object, DeferredParameter> existing,
Class<?> expectedType, boolean relaxedValidation) {
//null is easy
if (param == null) {
return new DeferredParameter() {
@Override
ResultHandle doLoad(MethodContext creator, MethodCreator method, ResultHandle array) {
return method.loadNull();
}
};
}
//check the loaded object support (i.e. config) to see if this is a config item
DeferredParameter loadedObject = findLoaded(param);
if (loadedObject != null) {
return loadedObject;
}
//Handle empty collections as returned by the Collections object
loadedObject = handleCollectionsObjects(param, existing, relaxedValidation);
if (loadedObject != null) {
return loadedObject;
}
//create the appropriate DeferredParameter, a lot of these a fairly simple constant values,
//but some are quite complex when dealing with objects and collections
if (substitutions.containsKey(param.getClass()) || substitutions.containsKey(expectedType)) {
//check for substitution types, if present we invoke recursively on the substitution
SubstitutionHolder holder = substitutions.get(param.getClass());
if (holder == null) {
holder = substitutions.get(expectedType);
}
try {
ObjectSubstitution substitution = holder.sub.newInstance();
Object res = substitution.serialize(param);