Coverage Summary for Class: Ruleset (org.kitodo.dataeditor.ruleset.xml)

Class Class, % Method, % Line, %
Ruleset 100% (1/1) 100% (22/22) 93,2% (96/103)


 /*
  * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
  *
  * This file is part of the Kitodo project.
  *
  * It is licensed under GNU General Public License version 3 or later.
  *
  * For the full copyright and license information, please read the
  * GPL3-License.txt file that was distributed with this source code.
  */
 
 package org.kitodo.dataeditor.ruleset.xml;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale.LanguageRange;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.TreeSet;
 import java.util.function.Function;
 
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 
 import org.kitodo.api.dataeditor.rulesetmanagement.Domain;
 import org.kitodo.dataeditor.ruleset.DivisionDeclaration;
 import org.kitodo.dataeditor.ruleset.Labeled;
 import org.kitodo.dataeditor.ruleset.Rule;
 import org.kitodo.dataeditor.ruleset.Settings;
 
 /**
  * This class maps the XML root element of the rule set. The ruleset is the
  * heart of Production. It consists of three sections: declaration, correlation
  * and editing.
  */
 @XmlRootElement(name = "ruleset", namespace = "http://names.kitodo.org/ruleset/v2")
 public class Ruleset {
 
     /**
      * The default language of the labels without language specification in the
      * rule set. Defaults to English.
      */
     @XmlAttribute
     private String lang;
 
     @XmlElement(name = "include", namespace = "http://names.kitodo.org/ruleset/v2")
     private List<String> includes = new ArrayList<>();
 
     /**
      * The declaration section defines divisions and keys.
      */
     @XmlElement(name = "declaration", namespace = "http://names.kitodo.org/ruleset/v2")
     private DeclarationElement declaration = new DeclarationElement();
 
     /**
      * The correlation section defines relationships between divisions and keys.
      */
     @XmlElementWrapper(name = "correlation", namespace = "http://names.kitodo.org/ruleset/v2")
     @XmlElement(name = "restriction", namespace = "http://names.kitodo.org/ruleset/v2")
     private List<RestrictivePermit> restrictions = new LinkedList<>();
 
     /**
      * In the editing section settings for the editor concerning keys are
      * defined.
      */
     @XmlElement(name = "editing", namespace = "http://names.kitodo.org/ruleset/v2")
     private EditingElement editing;
 
     private transient List<Key> keys;
 
     /**
      * Inserts all information from another ruleset into this ruleset. Information
      * of the same name will be overwritten.
      *
      * @param other ruleset to insert
      */
     public void addAll(Ruleset other) {
         if (Objects.nonNull(other.declaration)) {
             if (Objects.isNull(declaration)) {
                 declaration = other.declaration;
             } else {
                 replaceOrAdd(other.declaration.getDivisions(), Division::getId, declaration.getDivisions());
                 replaceOrAdd(other.declaration.getKeys(), Key::getId, declaration.getKeys());
             }
         }
         if (Objects.nonNull(other.restrictions)) {
             if (Objects.isNull(restrictions)) {
                 restrictions = other.restrictions;
             } else {
                 replaceOrAdd(other.restrictions, RestrictivePermit::getKey, restrictions);
                 replaceOrAdd(other.restrictions, RestrictivePermit::getDivision, restrictions);
             }
         }
         if (Objects.nonNull(other.editing)) {
             if (Objects.isNull(editing)) {
                 editing = other.editing;
             } else {
                 replaceOrAdd(other.editing.getSettings(), Setting::getKey, editing.getSettings());
                 replaceOrAdd(other.editing.getAcquisitionStages(), AcquisitionStage::getName, editing.getAcquisitionStages());
             }
         }
     }
 
     private static <I, T> void replaceOrAdd(List<T> data, Function<T, I> getId, List<T> collector) {
         for (T entry : data) {
             I id = getId.apply(entry);
             if (Objects.equals(id, Optional.empty())) {
                 continue;
             }
             boolean add = true;
             ListIterator<T> collectorIterator = collector.listIterator();
             while (collectorIterator.hasNext()) {
                 if (Objects.equals(id, getId.apply(collectorIterator.next()))) {
                     collectorIterator.set(entry);
                     add = false;
                     break;
                 }
             }
             if (add) {
                 collector.add(entry);
             }
         }
     }
 
     /**
      * Returns an acquisition stage by name.
      *
      * @param name
      *            name of the acquisition stage to be searched
      * @return the acquisition stage, if there is one
      */
     public Optional<AcquisitionStage> getAcquisitionStage(String name) {
         if (editing == null) {
             return Optional.empty();
         }
         return editing.getAcquisitionStage(name);
     }
 
     /**
      * Returns all acquisition stages defined in the rule set.
      *
      * @return the list of the acquisition stages
      */
     public List<AcquisitionStage> getAcquisitionStages() {
         if (editing == null) {
             return Collections.emptyList();
         }
         return editing.getAcquisitionStages();
     }
 
     /**
      * Returns the default language of labels without a language in the ruleset.
      * Defaults to English.
      *
      * @return the default language of labels without a language
      */
     public String getDefaultLang() {
         return lang == null ? "en" : lang;
     }
 
     /**
      * Returns a division based on its identification.
      *
      * @param id
      *            Identification of the division to be searched
      * @return the division, if there is one
      */
     public Optional<Division> getDivision(String id) {
         Optional<Division> optionalDivision = declaration.getDivisions().parallelStream()
                 .filter(division -> division.getId().equals(id)).findFirst();
         if (optionalDivision.isPresent()) {
             return optionalDivision;
         } else {
             return this.getDivisions().parallelStream().flatMap(division -> division.getDivisions().parallelStream())
                     .filter(division -> division.getId().equals(id)).findFirst();
         }
     }
 
     /**
      * Returns a restriction rule for a division, if any.
      *
      * @param division
      *            Division to search a restriction rule for
      * @return the restriction rule if there is one
      */
     public Optional<RestrictivePermit> getDivisionRestriction(String division) {
         return restrictions.parallelStream()
                 .filter(restriction -> division.equals(restriction.getDivision().orElse(null)))
                 .findFirst();
     }
 
     /**
      * Returns the total list of all divisions returned by the ruleset.
      *
      * @return all divisions of the rule set
      */
     public List<Division> getDivisions() {
         return declaration.getDivisions();
     }
 
     /**
      * Returns the divisions defined in this rule set.
      *
      * @param priorityList
      *            weighted list of user-preferred display languages. Return
      *            value of the function {@link LanguageRange#parse(String)}.
      * @param all
      *            whether to return all divisions, or only those capable to form
      *            a process title
      * @param subdivisionsByDate
      *            whether subdivisions by date should be returned
      * @return all outline elements as map from IDs to labels
      */
     public Map<String, String> getDivisions(List<LanguageRange> priorityList, boolean all, boolean subdivisionsByDate) {
         Collection<DivisionDeclaration> divisionDeclarations = getDivisionDeclarations(all, subdivisionsByDate);
         return all || !divisionDeclarations.isEmpty() ? Labeled.listByTranslatedLabel(this,
             divisionDeclarations, DivisionDeclaration::getId, DivisionDeclaration::getLabels, priorityList)
                 : getDivisions(priorityList, true, subdivisionsByDate);
     }
 
     /**
      * Returns all division declarations as a collection.
      *
      * @param all
      *            if true, returns all division declarations; if false, only
      *            returns division declarations with {@code processTitle}
      *            attribute set, which indicates that they are candidates for
      *            the logical root type, which typically corresponds to the
      *            media format
      * @param subdivisionsByDate
      *            if subdivisions by date should be included
      * @return a collection of division declarations
      */
     public Collection<DivisionDeclaration> getDivisionDeclarations(boolean all, boolean subdivisionsByDate) {
         Collection<DivisionDeclaration> divisionDeclarations = new LinkedList<>();
         for (Division division : declaration.getDivisions()) {
             DivisionDeclaration divisionDeclaration = new DivisionDeclaration(this, division);
             if (all || divisionDeclaration.getProcessTitle().isPresent()) {
                 divisionDeclarations.add(divisionDeclaration);
             }
             if (subdivisionsByDate) {
                 for (DivisionDeclaration subdivision : divisionDeclaration.getAllowedDivisionDeclarations()) {
                     if (all || subdivision.getProcessTitle().isPresent()) {
                         divisionDeclarations.add(subdivision);
                     }
                 }
             }
         }
         return divisionDeclarations;
     }
 
     /**
      * Returns the filenames of the included rulesets.
      *
      * @return filenames of the included rulesets
      */
     public List<String> getIncludes() {
         return includes;
     }
 
     /**
      * Returns a key from the ruleset, if defined.
      *
      * @param keyId
      *            Identifier of the key
      * @return a key, if any
      */
     public Optional<Key> getKey(String keyId) {
         return declaration.getKeys().parallelStream().filter(key -> keyId.equals(key.getId())).findAny();
     }
 
     /**
      * Returns the restriction on a key, if there is one.
      *
      * @param keyId
      *            key for which the restriction is to be given
      * @return the restriction on a key, if any
      */
     public Optional<RestrictivePermit> getKeyRestriction(String keyId) {
         return restrictions.parallelStream().filter(restriction -> keyId.equals(restriction.getKey().orElse(null)))
                 .findAny();
     }
 
     /**
      * Returns the complete list of all keys in this ruleset.
      *
      * @return all keys in this ruleset
      */
     public List<Key> getKeys() {
         if (Objects.isNull(keys)) {
             keys = defineMetsDivKeys(declaration.getKeys());
         }
         return keys;
     }
 
     /**
      * Hard define keys for METS attributes CONTENTIDS, ORDER, and ORDERLABEL.
      *
      * @param keys
      *            keys defined in the ruleset
      * @return completed definition
      */
     private static List<Key> defineMetsDivKeys(List<Key> keys) {
         defineKey(keys, "CONTENTIDS", Type.ANY_URI, new Label("METS content ID"), new Label("de", "METS-Inhalts-ID"));
         defineKey(keys, "LABEL", Type.STRING, new Label("METS label"), new Label("de", "METS-Beschriftung"));
         defineKey(keys, "ORDERLABEL", Type.STRING, new Label("METS order label"),
             new Label("de", "METS-Anordnungsbeschriftung"));
         return keys;
     }
 
     /**
      * Hard defines a key which represents a METS attribute
      * ({@code domain="mets:div"}).
      *
      * @param keys
      *            key definition list, may already contain the key. If so, the
      *            key is updated (if necessary), else it is added.
      * @param id
      *            ID of the key
      * @param type
      *            type of the key
      * @param labels
      *            labels for the key if it is not defined
      */
     private static void defineKey(List<Key> keys, String id, Type type, Label... labels) {
         Optional<Key> definition = keys.parallelStream().filter(key -> key.getId().equalsIgnoreCase(id)).findAny();
         Key key;
         if (definition.isPresent()) {
             key = definition.get();
         } else {
             key = new Key();
             Collections.addAll(key.getLabels(), labels);
             keys.add(key);
         }
         key.setId(id);
         /*
          * If the key type is string (broadest type), but the required type is
          * narrower, set it. Do not set it if the user explicitly defined a
          * narrower type in the ruleset file.
          */
         Type currentType = key.getType();
         if (Objects.equals(currentType, Type.STRING) && !Objects.equals(currentType, type)) {
             key.setType(type);
         }
         key.setDomain(Domain.METS_DIV);
     }
 
     /**
      * Returns the total list of all settings returned by the ruleset.
      *
      * @return all settings of the rule set
      */
     public List<Setting> getSettings() {
         if (editing == null) {
             return Collections.emptyList();
         }
         return editing.getSettings();
     }
 
     /**
      * Returns the settings for an acquisition stage. That can be empty but it
      * works.
      *
      * @param acquisitionStage
      *            acquisition stage for which the settings is to be returned
      * @return settings for acquisition stage
      */
     public Settings getSettings(String acquisitionStage) {
         Settings settings = new Settings(this.getSettings());
         if (Objects.isNull(acquisitionStage)) {
             return settings;
         }
         Optional<AcquisitionStage> optionalAcquisitionStage = this.getAcquisitionStage(acquisitionStage);
         optionalAcquisitionStage.ifPresent(stage -> settings.merge(stage.getSettings()));
         return settings;
     }
 
     /**
      * Returns a fictitious metadata key for the rule set that contains all the
      * keys of the rule set.
      *
      * @return a fictitious metadata key for the rule set
      */
     public Key getFictiousRulesetKey() {
         Key fictiousRulesetKey = new Key();
         fictiousRulesetKey.setKeys(declaration.getKeys());
         return fictiousRulesetKey;
     }
 
     /**
      * Returns a rule for a key. The rule may be empty.
      *
      * @param keyId
      *            key for which a rule is to be returned
      * @return rule for the key
      */
     public Rule getRuleForKey(String keyId) {
         return new Rule(this, this.getKeyRestriction(keyId));
     }
 
     /**
      * Returns a rule for a division. The rule may be empty.
      *
      * @param division
      *            division for which a rule is to be returned
      * @return rule for division
      */
     public Rule getRuleForDivision(String division) {
         if (Objects.isNull(division)) {
             return new Rule(this, this.getDivisionRestriction(""));
         } else {
             return new Rule(this, this.getDivisionRestriction(division));
         }
     }
 }