001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.model.constraint;
028
029import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
030import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
031import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
032import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
033import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem;
034import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
035import gov.nist.secauto.metaschema.core.util.ObjectUtils;
036import gov.nist.secauto.metaschema.core.util.ReplacementScanner;
037
038import java.util.Map;
039import java.util.Objects;
040import java.util.Set;
041import java.util.regex.Pattern;
042
043import javax.xml.namespace.QName;
044
045import edu.umd.cs.findbugs.annotations.NonNull;
046import edu.umd.cs.findbugs.annotations.Nullable;
047
048public final class DefaultExpectConstraint
049    extends AbstractConstraint
050    implements IExpectConstraint {
051  @SuppressWarnings("null")
052  @NonNull
053  private static final Pattern METAPATH_VALUE_TEMPLATE_PATTERN
054      = Pattern.compile("(?<!\\\\)(\\{\\s*((?:(?:\\\\})|[^}])*)\\s*\\})");
055  @NonNull
056  private final MetapathExpression test;
057  private final String message;
058
059  /**
060   * Construct a new expect constraint which requires that the associated test
061   * evaluates to {@link IBooleanItem#TRUE} against the target.
062   *
063   * @param id
064   *          the optional identifier for the constraint
065   * @param formalName
066   *          the constraint's formal name or {@code null} if not provided
067   * @param description
068   *          the constraint's semantic description or {@code null} if not
069   *          provided
070   * @param source
071   *          information about the constraint source
072   * @param level
073   *          the significance of a violation of this constraint
074   * @param target
075   *          the Metapath expression identifying the nodes the constraint targets
076   * @param properties
077   *          a collection of associated properties
078   * @param test
079   *          a Metapath expression that is evaluated against the target node to
080   *          determine if the constraint passes
081   * @param message
082   *          an optional message to emit when the constraint is violated
083   * @param remarks
084   *          optional remarks describing the intent of the constraint
085   */
086  private DefaultExpectConstraint(
087      @Nullable String id,
088      @Nullable String formalName,
089      @Nullable MarkupLine description,
090      @NonNull ISource source,
091      @NonNull Level level,
092      @NonNull MetapathExpression target,
093      @NonNull Map<QName, Set<String>> properties,
094      @NonNull MetapathExpression test,
095      @Nullable String message,
096      MarkupMultiline remarks) {
097    super(id, formalName, description, source, level, target, properties, remarks);
098    this.test = Objects.requireNonNull(test);
099    this.message = message;
100  }
101
102  @Override
103  public MetapathExpression getTest() {
104    return test;
105  }
106
107  @Override
108  public String getMessage() {
109    return message;
110  }
111
112  @Override
113  public CharSequence generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context) {
114    String message = getMessage();
115
116    return message == null ? null
117        : ReplacementScanner.replaceTokens(message, METAPATH_VALUE_TEMPLATE_PATTERN, match -> {
118          @SuppressWarnings("null")
119          @NonNull String metapath = match.group(2);
120          MetapathExpression expr = MetapathExpression.compile(metapath);
121          return expr.evaluateAs(item, MetapathExpression.ResultType.STRING, context);
122        });
123  }
124
125  @Override
126  public <T, R> R accept(IConstraintVisitor<T, R> visitor, T state) {
127    return visitor.visitExpectConstraint(this, state);
128  }
129
130  @NonNull
131  public static Builder builder() {
132    return new Builder();
133  }
134
135  public static final class Builder
136      extends AbstractConstraintBuilder<Builder, DefaultExpectConstraint> {
137    private MetapathExpression test;
138    private String message;
139
140    private Builder() {
141      // disable construction
142    }
143
144    public Builder test(@NonNull MetapathExpression test) {
145      this.test = test;
146      return this;
147    }
148
149    public Builder message(@NonNull String message) {
150      this.message = message;
151      return this;
152    }
153
154    @Override
155    protected Builder getThis() {
156      return this;
157    }
158
159    @Override
160    protected void validate() {
161      super.validate();
162
163      ObjectUtils.requireNonNull(getTest());
164    }
165
166    protected MetapathExpression getTest() {
167      return test;
168    }
169
170    protected String getMessage() {
171      return message;
172    }
173
174    @Override
175    protected DefaultExpectConstraint newInstance() {
176      return new DefaultExpectConstraint(
177          getId(),
178          getFormalName(),
179          getDescription(),
180          ObjectUtils.notNull(getSource()),
181          getLevel(),
182          getTarget(),
183          getProperties(),
184          ObjectUtils.requireNonNull(getTest()),
185          getMessage(),
186          getRemarks());
187    }
188  }
189}