Coverage Summary for Class: IndividualIssue (org.kitodo.production.model.bibliography.course)

Class Method, % Line, %
IndividualIssue 52,9% (9/17) 45,5% (46/101)
IndividualIssue$1 100% (1/1) 100% (1/1)
Total 55,6% (10/18) 46,1% (47/102)


 /*
  * (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.production.model.bibliography.course;
 
 import java.time.LocalDate;
 import java.time.MonthDay;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.WeekFields;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.kitodo.api.Metadata;
 import org.kitodo.api.MetadataEntry;
 import org.kitodo.exceptions.InvalidMetadataValueException;
 import org.kitodo.exceptions.UnreachableCodeException;
 import org.kitodo.production.model.bibliography.course.metadata.CountableMetadata;
 
 /**
  * Represents a stamping of an Issue, that is one distinguishable physically
  * appeared issue. In opposition, the class {@link Issue} represents the
  * <em>type</em> of issue.
  */
 public class IndividualIssue {
     private static final Logger logger = LogManager.getLogger(IndividualIssue.class);
 
     /**
      * The constant DAY holds a DateTimeFormatter used to get the a two-digit
      * day (01—31) from the newspaper’s date.
      */
     private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("dd");
 
     /**
      * The constant MONTH holds a DateTimeFormatter used to get the a two-digit
      * month (01—12) from the newspaper’s date.
      */
     private static final DateTimeFormatter MONTH = DateTimeFormatter.ofPattern("MM");
 
     /**
      * The constant YEAR2 holds a DateTimeFormatter used to get the a four-digit
      * year of era (00—99, always positive) from the newspaper’s date.
      */
     private static final DateTimeFormatter YEAR2 = DateTimeFormatter.ofPattern("YY");
 
     /**
      * The constant YEAR4 holds a DateTimeFormatter used to get the a four-digit
      * year of era (0001—9999, always positive) from the newspaper’s date.
      */
     private static final DateTimeFormatter YEAR4 = DateTimeFormatter.ofPattern("YYYY");
 
     /**
      * Metadata key to store the sorting number.
      */
     public static final String RULESET_ORDER_NAME = "CurrentNoSorting";
 
     /**
      * Date of this issue.
      */
     protected final LocalDate date;
 
     /**
      * The issue this is an issue from.
      */
     protected final Issue issue;
 
     /**
      * The sorting number of the issue.
      */
     private Integer sortingNumber;
 
     /**
      * Block that the issue this is an issue from is in.
      */
     protected final Block block;
 
     /**
      * Constructor to create an IndividualIssue.
      *
      * @param block
      *            Block block this issue is in
      * @param issue
      *            Issue type that this issue is of
      * @param date
      *            Date of appearance
      * @param sortingNumber
      *            sorting number
      */
     IndividualIssue(Block block, Issue issue, LocalDate date, Integer sortingNumber) {
         this.block = block;
         this.issue = issue;
         this.date = date;
         this.sortingNumber = sortingNumber;
     }
 
     /**
      * Returns an integer which, for a given Granularity, shall indicate for two
      * neighboring individual issues whether they form the same process (break
      * mark is equal) or two different processes (break mark differs).
      *
      * @param mode
      *            how the course shall be broken into processes
      * @param yearStart
      *            the first day of the business year
      * @return an int which differs if two neighboring individual issues belong
      *         to different processes
      */
     public int getBreakMark(Granularity mode, MonthDay yearStart) {
         final int prime = 31;
         switch (mode) {
             case ISSUES:
                 return this.hashCode();
             case DAYS:
                 return date.hashCode();
             case WEEKS:
                 return prime * getFirstYear(yearStart) + date.get(WeekFields.ISO.weekOfWeekBasedYear()); // TODO Weekfields.ISO correct?
             case MONTHS:
                 return prime * getFirstYear(yearStart) + date.getMonthValue();
             case QUARTERS:
                 return prime * getFirstYear(yearStart) + (date.getMonthValue() - 1) / 3;
             case YEARS:
                 return getFirstYear(yearStart);
             default:
                 throw new UnreachableCodeException("default case in complete switch statement");
         }
     }
 
     /**
      * Returns the first calendar year of the year range this issue is on.
      *
      * @param yearStart
      *            the day the new year starts
      * @return the first calendar year of the year range this issue is on
      */
     private int getFirstYear(MonthDay yearStart) {
         int year = date.getYear();
         return date.compareTo(yearStart.atYear(year)) < 0 ? year - 1 : year;
     }
 
     /**
      * Returns the date of this issue.
      *
      * @return the date of this issue
      */
     public LocalDate getDate() {
         return date;
     }
 
     /**
      * Returns a map with generic fields that can be configured for process
      * title creation in kitodo_projects.xml. It provides the issue information
      * in the following fields:
      *
      * <dl>
      * <dt>{@code #DAY}</dt>
      * <dd>two-digit day of month</dd>
      * <dt>{@code #Issue}</dt>
      * <dd>issue name</dd>
      * <dt>{@code #MONTH}</dt>
      * <dd>two-digit month of year</dd>
      * <dt>{@code #YEAR}</dt>
      * <dd>four-digit year</dd>
      * </dl>
      *
      * <p>
      * In addition, the following abbreviated fields are provided:
      *
      * <dl>
      * <dt>{@code #i}</dt>
      * <dd>first letter of issue name in lower case</dd>
      * <dt>{@code #I}</dt>
      * <dd>first letter of issue name in upper case</dd>
      * <dt>{@code #is}</dt>
      * <dd>first two letters of issue name in lower case</dd>
      * <dt>{@code #IS}</dt>
      * <dd>first two letters of issue name in upper case</dd>
      * <dt>{@code #iss}</dt>
      * <dd>first three letters of issue name in lower case</dd>
      * <dt>{@code #ISS}</dt>
      * <dd>first three letters of issue name in upper case</dd>
      * <dt>{@code #issu}</dt>
      * <dd>first four letters of issue name in lower case</dd>
      * <dt>{@code #ISSU}</dt>
      * <dd>first four letters of issue name in upper case</dd>
      * <dt>{@code #YR}</dt>
      * <dd>two-digit year of century</dd>
      * </dl>
      *
      * @return the generic fields for process title creation
      */
     public Map<String, String> getGenericFields() {
         Map<String, String> genericFields = new HashMap<>(18);
         String heading = issue.getHeading();
         int length = heading.length();
         String upperCase = heading.toUpperCase();
         String lowerCase = heading.toLowerCase();
         genericFields.put("#DAY", DAY.format(date));
         genericFields.put("#I", length > 1 ? upperCase.substring(0, 1) : upperCase);
         genericFields.put("#i", length > 1 ? lowerCase.substring(0, 1) : lowerCase);
         genericFields.put("#IS", length > 2 ? upperCase.substring(0, 2) : upperCase);
         genericFields.put("#is", length > 2 ? lowerCase.substring(0, 2) : lowerCase);
         genericFields.put("#ISS", length > 3 ? upperCase.substring(0, 3) : upperCase);
         genericFields.put("#iss", length > 3 ? lowerCase.substring(0, 3) : lowerCase);
         genericFields.put("#ISSU", length > 4 ? upperCase.substring(0, 4) : upperCase);
         genericFields.put("#issu", length > 4 ? lowerCase.substring(0, 4) : lowerCase);
         genericFields.put("#Issue", heading);
         genericFields.put("#MONTH", MONTH.format(date));
         genericFields.put("#YEAR", YEAR4.format(date));
         genericFields.put("#YR", YEAR2.format(date));
         return genericFields;
     }
 
     /**
      * Returns the metadata for this individual issue.
      *
      * @param monthOfYear
      *            the month of the year start—relevant to correctly calculate
      *            the counter value
      * @param dayOfMonth
      *            the day of the year start—relevant to correctly calculate the
      *            counter value
      * @return a list of pairs, each consisting of the metadata type name and
      *         the value
      */
     public Iterable<Metadata> getMetadata(int monthOfYear, int dayOfMonth) {
         return getMetadata(MonthDay.of(monthOfYear, dayOfMonth));
     }
 
     /**
      * Returns the metadata for this individual issue.
      *
      * @param yearStart
      *            the day of the year start—relevant to correctly calculate the
      *            counter value
      * @return a list of pairs, each consisting of the metadata type name and
      *         the value
      */
     public Iterable<Metadata> getMetadata(MonthDay yearStart) {
         List<Metadata> result = new ArrayList<>();
         Pair<LocalDate, Issue> selectedIssue = Pair.of(date, issue);
         for (CountableMetadata counter : block.getMetadata(selectedIssue, null)) {
             Collection<Metadata> metadata = Collections.emptyList();
             try {
                 metadata = counter.getMetadataDetail().getMetadataWithFilledValues();
             } catch (InvalidMetadataValueException e) {
                 logger.error(e.getLocalizedMessage());
             }
             if (counter.getMetadataDetail().getInput().equals("inputText")
                     || counter.getMetadataDetail().getInput().equals("inputTextarea")) {
                 String value = counter.getValue(selectedIssue, yearStart);
                 if (metadata.stream().findFirst().isPresent()
                         && metadata.stream().findFirst().get() instanceof MetadataEntry) {
                     ((MetadataEntry) metadata.stream().findFirst().get()).setValue(value);
                 }
             }
             result.addAll(metadata);
         }
         return result;
     }
 
     /**
      * Returns the name of the issue this is an issue from.
      *
      * @return the issue’s name
      */
     public String getHeading() {
         return issue.getHeading();
     }
 
     /**
      * The function getIssue() returns the issue this is an issue from.
      *
      * @return the issue
      */
     public Issue getIssue() {
         return issue;
     }
 
     /**
      * Returns the list of issues before this issue.
      *
      * @return the list of issues before this
      */
     public List<String> getIssuesBefore() {
         List<String> result = new ArrayList<>();
         for (Issue issue : block.getIssues()) {
             String heading = issue.getHeading();
             if (heading.equals(this.issue.getHeading())) {
                 break;
             }
             result.add(heading);
         }
         return result;
     }
 
     /**
      * Returns the sorting number of the issue.
      *
      * @return the sorting number
      */
     public Integer getSortingNumber() {
         return sortingNumber;
     }
 
     /**
      * Returns the index of the first occurrence of the block of this issue in
      * the given course, or -1 if the course does not contain the element.
      *
      * @param course
      *            course to find the block in
      * @return the index of the first occurrence of the block of this issue in
      *         the course, or -1 if the course does not contain the element
      */
     int indexIn(Course course) {
         return course.indexOf(block);
     }
 
     /**
      * Sets the sorting number of the issue.
      *
      * @param sortingNumber
      *            the sorting number to set
      */
     public void setSortingNumber(Integer sortingNumber) {
         this.sortingNumber = sortingNumber;
     }
 
     /**
      * Provides returns a string that contains a concise but informative
      * representation of this issue that is easy for a person to read.
      *
      * @return a string representation of the issue
      * @see java.lang.Object#toString()
      */
     @Override
     public String toString() {
         try {
             if (issue.getHeading().length() == 0) {
                 return date.toString();
             } else {
                 return date.toString() + ", " + issue.getHeading();
             }
         } catch (RuntimeException e) {
             return super.toString();
         }
     }
 
     /**
      * Returns a hash code for the object which depends on the content of its
      * variables. Whenever IndividualIssue objects are held in HashSet objects,
      * a hashCode() is essentially necessary.
      *
      * <p>
      * The method was generated by Eclipse using right-click → Source → Generate
      * hashCode() and equals()…. If you will ever change the classes’ fields,
      * just re-generate it.
      *
      * @see java.lang.Object#hashCode()
      */
     @Override
     public int hashCode() {
         final int prime = 31;
         int hashCode = 1;
         hashCode = prime * hashCode + ((date == null) ? 0 : date.hashCode());
         hashCode = prime * hashCode + ((issue == null) ? 0 : issue.hashCode());
         hashCode = prime * hashCode + ((block == null) ? 0 : block.hashCode());
         return hashCode;
     }
 
     /**
      * Returns whether two individual issues are equal; the decision depends on
      * the content of its variables.
      *
      * <p>
      * The method was generated by Eclipse using right-click → Source → Generate
      * hashCode() and equals()…. If you will ever change the classes’ fields,
      * just re-generate it.
      *
      * @see java.lang.Object#equals(java.lang.Object)
      */
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
             return true;
         }
 
         if (obj instanceof IndividualIssue) {
             IndividualIssue other = (IndividualIssue) obj;
             if (Objects.isNull(date)) {
                 if (Objects.nonNull(other.date)) {
                     return false;
                 }
             } else if (!date.equals(other.date)) {
                 return false;
             }
             if (Objects.isNull(issue)) {
                 if (Objects.nonNull(other.issue)) {
                     return false;
                 }
             } else if (!issue.equals(other.issue)) {
                 return false;
             }
             if (Objects.isNull(block)) {
                 return Objects.isNull(other.block);
             } else {
                 return block.equals(other.block);
             }
         }
         return false;
     }
 }