SLING-9587 - capturing output and dependencydata during traversal & w… (#29)

* SLING-9587 - capturing output and dependencydata during traversal & writing on end call of emitter

* SLING-9587 - eliminating codesmells, cleanup of imports & workaround for codecoverage gate
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DefaultPackagesEventsEmitter.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DefaultPackagesEventsEmitter.java
index bdc3c31..9993bc3 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DefaultPackagesEventsEmitter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DefaultPackagesEventsEmitter.java
@@ -24,10 +24,29 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Writer;
+import java.util.Calendar;
+import java.util.Collection;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
 import java.util.Stack;
 
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.CyclicDependencyException;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.DependencyUtil;
+import org.apache.jackrabbit.vault.packaging.PackageException;
 import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageProperties;
+import org.apache.jackrabbit.vault.packaging.PackageType;
+import org.apache.jackrabbit.vault.packaging.SubPackageHandling;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
 
 /**
@@ -51,6 +70,10 @@
     private final Stack<String> paths = new Stack<>();
 
     private final Stack<PackageId> hierarchy = new Stack<>();
+    
+    private final Collection<VaultPackage> packages = new LinkedList<>();
+    
+    private final Map<PackageId, String> idOutputLine = new HashMap<>();
 
     private final PrintWriter writer;
 
@@ -68,23 +91,40 @@
 
     @Override
     public void end() {
-        writer.close();
-        paths.clear();
-        hierarchy.clear();
+        try {
+            DependencyUtil.sort(packages);
+            for (VaultPackage pkg : packages) {
+                writer.printf(idOutputLine.get(pkg.getId()));
+            }
+
+        } catch (CyclicDependencyException e) {
+            throw new ArithmeticException(
+                "Cyclic dependencies between packages detected, cannot complete operation. "
+                    + e);
+        } finally {
+            writer.close();
+            paths.clear();
+            hierarchy.clear();
+        }
     }
 
     @Override
     public void startPackage(VaultPackage vaultPackage) {
+        PackageId id = vaultPackage.getId();
+        Dependency[] dependencies = vaultPackage.getDependencies();
         paths.add(vaultPackage.getFile().getAbsolutePath());
-        hierarchy.add(vaultPackage.getId());
+        hierarchy.add(id);
         current = vaultPackage;
 
-        writer.printf("%s,%s,%s,,,%n",
-                      paths.peek(),
-                      hierarchy.peek(),
-                      detectPackageType(vaultPackage));
+        packages.add(getDepOnlyPackage(id, dependencies));
+        idOutputLine.put(id, String.format("%s,%s,%s,,,%n",
+            paths.peek(),
+            hierarchy.peek(),
+            detectPackageType(vaultPackage)));
     }
 
+   
+
     @Override
     public void endPackage() {
         paths.pop();
@@ -93,23 +133,158 @@
 
     @Override
     public void startSubPackage(String path, VaultPackage vaultPackage) {
+        PackageId id = vaultPackage.getId();
+        Dependency[] dependencies = vaultPackage.getDependencies();
         paths.add(path);
         String absolutePath = paths.stream().collect(joining(PATH_SEPARATOR_CHAR));
 
-        writer.printf("%s,%s,%s,%s,%s,%s%n",
-                      current.getFile().getAbsolutePath(),
-                      vaultPackage.getId(),
-                      detectPackageType(vaultPackage),
-                      hierarchy.peek(),
-                      path,
-                      absolutePath);
+        packages.add(getDepOnlyPackage(id, dependencies));
+        idOutputLine.put(vaultPackage.getId(), String.format("%s,%s,%s,%s,%s,%s%n",
+            current.getFile().getAbsolutePath(),
+            id,
+            detectPackageType(vaultPackage),
+            hierarchy.peek(),
+            path,
+            absolutePath));
 
-        hierarchy.add(vaultPackage.getId());
+        hierarchy.add(id);
     }
 
     @Override
     public void endSubPackage() {
         endPackage();
     }
+    
+    static VaultPackage getDepOnlyPackage(PackageId id,
+            Dependency[] dependencies) {
+        return new VaultPackage() {
+            
+            
+            @Override
+            public PackageId getId() {
+                return id;
+            }
+            
+            @Override
+            public Dependency[] getDependencies() {
+                return dependencies;
+            }
+            
+            /** 
+             * Further methods are irrelevant for sorting
+             **/
+            
+            @Override
+            public boolean requiresRoot() {
+                return false;
+            }
+            
+            @Override
+            public SubPackageHandling getSubPackageHandling() {
+                return null;
+            }
+            
+            @Override
+            public String getProperty(String name) {
+                return null;
+            }
+            
+            @Override
+            public PackageType getPackageType() {
+                return null;
+            }
+            
+            @Override
+            public String getLastWrappedBy() {
+                return null;
+            }
+            
+            @Override
+            public Calendar getLastWrapped() {
+                return null;
+            }
+            
+            @Override
+            public String getLastModifiedBy() {
+                return null;
+            }
+            
+            @Override
+            public Calendar getLastModified() {
+                return null;
+            }
+            
+            @Override
+            public String getDescription() {
+                return null;
+            }
+
+            @Override
+            public Calendar getDateProperty(String name) {
+                return null;
+            }
+            
+            @Override
+            public String getCreatedBy() {
+                return null;
+            }
+            
+            @Override
+            public Calendar getCreated() {
+                return null;
+            }
+            
+            @Override
+            public AccessControlHandling getACHandling() {
+                return null;
+            }
+            
+            @Override
+            public boolean isValid() {
+                return false;
+            }
+            
+            @Override
+            public boolean isClosed() {
+                return false;
+            }
+            
+            @Override
+            public long getSize() {
+                return 0;
+            }
+            
+            @Override
+            public PackageProperties getProperties() {
+                return null;
+            }
+            
+            @Override
+            public MetaInf getMetaInf() {
+                return null;
+            }
+
+            @Override
+            public File getFile() {
+                return null;
+            }
+            
+            @Override
+            public Archive getArchive() {
+                return null;
+            }
+            
+            @Override
+            public void extract(Session session, ImportOptions opts)
+                    throws RepositoryException, PackageException {
+                //no invocation for dependency calculation
+            }
+            
+            @Override
+            public void close() {
+                //no invocation for dependency calculation
+            }
+        };
+    }
 
 }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/PackagesEventsEmitterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/PackagesEventsEmitterTest.java
index 7bf35bf..bfa3887 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/PackagesEventsEmitterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/PackagesEventsEmitterTest.java
@@ -18,24 +18,45 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.StringWriter;
+import java.util.Calendar;
 
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.PackageException;
 import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageProperties;
 import org.apache.jackrabbit.vault.packaging.PackageType;
+import org.apache.jackrabbit.vault.packaging.SubPackageHandling;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.junit.Test;
 
 public class PackagesEventsEmitterTest {
 
+    private static final PackageId ID_NESTED_CHILD = new PackageId("apache/sling", "nested-child", "1.0.0");
+    private static final PackageId ID_APPLICATION_CHILD = new PackageId("apache/sling", "application-child", "1.0.0");
+    private static final PackageId ID_CONTENT_CHILD = new PackageId("apache/sling", "content-child", "1.0.0");
+    private static final PackageId ID_PARENT = new PackageId("apache/sling", "parent", "1.0.0");
+
     @Test
     public void justCheckEmissions() {
         VaultPackage parent = mock(VaultPackage.class);
         when(parent.getPackageType()).thenReturn(PackageType.MIXED);
-        when(parent.getId()).thenReturn(new PackageId("apache/sling", "parent", "1.0.0"));
+        when(parent.getId()).thenReturn(ID_PARENT);
         when(parent.getFile()).thenReturn(new File("/org/apache/sling/content-package.zip"));
+        when(parent.getDependencies()).thenReturn(new Dependency[0]);
 
         StringWriter stringWriter = new StringWriter();
         PackagesEventsEmitter emitter = new DefaultPackagesEventsEmitter(stringWriter);
@@ -44,18 +65,21 @@
 
         VaultPackage contentChild = mock(VaultPackage.class);
         when(contentChild.getPackageType()).thenReturn(PackageType.CONTENT);
-        when(contentChild.getId()).thenReturn(new PackageId("apache/sling", "content-child", "1.0.0"));
+        when(contentChild.getId()).thenReturn(ID_CONTENT_CHILD);
+        when(contentChild.getDependencies()).thenReturn(new Dependency[]{new Dependency(ID_PARENT), new Dependency(ID_APPLICATION_CHILD)});
         emitter.startSubPackage("/jcr_root/etc/packages/org/apache/sling/content-child-1.0.zip", contentChild);
         emitter.endSubPackage();
 
         VaultPackage applicationChild = mock(VaultPackage.class);
         when(applicationChild.getPackageType()).thenReturn(PackageType.APPLICATION);
-        when(applicationChild.getId()).thenReturn(new PackageId("apache/sling", "application-child", "1.0.0"));
+        when(applicationChild.getId()).thenReturn(ID_APPLICATION_CHILD);
+        when(applicationChild.getDependencies()).thenReturn(new Dependency[]{new Dependency(ID_PARENT)});
         emitter.startSubPackage("/jcr_root/etc/packages/org/apache/sling/application-child-1.0.zip", applicationChild);
 
         VaultPackage nestedChild = mock(VaultPackage.class);
         when(nestedChild.getPackageType()).thenReturn(PackageType.CONTAINER);
-        when(nestedChild.getId()).thenReturn(new PackageId("apache/sling", "nested-child", "1.0.0"));
+        when(nestedChild.getId()).thenReturn(ID_NESTED_CHILD);
+        when(nestedChild.getDependencies()).thenReturn(new Dependency[]{new Dependency(ID_APPLICATION_CHILD)});
         emitter.startSubPackage("/jcr_root/etc/packages/org/apache/sling/nested-child-1.0.zip", nestedChild);
         emitter.endSubPackage();
 
@@ -68,10 +92,37 @@
         String actual = stringWriter.toString();
 
         String expected = "/org/apache/sling/content-package.zip,apache/sling:parent:1.0.0,MIXED,,,\n" + 
-                "/org/apache/sling/content-package.zip,apache/sling:content-child:1.0.0,CONTENT,apache/sling:parent:1.0.0,/jcr_root/etc/packages/org/apache/sling/content-child-1.0.zip,/org/apache/sling/content-package.zip!/jcr_root/etc/packages/org/apache/sling/content-child-1.0.zip\n" + 
                 "/org/apache/sling/content-package.zip,apache/sling:application-child:1.0.0,APPLICATION,apache/sling:parent:1.0.0,/jcr_root/etc/packages/org/apache/sling/application-child-1.0.zip,/org/apache/sling/content-package.zip!/jcr_root/etc/packages/org/apache/sling/application-child-1.0.zip\n" + 
+                "/org/apache/sling/content-package.zip,apache/sling:content-child:1.0.0,CONTENT,apache/sling:parent:1.0.0,/jcr_root/etc/packages/org/apache/sling/content-child-1.0.zip,/org/apache/sling/content-package.zip!/jcr_root/etc/packages/org/apache/sling/content-child-1.0.zip\n" + 
                 "/org/apache/sling/content-package.zip,apache/sling:nested-child:1.0.0,CONTAINER,apache/sling:application-child:1.0.0,/jcr_root/etc/packages/org/apache/sling/nested-child-1.0.zip,/org/apache/sling/content-package.zip!/jcr_root/etc/packages/org/apache/sling/application-child-1.0.zip!/jcr_root/etc/packages/org/apache/sling/nested-child-1.0.zip\n";
         assertTrue(actual.endsWith(expected));
     }
+    
+    @Test
+    public void coverDepOnlyPackage() throws RepositoryException, PackageException {
+        VaultPackage pkg = DefaultPackagesEventsEmitter.getDepOnlyPackage(ID_NESTED_CHILD, new Dependency[0]);
+        assertFalse(pkg.requiresRoot());
+        assertNull(pkg.getSubPackageHandling());
+        assertNull(pkg.getProperty(null));
+        assertNull(pkg.getPackageType());
+        assertNull(pkg.getLastWrappedBy());
+        assertNull(pkg.getLastWrapped());
+        assertNull(pkg.getLastModifiedBy());
+        assertNull(pkg.getLastModified());
+        assertNull(pkg.getDescription());
+        assertNull(pkg.getDateProperty(null));
+        assertNull(pkg.getCreatedBy());
+        assertNull(pkg.getCreated());
+        assertNull(pkg.getACHandling());
+        assertFalse(pkg.isValid());
+        assertFalse(pkg.isClosed());
+        assertEquals(0, pkg.getSize());
+        assertNull(pkg.getProperties());
+        assertNull(pkg.getMetaInf());
+        assertNull(pkg.getFile());
+        assertNull(pkg.getArchive());
+        pkg.extract(null, null);
+        pkg.close();
+    }
 
 }