All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.diver.repo.toc.RepoToc Maven / Gradle / Ivy

/*
 * Copyright (C) 2023-2024 Philip Helger & ecosio
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.diver.repo.toc;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ListIterator;
import java.util.Map;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import javax.annotation.concurrent.NotThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.annotation.VisibleForTesting;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.CommonsTreeMap;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsSortedMap;
import com.helger.commons.collection.impl.ICommonsSortedSet;
import com.helger.commons.datetime.PDTFactory;
import com.helger.commons.datetime.XMLOffsetDateTime;
import com.helger.commons.hashcode.HashCodeGenerator;
import com.helger.commons.state.EChange;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.ToStringGenerator;
import com.helger.diver.api.version.VESVersion;
import com.helger.diver.repo.toc.jaxb.v10.RTVersionListType;
import com.helger.diver.repo.toc.jaxb.v10.RTVersionType;
import com.helger.diver.repo.toc.jaxb.v10.RTVersioningType;
import com.helger.diver.repo.toc.jaxb.v10.RepoTocType;

/**
 * Local representation of a Repository Table of Contents (ToC) for a single
 * artifact. The key is the combination of Group ID and Artefact ID. A table of
 * contents can only contain static versions. Pseudo versions can never be in a
 * ToC.
 *
 * @author Philip Helger
 * @since 1.0.1
 */
@NotThreadSafe
public class RepoToc
{
  private static final Logger LOGGER = LoggerFactory.getLogger (RepoToc.class);

  private final String m_sGroupID;
  private final String m_sArtifactID;

  // Oldest version first; Latest version last
  // VESVersion uses a different "compare" then Version!
  private final ICommonsSortedMap  m_aVersions = new CommonsTreeMap <> ();

  // Status variables
  private VESVersion m_aLatestReleaseVersion;
  private OffsetDateTime m_aLatestReleaseVersionPubDT;

  /**
   * Constructor
   *
   * @param sGroupID
   *        VESID Group ID of the ToC. May neither be null nor
   *        empty.
   * @param sArtifactID
   *        VESID Artifact ID of the ToC. May neither be null nor
   *        empty.
   */
  public RepoToc (@Nonnull @Nonempty final String sGroupID, @Nonnull @Nonempty final String sArtifactID)
  {
    this (sGroupID, sArtifactID, null);
  }

  @VisibleForTesting
  RepoToc (@Nonnull @Nonempty final String sGroupID,
           @Nonnull @Nonempty final String sArtifactID,
           @Nullable final Map  aVersions)
  {
    ValueEnforcer.notEmpty (sGroupID, "GroupID");
    ValueEnforcer.notEmpty (sArtifactID, "ArtifactID");
    m_sGroupID = sGroupID;
    m_sArtifactID = sArtifactID;
    if (aVersions != null)
      aVersions.forEach (this::addVersion);
  }

  /**
   * @return The VESID Group ID as provided in the constructor. Neither
   *         null nor empty.
   */
  @Nonnull
  @Nonempty
  public final String getGroupID ()
  {
    return m_sGroupID;
  }

  /**
   * @return The VESID Artefact ID as provided in the constructor. Neither
   *         null nor empty.
   */
  @Nonnull
  @Nonempty
  public final String getArtifactID ()
  {
    return m_sArtifactID;
  }

  /**
   * @return The total number of contained versions. Always ≥ 0.
   */
  @Nonnegative
  public final int getVersionCount ()
  {
    return m_aVersions.size ();
  }

  /**
   * @return A copy of all contained versions as a map from version to its
   *         publication date. Never null.
   */
  @Nonnull
  @ReturnsMutableCopy
  public final ICommonsSortedMap  getAllVersions ()
  {
    return m_aVersions.getClone ();
  }

  /**
   * @return A copy of all contained versions as a sorted set. Never
   *         null.
   * @since 1.1.0
   */
  @Nonnull
  @ReturnsMutableCopy
  public final ICommonsSortedSet  getAllVersionsOnly ()
  {
    return m_aVersions.copyOfKeySet ();
  }

  /**
   * @return A copy of all contained versions as a list. Never
   *         null.
   * @since 1.2.0
   */
  @Nonnull
  @ReturnsMutableCopy
  public final ICommonsList  getAllVersionsAsList ()
  {
    return new CommonsArrayList <> (m_aVersions.keySet ());
  }

  /**
   * Get the latest overall version, including snapshot versions.
   *
   * @return The latest overall version. May be null if no version
   *         is contained.
   * @see #getLatestReleaseVersion()
   */
  @Nullable
  public final VESVersion getLatestVersion ()
  {
    return m_aVersions.getLastKey ();
  }

  /**
   * Get the latest overall version number, including snapshot versions.
   *
   * @return The version number of the latest overall version. May be
   *         null if no version is contained.
   * @see #getLatestReleaseVersionAsString()
   */
  @Nullable
  public final String getLatestVersionAsString ()
  {
    final VESVersion aVer = getLatestVersion ();
    return aVer == null ? null : aVer.getAsString ();
  }

  /**
   * Get the latest overall publication date time, including snapshot versions.
   *
   * @return The publication date time of the latest overall version. May be
   *         null if no version is contained.
   * @see #getLatestReleaseVersionPublicationDateTime()
   */
  @Nullable
  public final OffsetDateTime getLatestVersionPublicationDateTime ()
  {
    return m_aVersions.getLastValue ();
  }

  /**
   * Get the latest overall version, without snapshot versions.
   *
   * @return The latest overall release version. May be null if no
   *         version is contained.
   * @see #getLatestVersion()
   */
  @Nullable
  public final VESVersion getLatestReleaseVersion ()
  {
    return m_aLatestReleaseVersion;
  }

  /**
   * Get the latest overall version number, without snapshot versions.
   *
   * @return The version number of the latest overall release version. May be
   *         null if no version is contained.
   * @see #getLatestVersionAsString()
   */
  @Nullable
  public final String getLatestReleaseVersionAsString ()
  {
    final VESVersion aVer = getLatestReleaseVersion ();
    return aVer == null ? null : aVer.getAsString ();
  }

  /**
   * Get the latest overall publication date time, without snapshot versions.
   *
   * @return The publication date time of the latest overall release version.
   *         May be null if no version is contained.
   * @see #getLatestVersionPublicationDateTime()
   */
  @Nullable
  public final OffsetDateTime getLatestReleaseVersionPublicationDateTime ()
  {
    return m_aLatestReleaseVersionPubDT;
  }

  /**
   * Check if the provided version is contained or not.
   *
   * @param aVersion
   *        The version to check. May be null.
   * @return true if the version is contained, false
   *         if not.
   * @since 1.1.0
   */
  public final boolean containsVersion (@Nullable final VESVersion aVersion)
  {
    return aVersion != null && m_aVersions.containsKey (aVersion);
  }

  /**
   * Get the publication date of the specified version.
   *
   * @param aVersion
   *        The version to query. May be null
   * @return null if the provided version is null or
   *         not present.
   * @since 1.1.2
   */
  @Nullable
  public final OffsetDateTime getPublicationDateTimeOfVersion (@Nullable final VESVersion aVersion)
  {
    if (aVersion == null)
      return null;
    return m_aVersions.get (aVersion);
  }

  @Nonnull
  private EChange _addVersion (@Nonnull final VESVersion aVersion,
                               @Nonnull final OffsetDateTime aPublishDT,
                               final boolean bDoLog)
  {
    if (!aVersion.isStaticVersion ())
    {
      if (bDoLog)
        LOGGER.error ("Only static versions can be added to a table of contents. The version '" +
                      aVersion.getAsString () +
                      "' is not allowed.");
      return EChange.UNCHANGED;
    }

    if (m_aVersions.containsKey (aVersion))
    {
      if (bDoLog)
        if (LOGGER.isDebugEnabled ())
          LOGGER.debug ("The version '" + aVersion.getAsString () + "' is already contained in the table of contents.");
      return EChange.UNCHANGED;
    }

    // Use UTC only; truncate to millisecond precision
    final OffsetDateTime aRealPublishDT = PDTFactory.getWithMillisOnly (aPublishDT.withOffsetSameInstant (ZoneOffset.UTC));
    m_aVersions.put (aVersion, aRealPublishDT);

    // Remember last non-snapshot version
    if (!aVersion.isStaticSnapshotVersion ())
      if (m_aLatestReleaseVersion == null || aVersion.compareTo (m_aLatestReleaseVersion) > 0)
      {
        m_aLatestReleaseVersion = aVersion;
        m_aLatestReleaseVersionPubDT = aRealPublishDT;

        if (bDoLog)
          if (LOGGER.isDebugEnabled ())
            LOGGER.debug ("The added version '" +
                          aVersion.getAsString () +
                          "' is now the latest release version in the table of contents");
      }

    return EChange.CHANGED;
  }

  /**
   * Add a new version to the ToC.
   *
   * @param aVersion
   *        The version to be added. May not be null. Must be a
   *        static version - pseudo versions are not allowed.
   * @param aPublishDT
   *        The publication date time to use. May not be null. It
   *        is internally converted to UTC and cut after millisecond precision.
   * @return {@link EChange#CHANGED} if the version was successfully added,
   *         {@link EChange#UNCHANGED} otherwise.
   */
  @Nonnull
  @OverridingMethodsMustInvokeSuper
  public EChange addVersion (@Nonnull final VESVersion aVersion, @Nonnull final OffsetDateTime aPublishDT)
  {
    ValueEnforcer.notNull (aVersion, "Version");
    ValueEnforcer.notNull (aPublishDT, "PublishDT");

    return _addVersion (aVersion, aPublishDT, true);
  }

  @Nonnull
  @OverridingMethodsMustInvokeSuper
  public EChange removeVersion (@Nonnull final VESVersion aVersion)
  {
    ValueEnforcer.notNull (aVersion, "Version");
    ValueEnforcer.isTrue (aVersion.isStaticVersion (), "Version must be static and not pseudo");

    if (m_aVersions.removeObject (aVersion).isUnchanged ())
    {
      if (LOGGER.isDebugEnabled ())
        LOGGER.debug ("The version '" + aVersion.getAsString () + "' is not contained in the table of contents.");
      return EChange.UNCHANGED;
    }

    // Remember last non-snapshot version
    m_aLatestReleaseVersion = null;
    m_aLatestReleaseVersionPubDT = null;
    if (m_aVersions.isNotEmpty ())
    {
      // Copy from Set to List
      final ICommonsList > aVersionList = new CommonsArrayList <> (m_aVersions.entrySet ());
      // Iterate backwards
      final ListIterator > aIter = aVersionList.listIterator (aVersionList.size ());
      while (aIter.hasPrevious ())
      {
        final Map.Entry  aCur = aIter.previous ();
        if (!aCur.getKey ().isStaticSnapshotVersion ())
        {
          m_aLatestReleaseVersion = aCur.getKey ();
          m_aLatestReleaseVersionPubDT = aCur.getValue ();

          if (LOGGER.isDebugEnabled ())
            LOGGER.debug ("After deletion of '" +
                          aVersion.getAsString () +
                          "' the new latest release version '" +
                          m_aLatestReleaseVersion.getAsString () +
                          "' is used in the table of contents");

          break;
        }
      }
    }

    return EChange.CHANGED;
  }

  @Override
  public boolean equals (final Object o)
  {
    if (o == this)
      return true;
    if (o == null || !getClass ().equals (o.getClass ()))
      return false;
    final RepoToc rhs = (RepoToc) o;
    return m_sGroupID.equals (rhs.m_sGroupID) &&
           m_sArtifactID.equals (rhs.m_sArtifactID) &&
           m_aVersions.equals (rhs.m_aVersions);
  }

  @Override
  public int hashCode ()
  {
    return new HashCodeGenerator (this).append (m_sGroupID).append (m_sArtifactID).append (m_aVersions).getHashCode ();
  }

  @Override
  public String toString ()
  {
    return new ToStringGenerator (null).append ("GroupID", m_sGroupID)
                                       .append ("ArtifactID", m_sArtifactID)
                                       .append ("Versions", m_aVersions)
                                       .getToString ();
  }

  @Nonnull
  public RepoTocType getAsJaxbObject ()
  {
    final RepoTocType ret = new RepoTocType ();
    ret.setGroupId (m_sGroupID);
    ret.setArtifactId (m_sArtifactID);
    {
      final RTVersioningType aVersioning = new RTVersioningType ();
      // Element is mandatory
      aVersioning.setLatest (StringHelper.getNotNull (getLatestVersionAsString (), ""));
      // Element is mandatory
      aVersioning.setLatestRelease (StringHelper.getNotNull (getLatestReleaseVersionAsString (), ""));
      {
        final RTVersionListType aVersions = new RTVersionListType ();
        for (final Map.Entry  aEntry : m_aVersions.entrySet ())
        {
          final RTVersionType aVersion = new RTVersionType ();
          aVersion.setPublished (XMLOffsetDateTime.of (aEntry.getValue ()));
          aVersion.setValue (aEntry.getKey ().getAsString ());
          aVersions.addVersion (aVersion);
        }
        aVersioning.setVersions (aVersions);
      }
      ret.setVersioning (aVersioning);
    }
    return ret;
  }

  @Nonnull
  private static OffsetDateTime _toODT (@Nonnull final XMLOffsetDateTime aXODT)
  {
    return aXODT.hasOffset () ? aXODT.toOffsetDateTime () : aXODT.withOffsetSameInstant (ZoneOffset.UTC)
                                                                 .toOffsetDateTime ();
  }

  @Nonnull
  public static RepoToc createFromJaxbObject (@Nonnull final RepoTocType aRepoToc)
  {
    final RepoToc ret = new RepoToc (aRepoToc.getGroupId (), aRepoToc.getArtifactId ());
    // Try to read as silently as possible, without unnecessary logging
    aRepoToc.getVersioning ()
            .getVersions ()
            .getVersion ()
            .forEach (x -> ret._addVersion (VESVersion.parseOrThrow (x.getValue ()),
                                            _toODT (x.getPublished ()),
                                            false));
    return ret;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy