001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    Copyright © 2008-2024 Open Geospatial Consortium, Inc.
004 *    http://www.geoapi.org
005 *
006 *    Licensed under the Apache License, Version 2.0 (the "License");
007 *    you may not use this file except in compliance with the License.
008 *    You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *    Unless required by applicable law or agreed to in writing, software
013 *    distributed under the License is distributed on an "AS IS" BASIS,
014 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *    See the License for the specific language governing permissions and
016 *    limitations under the License.
017 */
018package org.opengis.test.metadata;
019
020import java.util.Date;
021import org.opengis.metadata.citation.*;
022import org.opengis.test.ValidatorContainer;
023
024import static org.junit.jupiter.api.Assertions.fail;
025
026
027/**
028 * Validates {@link Citation} and related objects from the
029 * {@code org.opengis.metadata.citation} package.
030 *
031 * <p>This class is provided for users wanting to override the validation methods. When the default
032 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more
033 * convenient way to validate various kinds of objects.</p>
034 *
035 * @author  Martin Desruisseaux (Geomatys)
036 * @version 3.1
037 * @since   2.2
038 */
039public class CitationValidator extends MetadataValidator {
040    /**
041     * Creates a new validator instance.
042     *
043     * @param container  the set of validators to use for validating other kinds of objects
044     *                   (see {@linkplain #container field javadoc}).
045     */
046    public CitationValidator(final ValidatorContainer container) {
047        super(container, "org.opengis.metadata.citation");
048    }
049
050    /**
051     * Validates the given citation.
052     *
053     * @param  object  the object to validate, or {@code null}.
054     */
055    public void validate(final Citation object) {
056        if (object == null) {
057            return;
058        }
059        validateMandatory(object.getTitle());
060        validateOptional (object.getEdition());
061        validate("alternateTitle", object.getAlternateTitles(), ValidatorContainer::validate, false);
062        validate("identifier",     object.getIdentifiers(),     ValidatorContainer::validate, false);
063        container.validate(object.getOtherCitationDetails());
064        validate(toArray(CitationDate.class, object.getDates()));
065        for (final Responsibility e : toArray(Responsibility.class, object.getCitedResponsibleParties())) {
066            validate(e);
067        }
068    }
069
070    /**
071     * Validates citation dates. If more than one dates is given, then this method will check
072     * for the following constraints:
073     *
074     * <ul>
075     *   <li>{@link DateType#CREATION} shall be before or equals to all other type of dates, ignoring user-defined codes.</li>
076     *   <li>{@link DateType#LAST_UPDATE} shall be before or equals to {@link DateType#NEXT_UPDATE}.</li>
077     *   <li>{@link DateType#VALIDITY_BEGINS} shall be before or equals to {@link DateType#VALIDITY_EXPIRES}.</li>
078     * </ul>
079     *
080     * Those constraints are verified in their iteration order. It is possible for example to have more than one
081     * (<var>validity begins</var>, <var>validity expires</var>) pair.
082     *
083     * @param dates  the citation dates to validate.
084     *
085     * @since 3.1
086     */
087    public void validate(final CitationDate... dates) {
088        if (dates == null) {
089            return;
090        }
091        Date creation       = null;
092        Date lastUpdate     = null;
093        Date validityBegins = null;
094        final int lastOrdinal = DateType.DISTRIBUTION.ordinal();
095        for (final CitationDate date : dates) {
096            if (date != null) {
097                final DateType type = date.getDateType();
098                final Date     time = date.getDate();
099                mandatory(type, "CitationDate: shall have a date type.");
100                mandatory(time, "CitationDate: shall have a timestamp.");
101                if (type != null && time != null) {
102                    if (type.equals(DateType.CREATION)) {
103                        creation = time;
104                    } else if (type.equals(DateType.LAST_UPDATE)) {
105                        lastUpdate = time;
106                    } else if (type.equals(DateType.NEXT_UPDATE)) {
107                        assertOrdered(DateType.LAST_UPDATE, lastUpdate, DateType.NEXT_UPDATE, time);
108                    } else if (type.equals(DateType.VALIDITY_BEGINS)) {
109                        validityBegins = time;
110                    } else if (type.equals(DateType.VALIDITY_EXPIRES)) {
111                        assertOrdered(DateType.VALIDITY_BEGINS, validityBegins, DateType.VALIDITY_EXPIRES, time);
112                    }
113                    if (type.ordinal() <= lastOrdinal) {
114                        assertOrdered(DateType.CREATION, creation, type, time);
115                    }
116                }
117            }
118        }
119    }
120
121    /**
122     * Asserts that the date {@code d2} is equal or after {@code d1}.
123     *
124     * @param  t1  type of the first date.
125     * @param  d1  the first date to compare.
126     * @param  t2  type of the second date.
127     * @param  d2  the second date to compare.
128     */
129    private static void assertOrdered(final DateType t1, final Date d1, final DateType t2, final Date d2) {
130        if (d1 != null && d2.before(d1)) {
131            fail("The ‘" + t2.identifier().orElse("<?>") + "’ date (" + d2 + ") shall be equal or after "
132               + "the ‘" + t1.identifier().orElse("<?>") + "’ date (" + d1 + ").");
133        }
134    }
135
136    /**
137     * Validates the given responsible party.
138     *
139     * @param  object  the object to validate, or {@code null}.
140     *
141     * @since 3.1
142     */
143    public void validate(final Responsibility object) {
144        if (object == null) {
145            return;
146        }
147        mandatory(object.getRole(), "Responsibility: shall have a role.");
148        for (final Party e : toArray(Party.class, object.getParties())) {
149            validate(e);
150        }
151    }
152
153    /**
154     * Validates the given party.
155     *
156     * @param  object  the object to validate, or {@code null}.
157     *
158     * @since 3.1
159     */
160    public void validate(final Party object) {
161        if (object == null) {
162            return;
163        }
164        boolean isMandatory = true;
165        if (object instanceof Individual) {
166            isMandatory &= isNullOrEmpty(((Individual) object).getPositionName());
167        }
168        if (object instanceof Organisation) {
169            isMandatory &= isNullOrEmpty(((Organisation) object).getLogo());
170        }
171        if (isMandatory) {
172            mandatory(object.getName(), "Party: shall have a name.");
173        }
174        for (final Contact e : toArray(Contact.class, object.getContactInfo())) {
175            validate(e);
176        }
177    }
178
179    /**
180     * Validates the given contact information.
181     *
182     * @param  object  the object to validate, or {@code null}.
183     *
184     * @since 3.1
185     */
186    public void validate(final Contact object) {
187        if (object == null) {
188            return;
189        }
190        for (final Telephone e : toArray(Telephone.class, object.getPhones())) {
191            validate(e);
192        }
193        for (final Address e : toArray(Address.class, object.getAddresses())) {
194            validate(e);
195        }
196        for (final OnlineResource e : toArray(OnlineResource.class, object.getOnlineResources())) {
197            validate(e);
198        }
199        validateOptional(object.getHoursOfService());
200        validateOptional(object.getContactInstructions());
201    }
202
203    /**
204     * Validates the given telephone information.
205     *
206     * @param  object  the object to validate, or {@code null}.
207     *
208     * @since 3.1
209     */
210    public void validate(final Telephone object) {
211        if (object == null) {
212            return;
213        }
214        mandatory(object.getNumber(), "Telephone: shall have a number.");
215    }
216
217    /**
218     * Validates the given address.
219     *
220     * @param  object  the object to validate, or {@code null}.
221     *
222     * @since 3.1
223     */
224    public void validate(final Address object) {
225        if (object == null) {
226            return;
227        }
228        validate(object.getDeliveryPoints());
229        validateOptional(object.getCity());
230        validateOptional(object.getAdministrativeArea());
231        validateOptional(object.getCountry());
232        validate(object.getElectronicMailAddresses());
233    }
234
235    /**
236     * Validates the given online resource.
237     *
238     * @param  object  the object to validate, or {@code null}.
239     *
240     * @since 3.1
241     */
242    public void validate(final OnlineResource object) {
243        if (object == null) {
244            return;
245        }
246        mandatory(object.getLinkage(), "OnlineResource: shall have a linkage.");
247        validateOptional(object.getDescription());
248    }
249}