diff --git a/ph-diver-repo/src/main/java/com/helger/diver/repo/RepoStorageKey.java b/ph-diver-repo/src/main/java/com/helger/diver/repo/RepoStorageKey.java index 12e62d0..beb6ac0 100644 --- a/ph-diver-repo/src/main/java/com/helger/diver/repo/RepoStorageKey.java +++ b/ph-diver-repo/src/main/java/com/helger/diver/repo/RepoStorageKey.java @@ -48,6 +48,8 @@ public final class RepoStorageKey */ public static final String FILENAME_TOC_DIVER_XML = "toc-diver.xml"; + public static final char GROUP_LEVEL_SEPARATOR = '.'; + private static final Logger LOGGER = LoggerFactory.getLogger (RepoStorageKey.class); // Special fake version to be used by the ToC where we don't need any version @@ -144,7 +146,7 @@ public static String getPathOfGroupIDAndArtifactID (@Nonnull @Nonempty final Str ValueEnforcer.notEmpty (sGroupID, "GroupID"); ValueEnforcer.notEmpty (sArtifactID, "ArtifactID"); - return sGroupID.replace ('.', '/') + "/" + sArtifactID + "/"; + return sGroupID.replace (GROUP_LEVEL_SEPARATOR, '/') + "/" + sArtifactID + "/"; } /** diff --git a/ph-diver-repo/src/main/java/com/helger/diver/repo/toc/RepoTopToc.java b/ph-diver-repo/src/main/java/com/helger/diver/repo/toc/RepoTopToc.java new file mode 100644 index 0000000..ea4f5a3 --- /dev/null +++ b/ph-diver-repo/src/main/java/com/helger/diver/repo/toc/RepoTopToc.java @@ -0,0 +1,227 @@ +package com.helger.diver.repo.toc; + +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.helger.commons.ValueEnforcer; +import com.helger.commons.annotation.Nonempty; +import com.helger.commons.annotation.ReturnsMutableCopy; +import com.helger.commons.collection.impl.CommonsTreeMap; +import com.helger.commons.collection.impl.CommonsTreeSet; +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.string.StringHelper; +import com.helger.diver.api.version.VESID; +import com.helger.diver.repo.RepoStorageKey; +import com.helger.diver.repo.toptoc.jaxb.v10.ArtifactType; +import com.helger.diver.repo.toptoc.jaxb.v10.GroupType; +import com.helger.diver.repo.toptoc.jaxb.v10.RepoTopTocType; + +/** + * JAXB independent top ToC representation + * + * @author Philip Helger + */ +public class RepoTopToc +{ + private static final class Group + { + private final String m_sName; + private final ICommonsSortedMap m_aSubGroups = new CommonsTreeMap <> (); + private final ICommonsSortedSet m_aArtifacts = new CommonsTreeSet <> (); + + public Group (@Nonnull @Nonempty final String sName) + { + ValueEnforcer.notEmpty (sName, "Name"); + ValueEnforcer.isTrue ( () -> VESID.isValidPart (sName), "Name is not a valid group part"); + m_sName = sName; + } + + public boolean deepEquals (@Nonnull final Group rhs) + { + // Check all fields - takes longer + return m_sName.equals (rhs.m_sName) && + m_aSubGroups.equals (rhs.m_aSubGroups) && + m_aArtifacts.equals (rhs.m_aArtifacts); + } + } + + private final ICommonsSortedMap m_aTopLevelGroups = new CommonsTreeMap <> (); + + public RepoTopToc () + {} + + @Nonnull + @ReturnsMutableCopy + public ICommonsSortedSet getAllTopLevelGroupNames () + { + return m_aTopLevelGroups.copyOfKeySet (); + } + + @Nullable + private Group _getGroup (@Nonnull @Nonempty final String sGroupID) + { + final ICommonsList aGroupPart = StringHelper.getExploded (RepoStorageKey.GROUP_LEVEL_SEPARATOR, sGroupID); + // Resolve all recursive subgroups + Group aGroup = m_aTopLevelGroups.get (aGroupPart.removeFirst ()); + while (aGroup != null && aGroupPart.isNotEmpty ()) + { + aGroup = aGroup.m_aSubGroups.get (aGroupPart.removeFirst ()); + } + return aGroup; + } + + private void _recursiveIterateExistingSubGroups (@Nonnull @Nonempty final String sAbsoluteGroupID, + @Nonnull final Group aCurGroup, + @Nonnull final BiConsumer aGroupNameConsumer) + { + for (final Map.Entry aEntry : aCurGroup.m_aSubGroups.entrySet ()) + { + final String sSubGroupName = aEntry.getKey (); + final String sSubAbsName = sAbsoluteGroupID + RepoStorageKey.GROUP_LEVEL_SEPARATOR + sSubGroupName; + aGroupNameConsumer.accept (sSubGroupName, sSubAbsName); + + // Descend + _recursiveIterateExistingSubGroups (sSubAbsName, aEntry.getValue (), aGroupNameConsumer); + } + } + + public void iterateAllSubGroups (@Nonnull @Nonempty final String sGroupID, + @Nonnull final BiConsumer aGroupNameConsumer) + { + ValueEnforcer.notEmpty (sGroupID, "GroupID"); + ValueEnforcer.notNull (aGroupNameConsumer, "GroupNameConsumer"); + + final Group aGroup = _getGroup (sGroupID); + if (aGroup != null) + { + _recursiveIterateExistingSubGroups (sGroupID, aGroup, aGroupNameConsumer); + } + // else: no group, no callback + } + + public void iterateAllArtifacts (@Nonnull @Nonempty final String sGroupID, + @Nonnull final Consumer aArtifactNameConsumer) + { + ValueEnforcer.notEmpty (sGroupID, "GroupID"); + ValueEnforcer.notNull (aArtifactNameConsumer, "ArtifactNameConsumer"); + + final Group aGroup = _getGroup (sGroupID); + if (aGroup != null) + { + for (final String sArtifactID : aGroup.m_aArtifacts) + aArtifactNameConsumer.accept (sArtifactID); + } + // else: no group, no callback + } + + @Nonnull + private Group _getOrCreateGroup (@Nonnull @Nonempty final String sGroupID) + { + final ICommonsList aGroupPart = StringHelper.getExploded (RepoStorageKey.GROUP_LEVEL_SEPARATOR, sGroupID); + // Resolve all recursive subgroups + Group aGroup = m_aTopLevelGroups.computeIfAbsent (aGroupPart.removeFirst (), Group::new); + while (aGroupPart.isNotEmpty ()) + { + aGroup = aGroup.m_aSubGroups.computeIfAbsent (aGroupPart.removeFirst (), Group::new); + } + return aGroup; + } + + public void registerGroupAndArtifact (@Nonnull @Nonempty final String sGroupID, + @Nonnull @Nonempty final String sArtifactID) + { + ValueEnforcer.notEmpty (sGroupID, "GroupID"); + ValueEnforcer.notEmpty (sArtifactID, "ArtifactID"); + + final Group aGroup = _getOrCreateGroup (sGroupID); + // Add to artifact list of latest subgroup + aGroup.m_aArtifacts.add (sArtifactID); + } + + public boolean deepEquals (@Nonnull final RepoTopToc aOther) + { + ValueEnforcer.notNull (aOther, "Other"); + + // Size must match + if (m_aTopLevelGroups.size () != aOther.m_aTopLevelGroups.size ()) + return false; + + for (final Map.Entry aEntry : m_aTopLevelGroups.entrySet ()) + { + final Group aOtherGroup = aOther.m_aTopLevelGroups.get (aEntry.getKey ()); + if (aOtherGroup == null) + { + // Only present in this but not in other + return false; + } + + final Group aThisGroup = aEntry.getValue (); + if (!aThisGroup.deepEquals (aOtherGroup)) + { + // Groups differ + return false; + } + } + return true; + } + + private static void _recursiveReadGroups (@Nonnull final String sAbsoluteGroupName, + @Nonnull final GroupType aSrcGroup, + @Nonnull final Group aDstGroup) + { + // First add artifacts + for (final ArtifactType aSrcArtifact : aSrcGroup.getArtifact ()) + { + final String sArtifactName = aSrcArtifact.getName (); + if (!aDstGroup.m_aArtifacts.add (sArtifactName)) + throw new IllegalStateException ("The artifact '" + + sAbsoluteGroupName + + ':' + + sArtifactName + + "' is contained more then once"); + } + + // Now all subgroups + for (final GroupType aSrcSubGroup : aSrcGroup.getGroup ()) + { + final String sSubGroupName = aSrcSubGroup.getName (); + final Group aDstSubGroup = new Group (sSubGroupName); + if (aDstGroup.m_aSubGroups.put (sSubGroupName, aDstSubGroup) != null) + throw new IllegalArgumentException ("Another group with name '" + + sAbsoluteGroupName + + RepoStorageKey.GROUP_LEVEL_SEPARATOR + + sSubGroupName + + "' is already contained"); + + // Descend recursively + _recursiveReadGroups (sAbsoluteGroupName + RepoStorageKey.GROUP_LEVEL_SEPARATOR + sSubGroupName, + aSrcSubGroup, + aDstSubGroup); + } + } + + @Nonnull + public static RepoTopToc createFromJaxbObject (@Nonnull final RepoTopTocType aRepoTopToc) + { + ValueEnforcer.notNull (aRepoTopToc, "RepoTopToc"); + + final RepoTopToc ret = new RepoTopToc (); + for (final GroupType aSrcGroup : aRepoTopToc.getGroup ()) + { + final String sGroupName = aSrcGroup.getName (); + final Group aDstGroup = new Group (sGroupName); + if (ret.m_aTopLevelGroups.put (sGroupName, aDstGroup) != null) + throw new IllegalArgumentException ("Another top-level group with name '" + + sGroupName + + "' is already contained"); + _recursiveReadGroups (sGroupName, aSrcGroup, aDstGroup); + } + return ret; + } +} diff --git a/ph-diver-repo/src/main/java/com/helger/diver/repo/toptoc/RepoTopToc1Marshaller.java b/ph-diver-repo/src/main/java/com/helger/diver/repo/toc/RepoTopToc1Marshaller.java similarity index 97% rename from ph-diver-repo/src/main/java/com/helger/diver/repo/toptoc/RepoTopToc1Marshaller.java rename to ph-diver-repo/src/main/java/com/helger/diver/repo/toc/RepoTopToc1Marshaller.java index 9fb055d..62cc8ee 100644 --- a/ph-diver-repo/src/main/java/com/helger/diver/repo/toptoc/RepoTopToc1Marshaller.java +++ b/ph-diver-repo/src/main/java/com/helger/diver/repo/toc/RepoTopToc1Marshaller.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.helger.diver.repo.toptoc; +package com.helger.diver.repo.toc; import com.helger.commons.collection.impl.CommonsArrayList; import com.helger.commons.io.resource.ClassPathResource; diff --git a/ph-diver-repo/src/test/java/com/helger/diver/repo/toptoc/RepoTopToc1MarshallerTest.java b/ph-diver-repo/src/test/java/com/helger/diver/repo/toc/RepoTopToc1MarshallerTest.java similarity index 95% rename from ph-diver-repo/src/test/java/com/helger/diver/repo/toptoc/RepoTopToc1MarshallerTest.java rename to ph-diver-repo/src/test/java/com/helger/diver/repo/toc/RepoTopToc1MarshallerTest.java index 6a18277..fa0f4cd 100644 --- a/ph-diver-repo/src/test/java/com/helger/diver/repo/toptoc/RepoTopToc1MarshallerTest.java +++ b/ph-diver-repo/src/test/java/com/helger/diver/repo/toc/RepoTopToc1MarshallerTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.helger.diver.repo.toptoc; +package com.helger.diver.repo.toc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -53,7 +53,7 @@ public void testRepoToc1 () assertEquals (2, aCom.getGroupAtIndex (0).getArtifactCount ()); assertEquals (0, aCom.getGroupAtIndex (1).getGroupCount ()); assertEquals (1, aCom.getGroupAtIndex (1).getArtifactCount ()); - assertEquals (0, aCom.getGroupAtIndex (2).getGroupCount ()); + assertEquals (1, aCom.getGroupAtIndex (2).getGroupCount ()); assertEquals (0, aCom.getGroupAtIndex (2).getArtifactCount ()); } } diff --git a/ph-diver-repo/src/test/java/com/helger/diver/repo/toc/RepoTopTocTest.java b/ph-diver-repo/src/test/java/com/helger/diver/repo/toc/RepoTopTocTest.java new file mode 100644 index 0000000..bb9e8c3 --- /dev/null +++ b/ph-diver-repo/src/test/java/com/helger/diver/repo/toc/RepoTopTocTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.helger.commons.collection.impl.CommonsArrayList; +import com.helger.commons.collection.impl.CommonsHashSet; +import com.helger.commons.collection.impl.ICommonsList; +import com.helger.commons.collection.impl.ICommonsSet; +import com.helger.commons.collection.impl.ICommonsSortedSet; +import com.helger.commons.io.resource.ClassPathResource; +import com.helger.diver.repo.toptoc.jaxb.v10.RepoTopTocType; + +/** + * Test class for class {@link RepoTopToc}. + * + * @author Philip Helger + */ +public final class RepoTopTocTest +{ + @Test + public void testBasic () + {} + + @Test + public void testCreateFromJaxb () + { + final RepoTopToc1Marshaller m = new RepoTopToc1Marshaller (); + final RepoTopTocType aRepoTopToc1 = m.read (new ClassPathResource ("repotoptoc/repotoptoc-1.xml")); + assertNotNull (aRepoTopToc1); + + final RepoTopToc aToC = RepoTopToc.createFromJaxbObject (aRepoTopToc1); + assertNotNull (aToC); + + // Check top level groups + final ICommonsSortedSet aTLGroups = aToC.getAllTopLevelGroupNames (); + assertNotNull (aTLGroups); + assertEquals (2, aTLGroups.size ()); + assertTrue (aTLGroups.contains ("com")); + assertTrue (aTLGroups.contains ("org")); + + // Check all subgroups from a specific start + final ICommonsList aAllRelSubgroups = new CommonsArrayList <> (); + final ICommonsSet aAllAbsSubgroups = new CommonsHashSet <> (); + aToC.iterateAllSubGroups ("com", (relgn, absgn) -> { + aAllRelSubgroups.add (relgn); + aAllAbsSubgroups.add (absgn); + }); + assertEquals (6, aAllAbsSubgroups.size ()); + assertTrue (aAllAbsSubgroups.contains ("com.ecosio")); + assertTrue (aAllAbsSubgroups.contains ("com.helger")); + assertTrue (aAllAbsSubgroups.contains ("com.rest")); + assertTrue (aAllAbsSubgroups.contains ("com.rest.of")); + assertTrue (aAllAbsSubgroups.contains ("com.rest.of.the")); + assertTrue (aAllAbsSubgroups.contains ("com.rest.of.the.fest")); + + assertEquals (6, aAllRelSubgroups.size ()); + assertEquals ("ecosio", aAllRelSubgroups.get (0)); + assertEquals ("helger", aAllRelSubgroups.get (1)); + assertEquals ("rest", aAllRelSubgroups.get (2)); + assertEquals ("of", aAllRelSubgroups.get (3)); + assertEquals ("the", aAllRelSubgroups.get (4)); + assertEquals ("fest", aAllRelSubgroups.get (5)); + + final ICommonsSet aAllArtifacts = new CommonsHashSet <> (); + aToC.iterateAllArtifacts ("com", artifactID -> { aAllArtifacts.add (artifactID); }); + } +} diff --git a/ph-diver-repo/src/test/resources/repotoptoc/repotoptoc-1.xml b/ph-diver-repo/src/test/resources/repotoptoc/repotoptoc-1.xml index e250a7e..f7f3eb4 100644 --- a/ph-diver-repo/src/test/resources/repotoptoc/repotoptoc-1.xml +++ b/ph-diver-repo/src/test/resources/repotoptoc/repotoptoc-1.xml @@ -27,7 +27,13 @@ - + + + + + + +