+ 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) {
+ // FIXME: sharpen check to an Notification.class
+ checkArgument(key.isInterface(), "Supplied class must be interface.");
+
+ // TODO: we should be able to work with bindingChild() instead of schemaTreeChild() here
+ final var qname = BindingReflections.findQName(key);
+ if (context.getTypes().schemaTreeChild(qname) instanceof NotificationRuntimeType type) {
+ return new NotificationCodecContext<>(key, type, BindingCodecContext.this);
+ }
+ throw new IllegalArgumentException("Supplied " + key + " is not valid notification");
+ }
+ });
+ 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 Function<RpcRuntimeType, ContainerLikeRuntimeType<?, ?>> lookup;
+ if (RpcInput.class.isAssignableFrom(key)) {
+ lookup = RpcRuntimeType::input;
+ } else if (RpcOutput.class.isAssignableFrom(key)) {
+ lookup = RpcRuntimeType::output;
+ } else {
+ throw new IllegalArgumentException(key + " does not represent an RPC container");
+ }
+
+ final QName qname = BindingReflections.findQName(key);
+ final QNameModule qnameModule = qname.getModule();
+ final Module module = context.getEffectiveModelContext().findModule(qnameModule)
+ .orElseThrow(() -> new IllegalArgumentException("Failed to find module for " + qnameModule));
+ final String className = Naming.getClassName(qname);
+
+ for (var potential : module.getRpcs()) {
+ final QName potentialQName = potential.getQName();
+ /*
+ * Check if rpc and class represents data from same module and then checks if rpc local name
+ * produces same class name as class name appended with Input/Output based on QName associated
+ * with binding class.
+ *
+ * FIXME: Rework this to have more precise logic regarding Binding Specification.
+ */
+ if (key.getSimpleName().equals(Naming.getClassName(potentialQName) + className)) {
+ final ContainerLike schema = getRpcDataSchema(potential, qname);
+ checkArgument(schema != null, "Schema for %s does not define input / output.", potentialQName);
+
+ final var runtimeType = context.getTypes().schemaTreeChild(potentialQName);
+ if (runtimeType instanceof RpcRuntimeType rpcType) {
+ // FIXME: accurate type
+ return new ContainerLikeCodecContext(key, lookup.apply(rpcType), BindingCodecContext.this);
+ }
+ throw new IllegalArgumentException("Cannot find runtime type for " + key);
+ }
+ }
+
+ throw new IllegalArgumentException("Supplied class " + key + " is not valid RPC class.");
+ }
+
+ /**
+ * Returns RPC input or output schema based on supplied QName.
+ *
+ * @param rpc RPC Definition
+ * @param qname input or output QName with namespace same as RPC
+ * @return input or output schema. Returns null if RPC does not have input/output specified.
+ */
+ private static @Nullable ContainerLike getRpcDataSchema(final @NonNull RpcDefinition rpc,
+ final @NonNull QName qname) {
+ requireNonNull(rpc, "Rpc Schema must not be null");
+ return switch (requireNonNull(qname, "QName must not be null").getLocalName()) {
+ case "input" -> rpc.getInput();
+ case "output" -> rpc.getOutput();
+ default -> throw new IllegalArgumentException(
+ "Supplied qname " + qname + " does not represent rpc input or output.");
+ };