+ final var instance = streamer.getDeclaredField(DataContainerStreamerGenerator.INSTANCE_FIELD);
+ return (DataContainerStreamer<?>) instance.get(null);
+ }
+ });
+ private final LoadingCache<Class<?>, DataContainerSerializer> serializers = CacheBuilder.newBuilder()
+ .build(new CacheLoader<>() {
+ @Override
+ public DataContainerSerializer load(final Class<?> key) throws ExecutionException {
+ return new DataContainerSerializer(BindingCodecContext.this, streamers.get(key));
+ }
+ });
+ private final LoadingCache<Class<? extends DataObject>, DataContainerCodecContext<?, ?, ?>> childrenByClass =
+ CacheBuilder.newBuilder().build(new CacheLoader<>() {
+ @Override
+ public DataContainerCodecContext<?, ?, ?> load(final Class<? extends DataObject> key) {
+ final var childSchema = context.getTypes().bindingChild(JavaTypeName.create(key));
+ if (childSchema instanceof ContainerLikeRuntimeType containerLike) {
+ if (childSchema instanceof ContainerRuntimeType container
+ && container.statement().findFirstEffectiveSubstatement(PresenceEffectiveStatement.class)
+ .isEmpty()) {
+ return new StructuralContainerCodecContext<>(key, container, BindingCodecContext.this);
+ }
+ return new ContainerLikeCodecContext<>(key, containerLike, BindingCodecContext.this);
+ } else if (childSchema instanceof ListRuntimeType list) {
+ return list.keyType() == null ? new ListCodecContext<>(key, list, BindingCodecContext.this)
+ : MapCodecContext.of(key, list, BindingCodecContext.this);
+ } else if (childSchema instanceof ChoiceRuntimeType choice) {
+ return new ChoiceCodecContext<>(key, choice, BindingCodecContext.this);
+ } else if (childSchema == null) {
+ throw DataContainerCodecContext.childNullException(context, key, "%s is not top-level item.", key);
+ } else {
+ throw new IncorrectNestingException("%s is not a valid data tree child of %s", key, this);
+ }
+ }
+ });
+
+ // FIXME: this could also be a leaf!
+ private final LoadingCache<QName, DataContainerCodecContext<?, ?, ?>> childrenByDomArg =
+ CacheBuilder.newBuilder().build(new CacheLoader<>() {
+ @Override
+ public DataContainerCodecContext<?, ?, ?> load(final QName qname) throws ClassNotFoundException {
+ final var type = context.getTypes();
+ final var child = type.schemaTreeChild(qname);
+ if (child == null) {
+ final var module = qname.getModule();
+ if (context.modelContext().findModule(module).isEmpty()) {
+ throw new MissingSchemaException(
+ "Module " + module + " is not present in current schema context.");
+ }
+ throw new IncorrectNestingException("Argument %s is not valid child of %s", qname, type);
+ }
+
+ if (!(child instanceof DataRuntimeType)) {
+ throw new IncorrectNestingException("Argument %s is not valid data tree child of %s", qname, type);
+ }
+
+ // TODO: improve this check?
+ final var childSchema = child.statement();
+ if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) {
+ return getStreamChild(context.loadClass(child.javaType()));
+ }
+
+ throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass());
+ }
+ });
+
+ private final LoadingCache<Class<? extends DataObject>, ChoiceCodecContext<?>> choicesByClass =
+ CacheBuilder.newBuilder().build(new CacheLoader<>() {
+ @Override
+ public ChoiceCodecContext<?> load(final Class<? extends DataObject> caseType) {
+ final var choiceClass = findCaseChoice(caseType);
+ if (choiceClass == null) {
+ throw new IllegalArgumentException(caseType + " is not a valid case representation");
+ }
+ if (context.getSchemaDefinition(choiceClass) instanceof ChoiceRuntimeType choiceType) {
+ // FIXME: accurate type!
+ return new ChoiceCodecContext(choiceClass, choiceType, BindingCodecContext.this);
+ }
+ throw new IllegalArgumentException(caseType + " does not refer to a choice");
+ }
+
+ private static Class<?> findCaseChoice(final Class<? extends DataObject> caseClass) {
+ for (var type : caseClass.getGenericInterfaces()) {
+ if (type instanceof Class<?> typeClass && ChoiceIn.class.isAssignableFrom(typeClass)) {
+ return typeClass.asSubclass(ChoiceIn.class);
+ }
+ }
+ return null;
+ }
+ });
+
+ private final LoadingCache<Class<? extends Action<?, ?, ?>>, ActionCodecContext> actionsByClass =
+ CacheBuilder.newBuilder().build(new CacheLoader<>() {
+ @Override
+ public ActionCodecContext load(final Class<? extends Action<?, ?, ?>> action) {
+ if (KeyedListAction.class.isAssignableFrom(action)) {
+ return prepareActionContext(2, 3, 4, action, KeyedListAction.class);
+ } else if (Action.class.isAssignableFrom(action)) {
+ return prepareActionContext(1, 2, 3, action, Action.class);
+ }
+ throw new IllegalArgumentException("The specific action type does not exist for action "
+ + action.getName());
+ }
+
+ private ActionCodecContext prepareActionContext(final int inputOffset, final int outputOffset,
+ final int expectedArgsLength, final Class<? extends Action<?, ?, ?>> action,
+ final Class<?> actionType) {
+ final var args = ClassLoaderUtils.findParameterizedType(action, actionType)
+ .orElseThrow(() -> new IllegalStateException(action + " does not specialize " + actionType))
+ .getActualTypeArguments();
+ checkArgument(args.length == expectedArgsLength, "Unexpected (%s) Action generatic arguments",
+ args.length);
+ final ActionRuntimeType schema = context.getActionDefinition(action);
+ return new ActionCodecContext(
+ new ContainerLikeCodecContext(asClass(args[inputOffset], RpcInput.class), schema.input(),
+ BindingCodecContext.this),
+ new ContainerLikeCodecContext(asClass(args[outputOffset], RpcOutput.class), schema.output(),
+ BindingCodecContext.this));
+ }
+
+ private static <T extends DataObject> Class<? extends T> asClass(final Type type, final Class<T> target) {
+ verify(type instanceof Class, "Type %s is not a class", type);
+ return ((Class<?>) type).asSubclass(target);
+ }
+ });
+
+ private final LoadingCache<Class<?>, NotificationCodecContext<?>> notificationsByClass = CacheBuilder.newBuilder()
+ .build(new CacheLoader<>() {
+ @Override
+ public NotificationCodecContext<?> load(final Class<?> key) {
+ final var runtimeType = context.getTypes().bindingChild(JavaTypeName.create(key));
+ if (runtimeType instanceof NotificationRuntimeType notification) {
+ return new NotificationCodecContext<>(key, notification, BindingCodecContext.this);
+ } if (runtimeType != null) {
+ throw new IllegalArgumentException(key + " maps to unexpected " + runtimeType);
+ }
+ throw new IllegalArgumentException(key + " is not a known class");
+ }
+ });
+ private final LoadingCache<Absolute, NotificationCodecContext<?>> notificationsByPath =
+ CacheBuilder.newBuilder().build(new CacheLoader<>() {
+ @Override
+ public NotificationCodecContext<?> load(final Absolute key) {
+ final var cls = context.getClassForSchema(key);
+ try {
+ return getNotificationContext(cls.asSubclass(Notification.class));
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException("Path " + key + " does not represent a notification", e);
+ }
+ }
+ });
+
+ private final LoadingCache<Class<?>, ContainerLikeCodecContext<?>> rpcDataByClass =
+ CacheBuilder.newBuilder().build(new CacheLoader<>() {
+ @Override
+ public ContainerLikeCodecContext<?> load(final Class<?> key) {
+ final var runtimeType = context.getTypes().findSchema(JavaTypeName.create(key))
+ .orElseThrow(() -> new IllegalArgumentException(key + " is not a known class"));
+ if (RpcInput.class.isAssignableFrom(key) && runtimeType instanceof InputRuntimeType input) {
+ // FIXME: accurate type
+ return new ContainerLikeCodecContext(key, input, BindingCodecContext.this);
+ } else if (RpcOutput.class.isAssignableFrom(key) && runtimeType instanceof OutputRuntimeType output) {
+ // FIXME: accurate type
+ return new ContainerLikeCodecContext(key, output, BindingCodecContext.this);
+ } else {
+ throw new IllegalArgumentException(key + " maps to unexpected " + runtimeType);
+ }