1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.testsupport.builder;
7   
8   import java.util.ArrayList;
9   import java.util.List;
10  import java.util.function.Consumer;
11  
12  import dev.metaschema.core.metapath.IMetapathExpression;
13  import dev.metaschema.core.model.ISource;
14  import dev.metaschema.core.model.constraint.AbstractConstraintBuilder;
15  import dev.metaschema.core.model.constraint.AssemblyConstraintSet;
16  import dev.metaschema.core.model.constraint.IAllowedValuesConstraint;
17  import dev.metaschema.core.model.constraint.ICardinalityConstraint;
18  import dev.metaschema.core.model.constraint.IConstraint;
19  import dev.metaschema.core.model.constraint.IExpectConstraint;
20  import dev.metaschema.core.model.constraint.IIndexConstraint;
21  import dev.metaschema.core.model.constraint.IIndexHasKeyConstraint;
22  import dev.metaschema.core.model.constraint.IMatchesConstraint;
23  import dev.metaschema.core.model.constraint.IModelConstrained;
24  import dev.metaschema.core.model.constraint.IUniqueConstraint;
25  import dev.metaschema.core.model.constraint.MetaConstraintSet;
26  import edu.umd.cs.findbugs.annotations.NonNull;
27  import edu.umd.cs.findbugs.annotations.Nullable;
28  
29  /**
30   * Implementation of {@link IContextBuilder} for creating constraint contexts.
31   * <p>
32   * This builder supports all constraint types defined in the Metaschema
33   * constraint model:
34   * <ul>
35   * <li>{@link IAllowedValuesConstraint}</li>
36   * <li>{@link IMatchesConstraint}</li>
37   * <li>{@link IExpectConstraint}</li>
38   * <li>{@link IIndexHasKeyConstraint}</li>
39   * <li>{@link ICardinalityConstraint}</li>
40   * <li>{@link IIndexConstraint}</li>
41   * <li>{@link IUniqueConstraint}</li>
42   * </ul>
43   */
44  public class ContextBuilder implements IContextBuilder {
45    @NonNull
46    private final ISource source;
47    @NonNull
48    private final List<IMetapathExpression> metapaths = new ArrayList<>();
49    @NonNull
50    private final List<IConstraint> constraints = new ArrayList<>();
51    @NonNull
52    private final List<ContextBuilder> childContexts = new ArrayList<>();
53  
54    /**
55     * Construct a new context builder.
56     *
57     * @param source
58     *          the source for constraints in this context
59     */
60    public ContextBuilder(@NonNull ISource source) {
61      this.source = source;
62    }
63  
64    @Override
65    @NonNull
66    public IContextBuilder metapath(@NonNull String target) {
67      this.metapaths.add(IMetapathExpression.lazyCompile(target, source.getStaticContext()));
68      return this;
69    }
70  
71    @Override
72    @NonNull
73    public IContextBuilder constraint(
74        @NonNull AbstractConstraintBuilder<?, ? extends IConstraint> constraintBuilder) {
75      this.constraints.add(constraintBuilder.build());
76      return this;
77    }
78  
79    @Override
80    @NonNull
81    public IContextBuilder childContext(@NonNull Consumer<IContextBuilder> childConfigurer) {
82      ContextBuilder childBuilder = new ContextBuilder(source);
83      childConfigurer.accept(childBuilder);
84      this.childContexts.add(childBuilder);
85      return this;
86    }
87  
88    /**
89     * Build the context.
90     *
91     * @param parent
92     *          the parent context, or null if this is a top-level context
93     * @return the built context
94     */
95    @NonNull
96    MetaConstraintSet.Context build(@Nullable MetaConstraintSet.Context parent) {
97      // Create the constraint set for this context
98      IModelConstrained modelConstrained = new AssemblyConstraintSet(source);
99  
100     // Add constraints to the model
101     for (IConstraint constraint : constraints) {
102       addConstraint(modelConstrained, constraint);
103     }
104 
105     // Create the context with a defensive copy of metapaths
106     MetaConstraintSet.Context context = new MetaConstraintSet.Context(
107         parent,
108         source,
109         List.copyOf(metapaths),
110         modelConstrained);
111 
112     // Build and add child contexts
113     List<MetaConstraintSet.Context> builtChildren = new ArrayList<>();
114     for (ContextBuilder childBuilder : childContexts) {
115       builtChildren.add(childBuilder.build(context));
116     }
117     context.addAll(builtChildren);
118 
119     return context;
120   }
121 
122   /**
123    * Add a constraint to the model constrained object.
124    * <p>
125    * This method dispatches the constraint to the appropriate typed add method
126    * based on the constraint's runtime type.
127    * <p>
128    * <b>Note:</b> When new constraint types are added to the Metaschema constraint
129    * model, this method and the class-level Javadoc must be updated to include
130    * support for the new type.
131    *
132    * @param modelConstrained
133    *          the model constrained object to add the constraint to
134    * @param constraint
135    *          the constraint to add
136    * @throws UnsupportedOperationException
137    *           if the constraint type is not supported
138    */
139   @SuppressWarnings("PMD.CyclomaticComplexity")
140   private static void addConstraint(@NonNull IModelConstrained modelConstrained, @NonNull IConstraint constraint) {
141     if (constraint instanceof IAllowedValuesConstraint) {
142       modelConstrained.addConstraint((IAllowedValuesConstraint) constraint);
143     } else if (constraint instanceof IMatchesConstraint) {
144       modelConstrained.addConstraint((IMatchesConstraint) constraint);
145     } else if (constraint instanceof IExpectConstraint) {
146       modelConstrained.addConstraint((IExpectConstraint) constraint);
147     } else if (constraint instanceof IIndexHasKeyConstraint) {
148       modelConstrained.addConstraint((IIndexHasKeyConstraint) constraint);
149     } else if (constraint instanceof ICardinalityConstraint) {
150       modelConstrained.addConstraint((ICardinalityConstraint) constraint);
151     } else if (constraint instanceof IIndexConstraint) {
152       modelConstrained.addConstraint((IIndexConstraint) constraint);
153     } else if (constraint instanceof IUniqueConstraint) {
154       modelConstrained.addConstraint((IUniqueConstraint) constraint);
155     } else {
156       throw new UnsupportedOperationException(
157           "Unsupported constraint type: " + constraint.getClass().getName());
158     }
159   }
160 }