+
+ /**
+ * This similar to {@link PhaseModificationInNamespace}, but allows recursive descent until it finds the real
+ * target. The mechanics is driven as a sequence of prerequisites along a path: first we hook onto namespace to
+ * give us the first step. When it does, we hook onto the first item to provide us the second step and so on.
+ */
+ private final class PhaseModificationInNamespacePath<C extends Mutable<?, ?, ?>, K,
+ N extends IdentifierNamespace<K, ? extends StmtContext<?, ?, ?>>> extends AbstractPrerequisite<C>
+ implements OnNamespaceItemAdded, ContextMutation {
+ private final ModelProcessingPhase modPhase;
+ private final Iterable<K> keys;
+ private final Iterator<K> it;
+
+ PhaseModificationInNamespacePath(final ModelProcessingPhase phase, final Iterable<K> keys) {
+ this.modPhase = requireNonNull(phase);
+ this.keys = requireNonNull(keys);
+ it = keys.iterator();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return isApplied();
+ }
+
+ @Override
+ public void namespaceItemAdded(final StatementContextBase<?, ?, ?> context, final Class<?> namespace,
+ final Object key, final Object value) {
+ LOG.debug("Action for {} got key {}", keys, key);
+
+ final StatementContextBase<?, ?, ?> target = contextImpl(value);
+ if (!target.isSupportedByFeatures()) {
+ LOG.debug("Key {} in {} is not supported", key, keys);
+ resolvePrereq(null);
+ action.prerequisiteUnavailable(this);
+ return;
+ }
+
+ // Hook onto target: we either have a modification of the target itself or one of its children.
+ target.addMutation(modPhase, this);
+ // We have completed the context -> target step, hence we are no longer directly blocking context from
+ // making forward progress.
+ context.removeMutation(modPhase, this);
+
+ if (!it.hasNext()) {
+ // Last step: we are done
+ if (resolvePrereq((C) value)) {
+ tryApply();
+ }
+ return;
+ }
+
+ // Make sure target's storage notifies us when the next step becomes available.
+ hookOnto(target, namespace, it.next());
+ }
+
+ @Override
+ ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
+ return super.addToStringAttributes(toStringHelper).add("phase", modPhase).add("keys", keys);
+ }
+
+ void hookOnto(final StmtContext<?, ?, ?> context, final Class<?> namespace) {
+ checkArgument(it.hasNext(), "Namespace %s keys may not be empty", namespace);
+ hookOnto(contextImpl(context), namespace, it.next());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void hookOnto(final StatementContextBase<?, ?, ?> context, final Class<?> namespace, final K key) {
+ context.onNamespaceItemAddedAction((Class) namespace, requireNonNull(key), this);
+ }
+ }