001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.oscal.lib.model.control.catalog;
007
008import java.util.LinkedList;
009import java.util.List;
010import java.util.Objects;
011import java.util.stream.Stream;
012
013import dev.metaschema.core.datatype.markup.MarkupLine;
014import dev.metaschema.core.util.CollectionUtil;
015import dev.metaschema.core.util.ObjectUtils;
016import dev.metaschema.databind.io.IDeserializationHandler;
017import dev.metaschema.oscal.lib.model.Control;
018import dev.metaschema.oscal.lib.model.ControlPart;
019import dev.metaschema.oscal.lib.model.Link;
020import dev.metaschema.oscal.lib.model.Parameter;
021import dev.metaschema.oscal.lib.model.Property;
022import dev.metaschema.oscal.lib.model.control.AbstractParameter;
023import edu.umd.cs.findbugs.annotations.NonNull;
024
025public abstract class AbstractControl
026    implements IDeserializationHandler, IControl {
027  private Control parent;
028
029  @Override
030  public Control getParentControl() {
031    return parent;
032  }
033
034  @Override
035  public void setParentControl(Control parent) {
036    this.parent = parent;
037  }
038
039  @Override
040  public void beforeDeserialize(Object parent) { // NOPMD noop default
041    // do nothing
042  }
043
044  @Override
045  public void afterDeserialize(Object parent) {
046    if (parent instanceof Control) {
047      setParentControl((Control) parent);
048    }
049  }
050
051  @NonNull
052  @Override
053  public Stream<String> getReferencedParameterIds() {
054
055    // get parameters referenced by the group's parts
056    Stream<String> insertIds = CollectionUtil.listOrEmpty(getParts()).stream()
057        // Get the full part hierarchy
058        .flatMap(part -> Stream.concat(Stream.of(part), part.getPartsRecursively()))
059        // Get the inserts for each part
060        .flatMap(part -> part.getInserts(insert -> "param".equals(insert.getType().toString())))
061        // Get the param ids for each insert
062        .map(insert -> insert.getIdReference().toString())
063        .flatMap(ObjectUtils::filterNull);
064
065    // get parameters referenced by the control's parameters
066    Stream<String> parameterIds = CollectionUtil.listOrEmpty(getParams()).stream()
067        .flatMap(ObjectUtils::filterNull)
068        .flatMap(AbstractParameter::getParameterReferences);
069
070    return ObjectUtils.notNull(
071        Stream.concat(insertIds, parameterIds).distinct());
072  }
073
074  @NonNull
075  public static Builder builder(@NonNull String id) {
076    return new Builder(id);
077  }
078
079  public static class Builder {
080    @NonNull
081    private final String id;
082
083    private String clazz;
084    private MarkupLine title;
085    private final List<Parameter> params = new LinkedList<>();
086    private final List<Property> props = new LinkedList<>();
087    private final List<Link> links = new LinkedList<>();
088    private final List<ControlPart> parts = new LinkedList<>();
089    private final List<Control> controls = new LinkedList<>();
090
091    public Builder(@NonNull String id) {
092      this.id = Objects.requireNonNull(id, "id");
093    }
094
095    @NonNull
096    public Builder clazz(@NonNull String value) {
097      this.clazz = Objects.requireNonNull(value);
098      return this;
099    }
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}