1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.oscal.lib.model.control.catalog;
7   
8   import java.util.LinkedList;
9   import java.util.List;
10  import java.util.Objects;
11  import java.util.stream.Stream;
12  
13  import dev.metaschema.core.datatype.markup.MarkupLine;
14  import dev.metaschema.core.util.CollectionUtil;
15  import dev.metaschema.core.util.ObjectUtils;
16  import dev.metaschema.databind.io.IDeserializationHandler;
17  import dev.metaschema.oscal.lib.model.Control;
18  import dev.metaschema.oscal.lib.model.ControlPart;
19  import dev.metaschema.oscal.lib.model.Link;
20  import dev.metaschema.oscal.lib.model.Parameter;
21  import dev.metaschema.oscal.lib.model.Property;
22  import dev.metaschema.oscal.lib.model.control.AbstractParameter;
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  
25  public abstract class AbstractControl
26      implements IDeserializationHandler, IControl {
27    private Control parent;
28  
29    @Override
30    public Control getParentControl() {
31      return parent;
32    }
33  
34    @Override
35    public void setParentControl(Control parent) {
36      this.parent = parent;
37    }
38  
39    @Override
40    public void beforeDeserialize(Object parent) { // NOPMD noop default
41      // do nothing
42    }
43  
44    @Override
45    public void afterDeserialize(Object parent) {
46      if (parent instanceof Control) {
47        setParentControl((Control) parent);
48      }
49    }
50  
51    @NonNull
52    @Override
53    public Stream<String> getReferencedParameterIds() {
54  
55      // get parameters referenced by the group's parts
56      Stream<String> insertIds = CollectionUtil.listOrEmpty(getParts()).stream()
57          // Get the full part hierarchy
58          .flatMap(part -> Stream.concat(Stream.of(part), part.getPartsRecursively()))
59          // Get the inserts for each part
60          .flatMap(part -> part.getInserts(insert -> "param".equals(insert.getType().toString())))
61          // Get the param ids for each insert
62          .map(insert -> insert.getIdReference().toString())
63          .flatMap(ObjectUtils::filterNull);
64  
65      // get parameters referenced by the control's parameters
66      Stream<String> parameterIds = CollectionUtil.listOrEmpty(getParams()).stream()
67          .flatMap(ObjectUtils::filterNull)
68          .flatMap(AbstractParameter::getParameterReferences);
69  
70      return ObjectUtils.notNull(
71          Stream.concat(insertIds, parameterIds).distinct());
72    }
73  
74    @NonNull
75    public static Builder builder(@NonNull String id) {
76      return new Builder(id);
77    }
78  
79    public static class Builder {
80      @NonNull
81      private final String id;
82  
83      private String clazz;
84      private MarkupLine title;
85      private final List<Parameter> params = new LinkedList<>();
86      private final List<Property> props = new LinkedList<>();
87      private final List<Link> links = new LinkedList<>();
88      private final List<ControlPart> parts = new LinkedList<>();
89      private final List<Control> controls = new LinkedList<>();
90  
91      public Builder(@NonNull String id) {
92        this.id = Objects.requireNonNull(id, "id");
93      }
94  
95      @NonNull
96      public Builder clazz(@NonNull String value) {
97        this.clazz = Objects.requireNonNull(value);
98        return this;
99      }
100 
101     @NonNull
102     public Builder title(@NonNull String markdown) {
103       this.title = MarkupLine.fromMarkdown(Objects.requireNonNull(markdown));
104       return this;
105     }
106 
107     @NonNull
108     public Builder title(@NonNull MarkupLine value) {
109       this.title = Objects.requireNonNull(value);
110       return this;
111     }
112 
113     @NonNull
114     public Builder param(@NonNull Parameter value) {
115       this.params.add(Objects.requireNonNull(value));
116       return this;
117     }
118 
119     @NonNull
120     public Builder prop(@NonNull Property value) {
121       this.props.add(Objects.requireNonNull(value));
122       return this;
123     }
124 
125     @NonNull
126     public Builder link(@NonNull Link value) {
127       this.links.add(Objects.requireNonNull(value));
128       return this;
129     }
130 
131     @NonNull
132     public Builder part(@NonNull ControlPart value) {
133       this.parts.add(Objects.requireNonNull(value));
134       return this;
135     }
136 
137     @NonNull
138     public Builder control(@NonNull Control value) {
139       this.controls.add(Objects.requireNonNull(value));
140       return this;
141     }
142 
143     @NonNull
144     public Control build() {
145       Control retval = new Control();
146       retval.setId(id);
147 
148       if (title == null) {
149         throw new IllegalStateException("a title must be provided");
150       }
151       retval.setTitle(title);
152 
153       if (clazz != null) {
154         retval.setClazz(clazz);
155       }
156       if (!params.isEmpty()) {
157         retval.setParams(params);
158       }
159       if (!props.isEmpty()) {
160         retval.setProps(props);
161       }
162       if (!links.isEmpty()) {
163         retval.setLinks(links);
164       }
165       if (!parts.isEmpty()) {
166         retval.setParts(parts);
167       }
168       if (!controls.isEmpty()) {
169         controls.forEach(control -> control.setParentControl(retval));
170         retval.setControls(controls);
171       }
172 
173       return retval;
174     }
175   }
176 }