Fully functional jar-patching and good log output
diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java
index 2c1a09e..e312a47 100644
--- a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java
+++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java
@@ -23,6 +23,7 @@
     private final String name;
     private final String prefix;
     private final File file;
+    private int applied;
 
     public Clazz(final String name, final File file) {
         this.name = name;
@@ -30,6 +31,18 @@
         this.file = file;
     }
 
+    public void applied() {
+        this.applied++;
+    }
+
+    public boolean isApplied() {
+        return applied > 0;
+    }
+
+    public int getApplied() {
+        return applied;
+    }
+
     public String getPrefix() {
         return prefix;
     }
diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java
index 53bb1d8..09faa88 100644
--- a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java
+++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java
@@ -30,11 +30,13 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
 
+import static org.tomitribe.jkta.util.Predicates.not;
+
 public class Transformation {
 
     private final List<Clazz> classes = new ArrayList<Clazz>();
@@ -59,77 +61,76 @@
 
         try (final InputStream inputStream = IO.read(jar)) {
             try (final OutputStream outputStream = IO.write(tempFile)) {
-                final Jar old = Jar.enter(jar.getName());
-                try {
-                    scanJar(inputStream, outputStream);
-                } finally {
-                    Jar.exit(old);
-                }
-                return tempFile;
+                scanJar(jar.getName(), inputStream, outputStream);
             }
         }
+
+        return tempFile;
     }
 
-    private void scanJar(final InputStream inputStream, final OutputStream outputStream) throws IOException {
-        final ZipInputStream zipInputStream = new ZipInputStream(inputStream);
-        final ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
+    private void scanJar(final String name, final InputStream inputStream, final OutputStream outputStream) throws IOException {
+        final Jar oldJar = Jar.enter(name);
+        final Jar jar = Jar.current();
+        try {
+            final ZipInputStream zipInputStream = new ZipInputStream(inputStream);
+            final ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
 
-        ZipEntry oldEntry;
-        while ((oldEntry = zipInputStream.getNextEntry()) != null) {
-            // TODO: the name may be changed in transformation
-            final String path = oldEntry.getName();
+            ZipEntry oldEntry;
+            while ((oldEntry = zipInputStream.getNextEntry()) != null) {
+                // TODO: the name may be changed in transformation
+                final String path = oldEntry.getName();
 
-            /*
-             * If this entry has been patched, skip it
-             * We will add the patched version at the end
-             */
-            if (isPatched(path)) {
-                log.debug("Skipping class " + path);
-                IO.copy(zipInputStream, skipped);
-                continue;
-            }
-
-            final ZipEntry newEntry = new ZipEntry(path);
-
-//            copyAttributes(oldEntry, newEntry);
-
-            zipOutputStream.putNextEntry(newEntry);
-
-            try {
-                if (path.endsWith(".class")) {
-                    scanClass(zipInputStream, zipOutputStream);
-                } else if (isZip(path)) {
-                    final Jar old = Jar.enter(path);
-                    try {
-                        scanJar(zipInputStream, zipOutputStream);
-                    } finally {
-                        Jar.exit(old); // restore the old state
-                    }
-                } else {
-                    IO.copy(zipInputStream, zipOutputStream);
+                /*
+                 * If this entry has been patched, skip it
+                 * We will add the patched version at the end
+                 */
+                if (isPatched(path, jar)) {
+                    log.debug("Skipping class " + path);
+                    IO.copy(zipInputStream, skipped);
+                    continue;
                 }
-            } finally {
-                zipOutputStream.closeEntry();
-            }
-        }
 
-        // If we skipped any classes, add them now
-        if (Jar.current().hasPatches()) {
-            log.info("Patching " + Jar.current().getName());
-            for (final Clazz clazz : Jar.current().getSkipped()) {
-                log.info("Applying patch " + clazz.getName());
+                final ZipEntry newEntry = new ZipEntry(path);
 
-                final ZipEntry newEntry = new ZipEntry(clazz.getName());
+                //            copyAttributes(oldEntry, newEntry);
+
                 zipOutputStream.putNextEntry(newEntry);
 
-                // Run any transformations on these classes as well
-                scanClass(IO.read(clazz.getFile()), zipOutputStream);
-
-                zipOutputStream.closeEntry();
+                try {
+                    if (path.endsWith(".class")) {
+                        scanClass(zipInputStream, zipOutputStream);
+                    } else if (isZip(path)) {
+                        scanJar(path, zipInputStream, zipOutputStream);
+                    } else {
+                        IO.copy(zipInputStream, zipOutputStream);
+                    }
+                } finally {
+                    zipOutputStream.closeEntry();
+                }
             }
-        }
 
-        zipOutputStream.close();
+            // If we skipped any classes, add them now
+            if (jar.hasPatches()) {
+                log.info("Patching " + jar.getName());
+                for (final Clazz clazz : jar.getSkipped()) {
+                    log.debug("Applying patch " + clazz.getName());
+
+                    final ZipEntry newEntry = new ZipEntry(clazz.getName());
+                    zipOutputStream.putNextEntry(newEntry);
+
+                    // Run any transformations on these classes as well
+                    scanClass(IO.read(clazz.getFile()), zipOutputStream);
+
+                    zipOutputStream.closeEntry();
+                    clazz.applied();
+                }
+            }
+            zipOutputStream.finish();
+        } catch (IOException e) {
+            throw new IOException(jar.getPath() + e.getMessage(), e);
+        } finally {
+            Jar.exit(oldJar);
+        }
     }
 
     private static void copyAttributes(final ZipEntry oldEntry, final ZipEntry newEntry) {
@@ -156,20 +157,63 @@
         outputStream.write(bytes);
     }
 
+    public void complete() {
+        final List<Clazz> appliedPatches = classes.stream()
+                .filter(Clazz::isApplied)
+                .collect(Collectors.toList());
+
+        final List<Clazz> unappliedPatches = classes.stream()
+                .filter(not(Clazz::isApplied))
+                .collect(Collectors.toList());
+
+        final int applied = appliedPatches.stream()
+                .map(Clazz::getApplied)
+                .reduce(Integer::sum)
+                .orElse(0);
+
+        log.info(String.format("Applied %s patches to %s locations", appliedPatches.size(), applied));
+        appliedPatches.stream()
+                .map(Clazz::getName)
+                .map(s -> "  " + s)
+                .forEach(log::debug);
+
+        if (unappliedPatches.size() > 0) {
+            final String message = String.format("Failed to apply %s patches", unappliedPatches.size());
+            log.error(message);
+            unappliedPatches.stream()
+                    .map(Clazz::getName)
+                    .map(s -> "  " + s)
+                    .forEach(log::error);
+            throw new UnappliedPatchesException(unappliedPatches);
+        }
+    }
+
     public static class Jar {
-        private static final AtomicReference<Jar> current = new AtomicReference<>(new Jar("<none>"));
+        private static final ThreadLocal<Jar> current = ThreadLocal.withInitial(Jar::new);
 
         private final Set<Clazz> patches = new HashSet<>();
         private final String name;
+        private final Jar parent;
 
-        public Jar(final String name) {
+        private Jar() {
+            this.name = "";
+            this.parent = null;
+        }
+
+        private Jar(final String name, Jar parent) {
             this.name = name;
+            this.parent = parent;
         }
 
         public String getName() {
             return name;
         }
 
+        public String getPath() {
+            if (parent == null) return name;
+            return parent.getPath() + "/" + name;
+        }
+
         public boolean hasPatches() {
             return patches.size() > 0;
         }
@@ -179,11 +223,13 @@
         }
 
         public static Jar enter(final String name) {
-            return current.getAndSet(new Jar(name));
+            final Jar old = current.get();
+            current.set(new Jar(name, old));
+            return old;
         }
 
         public static void exit(final Jar oldJar) {
-            current.getAndSet(oldJar);
+            current.set(oldJar);
         }
 
         public Collection<Clazz> getSkipped() {
@@ -201,10 +247,10 @@
         }
     }
 
-    private boolean isPatched(final String path) {
+    private boolean isPatched(final String path, final Jar jar) {
         for (final Clazz clazz : classes) {
             if (path.startsWith(clazz.getPrefix())) {
-                Jar.current().patch(clazz, classes);
+                jar.patch(clazz, classes);
                 return true;
             }
         }
diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/UnappliedPatchesException.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/UnappliedPatchesException.java
new file mode 100644
index 0000000..68d9970
--- /dev/null
+++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/UnappliedPatchesException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.tomee.patch.core;
+
+import java.util.List;
+
+public class UnappliedPatchesException extends IllegalStateException {
+
+    private final List<Clazz> unappliedPatches;
+
+    public UnappliedPatchesException(final List<Clazz> unappliedPatches) {
+        super(String.format("Failed to apply %s patches", unappliedPatches.size()));
+        this.unappliedPatches = unappliedPatches;
+    }
+
+    public List<Clazz> getUnappliedPatches() {
+        return unappliedPatches;
+    }
+}
diff --git a/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java b/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java
index e4e4ed8..a74eb2e 100644
--- a/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java
+++ b/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java
@@ -179,6 +179,7 @@
                 IO.copy(patched, file);
             }
 
+            transformation.complete();
         } catch (IOException e) {
             throw new MojoExecutionException("Error occurred during execution", e);
         }