changeset 14214:236a0b5f4df8

Post processing API and implementation. Reviewed-by: jlaskey
author jfdenise
date Fri, 09 Oct 2015 18:03:49 +0200
parents 67a090f8d062
children fee0cf90610a
files src/jdk.jlink/share/classes/jdk/tools/jlink/JlinkTask.java src/jdk.jlink/share/classes/jdk/tools/jlink/TaskHelper.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginConfiguration.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ProcessingManagerImpl.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/CmdPostProcessingPluginProvider.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/DefaultImageBuilder.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/DefaultImageBuilderProvider.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ExecutableImage.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ImageBuilder.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ImageBuilderProvider.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/Jlink.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/OnOffPostProcessingPluginProvider.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PluginProvider.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PostProcessingPlugin.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PostProcessingPluginProvider.java src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ProcessingManager.java src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties test/jdk/jigsaw/tools/jlink/ImageFileCreatorTest.java test/jdk/jigsaw/tools/jlink/IntegrationTest.java test/jdk/jigsaw/tools/jlink/JLinkPostProcessingTest.java test/jdk/jigsaw/tools/jlink/customplugin/plugin/CustomImageBuilder.java test/jdk/jigsaw/tools/jlink/customplugin/plugin/CustomImageBuilderProvider.java test/jdk/jigsaw/tools/jlink/customplugin/plugin/SameNamedImageBuilderProvider.java test/jdk/jigsaw/tools/jlink/customplugin/plugin/SecondImageBuilderProvider.java test/jdk/jigsaw/tools/jlink/plugins/CompressorPluginTest.java test/jdk/jigsaw/tools/jlink/plugins/LastSorterTest.java test/jdk/jigsaw/tools/jlink/plugins/OnOffProviderTest.java test/jdk/jigsaw/tools/jlink/plugins/PluginOrderTest.java test/jdk/jigsaw/tools/jlink/plugins/PluginsNegativeTest.java test/jdk/jigsaw/tools/jlink/plugins/PrevisitorTest.java test/jdk/jigsaw/tools/jlink/plugins/StripDebugPluginTest.java test/jdk/jigsaw/tools/lib/tests/Helper.java test/jdk/jigsaw/tools/lib/tests/JImageGenerator.java
diffstat 35 files changed, 1529 insertions(+), 158 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/JlinkTask.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/JlinkTask.java	Fri Oct 09 18:03:49 2015 +0200
@@ -44,6 +44,7 @@
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -63,7 +64,11 @@
 import jdk.tools.jlink.internal.DirArchive;
 import jdk.tools.jlink.internal.ImageFileCreator;
 import jdk.tools.jlink.internal.ImagePluginConfiguration;
+import jdk.tools.jlink.internal.ImagePluginProviderRepository;
 import jdk.tools.jlink.internal.ImagePluginStack;
+import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider;
+import jdk.tools.jlink.plugins.ExecutableImage;
+import jdk.tools.jlink.plugins.ImageBuilderProvider;
 import jdk.tools.jlink.plugins.Jlink.JlinkConfiguration;
 import jdk.tools.jlink.plugins.Jlink.PluginsConfiguration;
 
@@ -128,6 +133,13 @@
             task.options.output = path;
         }, "--output"),
         new Option<JlinkTask>(true, (task, opt, arg) -> {
+            Path path = Paths.get(arg);
+            if (!Files.exists(path) || !Files.isDirectory(path)) {
+                throw taskHelper.newBadArgs("err.existing.image.must.exist");
+            }
+            task.options.existingImage = path;
+        }, "--post-process-path"),
+        new Option<JlinkTask>(true, (task, opt, arg) -> {
             if ("little".equals(arg)) {
                 task.options.endian = ByteOrder.LITTLE_ENDIAN;
             } else {
@@ -174,6 +186,7 @@
         Set<String> limitMods = new HashSet<>();
         Set<String> addMods = new HashSet<>();
         Path output;
+        Path existingImage;
         ByteOrder endian = ByteOrder.nativeOrder();
     }
 
@@ -195,10 +208,14 @@
                 optionsHelper.showPlugins(log, true);
                  return EXIT_OK;
             }
-            if (options.modulePath == null || options.modulePath.length == 0)
-                throw taskHelper.newBadArgs("err.modulepath.must.be.specified").showUsage(true);
-
-            createImage();
+            if (options.existingImage == null) {
+                if (options.modulePath == null || options.modulePath.length == 0) {
+                    throw taskHelper.newBadArgs("err.modulepath.must.be.specified").showUsage(true);
+                }
+                createImage();
+            } else {
+                postProcessOnly();
+            }
 
             return EXIT_OK;
         } catch (IOException | ResolutionException e) {
@@ -270,8 +287,8 @@
      * Jlink API entry point.
      */
     public static void createImage(JlinkConfiguration config,
-                                   PluginsConfiguration plugins)
-        throws Exception
+            PluginsConfiguration plugins)
+            throws Exception
     {
         Objects.requireNonNull(config);
         Objects.requireNonNull(config.getOutput());
@@ -288,14 +305,60 @@
         Path[] pluginsPath = new Path[config.getPluginpaths().size()];
         pluginsPath = config.getPluginpaths().toArray(pluginsPath);
 
-        ImageFileHelper imageHelper
-            = createImageFileHelper(config.getOutput(),
+        // First create the image provider
+        ImageProvider imageProvider
+                = createImageProvider(config.getOutput(),
                         finder,
                         checkAddMods(config.getModules(), config.getLimitmods()),
                         config.getLimitmods(),
+                        genBOMContent(config, plugins), config.getByteOrder());
+
+        // Then create the Plugin Stack
+        ImagePluginStack stack = ImagePluginConfiguration.
+                parseConfiguration(config.getOutput(), plugins,
                         TaskHelper.createPluginsLayer(pluginsPath),
-                        genBOMContent(config, plugins), config.getByteOrder());
-        imageHelper.createModularImage(plugins);
+                        genBOMContent(config, plugins));
+
+        //Ask the stack to proceed;
+        stack.operate(imageProvider);
+    }
+
+    private class PostProcessingImageHelper implements ImageProvider {
+
+        private ImageBuilderProvider provider;
+        @Override
+        public ExecutableImage retrieve(ImagePluginStack stack) throws IOException {
+            ExecutableImage img = null;
+            for (ImageBuilderProvider provider
+                    : ImagePluginProviderRepository.
+                    getImageBuilderProviders(optionsHelper.getPluginsLayer())) {
+                img = provider.canExecute(options.existingImage);
+                if (img != null) {
+                    this.provider = provider;
+                }
+            }
+            if (img == null) {
+                throw new IOException("Image in " + options.existingImage + " is not recognized");
+            }
+            return img;
+        }
+
+        @Override
+        public void storeLauncherArgs(ImagePluginStack stack, ExecutableImage image,
+                List<String> args) throws IOException {
+            provider.storeLauncherOptions(image, args);
+        }
+
+    }
+
+    private void postProcessOnly() throws Exception {
+        // Create the Plugin Stack
+        ImagePluginStack stack = ImagePluginConfiguration.
+                parseConfiguration(null, taskHelper.getPluginsConfig(),
+                        optionsHelper.getPluginsLayer(),
+                        genBOMContent());
+
+        stack.operate(new PostProcessingImageHelper());
     }
 
     private void createImage() throws Exception {
@@ -310,16 +373,22 @@
             throw taskHelper.newBadArgs("err.mods.must.be.specified", "--addmods")
                     .showUsage(true);
         }
-        ImageFileHelper imageHelper
-            = createImageFileHelper(options.output,
-                                    finder,
-                                    options.addMods,
-                                    options.limitMods,
-                                    optionsHelper.getPluginsLayer(),
-                                    genBOMContent(),
-                                    options.endian);
+        // First create the image provider
+        ImageProvider imageProvider
+                = createImageProvider(options.output, finder,
+                        options.addMods,
+                        options.limitMods,
+                        genBOMContent(),
+                        options.endian);
 
-        imageHelper.createModularImage(taskHelper.getPluginsConfig());
+        // Then create the Plugin Stack
+        ImagePluginStack stack = ImagePluginConfiguration.
+                parseConfiguration(options.output, taskHelper.getPluginsConfig(),
+                        optionsHelper.getPluginsLayer(),
+                        genBOMContent());
+
+        //Ask the stack to proceed
+        stack.operate(imageProvider);
     }
 
 
@@ -356,15 +425,13 @@
         return finder;
     }
 
-    private static ImageFileHelper createImageFileHelper(Path output,
-                                                         ModuleFinder finder,
-                                                         Set<String> addMods,
-                                                         Set<String> limitMods,
-                                                         Layer pluginsLayer,
-                                                         String bom,
-                                                         ByteOrder order)
-        throws IOException
-    {
+    private static ImageProvider createImageProvider(Path output,
+            ModuleFinder finder,
+            Set<String> addMods,
+            Set<String> limitMods,
+            String bom,
+            ByteOrder order)
+            throws IOException {
         if (addMods.isEmpty()) {
             if (limitMods.isEmpty()) {
                 throw new IllegalArgumentException("empty modules and limitmods");
@@ -372,13 +439,13 @@
             addMods = limitMods;
         }
         Configuration cf
-            = Configuration.resolve(finder,
-                    Layer.empty(),
-                    ModuleFinder.empty(),
-                    addMods);
+                = Configuration.resolve(finder,
+                        Layer.empty(),
+                        ModuleFinder.empty(),
+                        addMods);
         cf = cf.bind();
         Map<String, Path> mods = modulesToPath(finder, cf.descriptors());
-        return new ImageFileHelper(cf, mods, output, pluginsLayer, bom, order);
+        return new ImageHelper(cf, mods, output, bom, order);
     }
 
 
@@ -461,23 +528,19 @@
         return sb.toString();
     }
 
-    private static class ImageFileHelper {
+    private static class ImageHelper implements ImageProvider {
         final Path output;
         final Set<Archive> archives;
-        final Layer pluginsLayer;
         final String bom;
         final ByteOrder order;
 
-        ImageFileHelper(Configuration cf,
-                        Map<String, Path> modsPaths,
-                        Path output,
-                        Layer pluginsLayer,
-                        String bom,
-                        ByteOrder order)
-            throws IOException
-        {
+        ImageHelper(Configuration cf,
+                Map<String, Path> modsPaths,
+                Path output,
+                String bom,
+                ByteOrder order)
+                throws IOException {
             this.output = output;
-            this.pluginsLayer = pluginsLayer;
             this.bom = bom;
             archives = modsPaths.entrySet().stream()
                     .map(e -> newArchive(e.getKey(), e.getValue()))
@@ -485,12 +548,6 @@
             this.order = order;
         }
 
-        void createModularImage(PluginsConfiguration plugins) throws Exception {
-            ImagePluginStack pc = ImagePluginConfiguration.
-                    parseConfiguration(output, plugins, pluginsLayer, bom);
-            ImageFileCreator.create(archives, order, pc);
-        }
-
         private Archive newArchive(String module, Path path) {
             if (path.toString().endsWith(".jmod")) {
                 return new JmodArchive(module, path);
@@ -510,6 +567,16 @@
             }
             return null;
         }
+
+        @Override
+        public ExecutableImage retrieve(ImagePluginStack stack) throws IOException {
+            return ImageFileCreator.create(archives, order, stack);
+        }
+
+        @Override
+        public void storeLauncherArgs(ImagePluginStack stack, ExecutableImage image, List<String> args) throws IOException {
+            stack.getImageBuilder().storeJavaLauncherOptions(image, args);
+        }
     }
 
     private static enum Section {
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/TaskHelper.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/TaskHelper.java	Fri Oct 09 18:03:49 2015 +0200
@@ -57,9 +57,11 @@
 import jdk.tools.jlink.plugins.Jlink.PluginsConfiguration;
 import jdk.tools.jlink.plugins.Jlink.StackedPluginConfiguration;
 import jdk.tools.jlink.plugins.OnOffImageFilePluginProvider;
+import jdk.tools.jlink.plugins.OnOffPostProcessingPluginProvider;
 import jdk.tools.jlink.plugins.OnOffResourcePluginProvider;
 import jdk.tools.jlink.plugins.PluginProvider;
 import jdk.tools.jlink.plugins.PluginProvider.ORDER;
+import jdk.tools.jlink.plugins.PostProcessingPluginProvider;
 
 /**
  *
@@ -278,6 +280,8 @@
                         } else {
                             if (provider instanceof OnOffImageFilePluginProvider) {
                                 edefault = ((OnOffImageFilePluginProvider) provider).isEnabledByDefault();
+                            } else if (provider instanceof OnOffImageFilePluginProvider) {
+                                edefault = ((OnOffPostProcessingPluginProvider) provider).isEnabledByDefault();
                             }
                         }
                         if (edefault) {
@@ -358,7 +362,8 @@
         }
 
         private PluginsConfiguration getPluginsConfig() throws IOException {
-            List<StackedPluginConfiguration> lst = new ArrayList<>();
+            List<StackedPluginConfiguration> preProcessing = new ArrayList<>();
+            List<StackedPluginConfiguration> postProcessing = new ArrayList<>();
             for (Entry<PluginProvider, Map<String, String>> entry : plugins.entrySet()) {
                 PluginProvider provider = entry.getKey();
                 // User defined index?
@@ -383,7 +388,14 @@
                 }
                 Map<String, Object> config = new HashMap<>();
                 config.putAll(entry.getValue());
-                lst.add(new Jlink.StackedPluginConfiguration(provider.getName(), i, absolute, config));
+                StackedPluginConfiguration conf
+                        = new Jlink.StackedPluginConfiguration(provider.getName(),
+                                i, absolute, config);
+                if (provider instanceof PostProcessingPluginProvider) {
+                    postProcessing.add(conf);
+                } else {
+                    preProcessing.add(conf);
+                }
             }
 
             imgBuilder = imgBuilder == null ? DefaultImageBuilderProvider.NAME : imgBuilder;
@@ -394,7 +406,7 @@
                     builderConfig.putAll(conf);
                 }
             }
-            return new Jlink.PluginsConfiguration(lst,
+            return new Jlink.PluginsConfiguration(preProcessing, postProcessing,
                     new Jlink.PluginConfiguration(imgBuilder, builderConfig),
                     lastSorter);
         }
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java	Fri Oct 09 18:03:49 2015 +0200
@@ -45,6 +45,7 @@
 import java.util.stream.Stream;
 import jdk.tools.jlink.internal.Archive.Entry;
 import jdk.tools.jlink.internal.Archive.Entry.EntryType;
+import jdk.tools.jlink.plugins.ExecutableImage;
 import jdk.tools.jlink.plugins.ImageFilePool.ImageFile;
 import jdk.tools.jlink.plugins.ImageFilePool.ImageFile.ImageFileType;
 import jdk.tools.jlink.plugins.ResourcePool;
@@ -76,30 +77,30 @@
         this.plugins = plugins;
     }
 
-    public static ImageFileCreator create(Set<Archive> archives,
+    public static ExecutableImage create(Set<Archive> archives,
             ImagePluginStack plugins)
             throws IOException {
         return ImageFileCreator.create(archives, ByteOrder.nativeOrder(),
                 plugins);
     }
 
-    public static ImageFileCreator create(Set<Archive> archives,
+    public static ExecutableImage create(Set<Archive> archives,
             ByteOrder byteOrder)
             throws IOException {
         return ImageFileCreator.create(archives, byteOrder,
                 new ImagePluginStack(null));
     }
 
-    public static ImageFileCreator create(Set<Archive> archives,
-                                   ByteOrder byteOrder,
-                                   ImagePluginStack plugins)
-        throws IOException
+    public static ExecutableImage create(Set<Archive> archives,
+            ByteOrder byteOrder,
+            ImagePluginStack plugins)
+            throws IOException
     {
         ImageFileCreator image = new ImageFileCreator(plugins);
         image.readAllEntries(archives);
         // write to modular image
         image.writeImage(archives, byteOrder);
-        return image;
+        return plugins.getExecutableImage();
     }
 
     private void readAllEntries(Set<Archive> archives) {
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginConfiguration.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginConfiguration.java	Fri Oct 09 18:03:49 2015 +0200
@@ -37,15 +37,19 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import jdk.tools.jlink.plugins.ExecutableImage;
 
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageFilePlugin;
 import jdk.tools.jlink.plugins.ImageFilePluginProvider;
 import jdk.tools.jlink.plugins.ImageFilePool;
 import jdk.tools.jlink.plugins.Jlink;
+import jdk.tools.jlink.plugins.Jlink.StackedPluginConfiguration;
 import jdk.tools.jlink.plugins.ResourcePlugin;
 import jdk.tools.jlink.plugins.ResourcePluginProvider;
 import jdk.tools.jlink.plugins.PluginProvider;
+import jdk.tools.jlink.plugins.PostProcessingPlugin;
+import jdk.tools.jlink.plugins.PostProcessingPluginProvider;
 
 /**
  * Plugins configuration.
@@ -98,6 +102,9 @@
     private static final List<String> FILES_CATEGORIES = new ArrayList<>();
     private static final Map<String, Integer> FILES_RANGES = new HashMap<>();
 
+    private static final List<String> POST_PROCESSORS_CATEGORIES = new ArrayList<>();
+    private static final Map<String, Integer> POST_PROCESSORS_RANGES = new HashMap<>();
+
     private static final int RANGE_LENGTH = 5000;
 
     static {
@@ -105,7 +112,6 @@
         RESOURCES_CATEGORIES.add(PluginProvider.TRANSFORMER);
         RESOURCES_CATEGORIES.add(PluginProvider.SORTER);
         RESOURCES_CATEGORIES.add(PluginProvider.COMPRESSOR);
-        RESOURCES_CATEGORIES.add(PluginProvider.PACKAGER);
 
         int end = RANGE_LENGTH;
         for(String category : RESOURCES_CATEGORIES) {
@@ -117,13 +123,23 @@
         FILES_CATEGORIES.add(PluginProvider.TRANSFORMER);
         FILES_CATEGORIES.add(PluginProvider.SORTER);
         FILES_CATEGORIES.add(PluginProvider.COMPRESSOR);
-        FILES_CATEGORIES.add(PluginProvider.PACKAGER);
 
         int end2 = RANGE_LENGTH;
         for(String category : FILES_CATEGORIES) {
             FILES_RANGES.put(category, end2);
             end2 +=RANGE_LENGTH;
         }
+
+        POST_PROCESSORS_CATEGORIES.add(PluginProvider.VERIFIER);
+        POST_PROCESSORS_CATEGORIES.add(PluginProvider.PROCESSOR);
+        POST_PROCESSORS_CATEGORIES.add(PluginProvider.PACKAGER);
+
+        int end3 = RANGE_LENGTH;
+        for (String category : POST_PROCESSORS_CATEGORIES) {
+            POST_PROCESSORS_RANGES.put(category, end3);
+            end3 += RANGE_LENGTH;
+        }
+
     }
 
     private ImagePluginConfiguration() {
@@ -153,8 +169,10 @@
         // Validate stack
         Map<String, List<Integer>> resources = new HashMap<>();
         Map<String, List<Integer>> files = new HashMap<>();
-        List<String> seen = new ArrayList<>();
-        for (Jlink.StackedPluginConfiguration plug : plugins.getPluginsConfig()) {
+        List<OrderedPlugin> postProcessingPlugins = new ArrayList<>();
+        List<StackedPluginConfiguration> allPlugins = new ArrayList<>();
+
+        for (Jlink.StackedPluginConfiguration plug : plugins.getTransformerPluginsConfig()) {
             if (plug.getIndex() < 0) {
                 throw new Exception("Invalid index " + plug.getIndex() + " for "
                         + plug.getName());
@@ -166,12 +184,6 @@
             if (!isImageFileProvider(prov) && !isResourceProvider(prov)) {
                 throw new Exception("Invalid provider type " + prov);
             }
-
-            if (seen.contains(prov.getName())) {
-                throw new Exception("Plugin " + prov.getName()
-                        + " added more than once to stack ");
-            }
-            seen.add(prov.getName());
             Map<String, List<Integer>> map = isResourceProvider(prov) ? resources : files;
             List<Integer> lst = map.get(prov.getCategory());
             if (lst == null) {
@@ -194,10 +206,49 @@
                 throw new Exception(plug.getName() + ", a Plugin is already located at index " + index);
             }
             lst.add(index);
+            allPlugins.add(plug);
         }
 
-        for (Jlink.StackedPluginConfiguration prop : plugins.getPluginsConfig()) {
+        Map<String, List<Integer>> postprocessors = new HashMap<>();
+        for (Jlink.StackedPluginConfiguration plug : plugins.getPostProcessorPluginsConfig()) {
+            if (plug.getIndex() < 0) {
+                throw new Exception("Invalid index " + plug.getIndex() + " for "
+                        + plug.getName());
+            }
+            PluginProvider prov = providers.get(plug.getName());
+            if (prov == null) {
+                throw new Exception("Unknown plugin " + plug.getName());
+            }
+
+            if (!isPostProcessingProvider(prov)) {
+                throw new Exception("Invalid provider type " + prov);
+            }
+
+            List<Integer> lst = postprocessors.get(prov.getCategory());
+            if (lst == null) {
+                lst = new ArrayList<>();
+                postprocessors.put(prov.getCategory(), lst);
+            }
+            int index = getAbsoluteIndex(plug.getIndex(),
+                    prov.getCategory(),
+                    plug.isAbsoluteIndex(),
+                    POST_PROCESSORS_RANGES);
+
+            if (lst.contains(index)) {
+                throw new Exception(plug.getName() + ", a Plugin is already located at index " + index);
+            }
+            lst.add(index);
+            allPlugins.add(plug);
+        }
+
+        List<String> seen = new ArrayList<>();
+        for (StackedPluginConfiguration prop : allPlugins) {
             PluginProvider prov = providers.get(prop.getName());
+            if (seen.contains(prov.getName())) {
+                throw new Exception("Plugin " + prov.getName()
+                        + " added more than once to stack ");
+            }
+            seen.add(prov.getName());
             if (isResourceProvider(prov)) {
                 int index = getAbsoluteIndex(prop.getIndex(),
                         prov.getCategory(),
@@ -213,6 +264,15 @@
                             FILES_RANGES);
                     filePlugins.addAll(createOrderedPlugins(index, prop.getName(),
                             prop.getConfig(), pluginsLayer));
+                } else {
+                    if (isPostProcessingProvider(prov)) {
+                        int index = getAbsoluteIndex(prop.getIndex(),
+                                prov.getCategory(),
+                                prop.isAbsoluteIndex(),
+                                POST_PROCESSORS_RANGES);
+                        postProcessingPlugins.addAll(createOrderedPlugins(index, prop.getName(),
+                                prop.getConfig(), pluginsLayer));
+                    }
                 }
             }
         }
@@ -230,9 +290,12 @@
                     + plugins.getLastSorterPluginName());
         }
         List<ImageFilePlugin> filePluginsList = toPluginsList(filePlugins);
+
+        List<PostProcessingPlugin> postprocessorPluginsList = toPluginsList(postProcessingPlugins);
+
         ImageBuilder builder;
         if (outDir == null) {
-            // This should be the case for jimage only creation.
+            // This should be the case for jimage only creation or post-install.
             builder = new ImageBuilder() {
 
                 @Override
@@ -247,21 +310,40 @@
                 public DataOutputStream getJImageOutputStream() throws IOException {
                     throw new IOException("No directory setup to store files");
                 }
+
+                @Override
+                public ExecutableImage getExecutableImage() throws IOException {
+                    throw new UnsupportedOperationException("No directory setup to store files");
+                }
+
+                @Override
+                public void storeJavaLauncherOptions(ExecutableImage image, List<String> args) throws IOException {
+                    throw new UnsupportedOperationException("No directory setup to store files");
+                }
             };
         } else {
             String builderName = plugins.getImageBuilder() == null
                     ? DefaultImageBuilderProvider.NAME : plugins.getImageBuilder().getName();
             Map<String, Object> builderConfig = plugins.getImageBuilder() == null
                     ? Collections.emptyMap() : plugins.getImageBuilder().getConfig();
-            builder = ImagePluginProviderRepository.newImageBuilder(builderConfig,
+            Map<String, Object> map = getDefaultContent();
+            map.putAll(builderConfig);
+            builder = ImagePluginProviderRepository.newImageBuilder(map,
                     outDir,
                     builderName,
                     pluginsLayer);
         }
         return new ImagePluginStack(builder, resourcePluginsList,
-                lastSorter, filePluginsList, bom);
+                lastSorter, filePluginsList, postprocessorPluginsList, bom);
     }
 
+    private static Map<String, Object> getDefaultContent() {
+        Map<String, Object> map = new HashMap<>();
+        // Direct mapping from system properties
+        map.put(PluginProvider.PLATFORM_NAME_OPTION, System.getProperty("os.name"));
+
+        return map;
+    }
     private static boolean isResourceProvider(PluginProvider prov) {
         return prov instanceof ResourcePluginProvider;
     }
@@ -270,6 +352,10 @@
         return prov instanceof ImageFilePluginProvider;
     }
 
+    private static boolean isPostProcessingProvider(PluginProvider prov) {
+        return prov instanceof PostProcessingPluginProvider;
+    }
+
     private static int getAbsoluteIndex(int index, String category,
             boolean absolute, Map<String, Integer> ranges) throws Exception {
 
@@ -304,22 +390,14 @@
             ranges = RESOURCES_RANGES;
         } else if (isImageFileProvider(provider)) {
             ranges = FILES_RANGES;
+        } else if (isPostProcessingProvider(provider)) {
+            ranges = POST_PROCESSORS_RANGES;
         } else {
             throw new IllegalArgumentException("Unknown provider type");
         }
         return getRange(provider.getCategory(), ranges);
     }
 
-    /**
-     * Retrieve the range array (index 0 is range start, index 1 is range end)
-     * associated to a category.
-     * @param category The category for which the range is wanted.
-     * @return The range or null if the category is unknown.
-     */
-    public static Integer[] getFilesRange(String category) {
-        return getRange(category, FILES_RANGES);
-    }
-
     private static Integer[] getRange(String category, Map<String, Integer> ranges) {
         Objects.requireNonNull(category);
         Integer[] range = null;
@@ -332,27 +410,11 @@
         return range;
     }
 
-    /**
-     * Return a list of the names of the known plugin categories ordered from
-     * the smaller range start index to the bigger range start index.
-     * @return
-     */
-    public List<String> getOrderedResourcesCategories() {
-        return Collections.unmodifiableList(RESOURCES_CATEGORIES);
-    }
-
-    /**
-     * Return a list of the names of the known plugin categories ordered from
-     * the smaller range start index to the bigger range start index.
-     * @return
-     */
-    public List<String> getOrderedFilesCategories() {
-        return Collections.unmodifiableList(FILES_CATEGORIES);
-    }
-
     private static List<OrderedPlugin> createOrderedPlugins(int index,
             String name, Map<String, Object> config, Layer pluginsLayer) throws IOException {
-        Plugin[] plugins = ImagePluginProviderRepository.newPlugins(config,
+        Map<String, Object> map = getDefaultContent();
+        map.putAll(config);
+        Plugin[] plugins = ImagePluginProviderRepository.newPlugins(map,
                 name, pluginsLayer);
         List<OrderedPlugin> ordered = new ArrayList<>();
         if (plugins != null) {
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java	Fri Oct 09 18:03:49 2015 +0200
@@ -29,7 +29,6 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -41,9 +40,11 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import jdk.internal.jimage.decompressor.Decompressor;
+import jdk.tools.jlink.plugins.ExecutableImage;
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageFilePlugin;
 import jdk.tools.jlink.plugins.ImageFilePool.ImageFile;
+import jdk.tools.jlink.plugins.PostProcessingPlugin;
 import jdk.tools.jlink.plugins.ResourcePlugin;
 import jdk.tools.jlink.plugins.ResourcePool;
 import jdk.tools.jlink.plugins.ResourcePool.Resource;
@@ -55,6 +56,13 @@
  */
 public final class ImagePluginStack {
 
+    public interface ImageProvider {
+        ExecutableImage retrieve(ImagePluginStack stack) throws IOException;
+
+        public void storeLauncherArgs(ImagePluginStack stack, ExecutableImage image,
+                List<String> args) throws IOException;
+    }
+
     private static final class OrderedResourcePool extends ResourcePoolImpl {
 
         private final List<Resource> orderedList = new ArrayList<>();
@@ -156,6 +164,7 @@
     private final Plugin lastSorter;
     private final List<ResourcePlugin> resourcePlugins = new ArrayList<>();
     private final List<ImageFilePlugin> filePlugins = new ArrayList<>();
+    private final List<PostProcessingPlugin> postProcessingPlugins = new ArrayList<>();
     private final List<ResourcePrevisitor> resourcePrevisitors = new ArrayList<>();
 
     private final ImageBuilder imageBuilder;
@@ -163,13 +172,15 @@
     private final String bom;
 
     public ImagePluginStack(String bom) {
-        this(null, Collections.emptyList(), null, Collections.emptyList(), null);
+        this(null, Collections.emptyList(), null, Collections.emptyList(),
+                Collections.emptyList(), null);
     }
 
     public ImagePluginStack(ImageBuilder imageBuilder,
             List<ResourcePlugin> resourcePlugins,
             Plugin lastSorter,
             List<ImageFilePlugin> filePlugins,
+            List<PostProcessingPlugin> postprocessingPlugins,
             String bom) {
         Objects.requireNonNull(resourcePlugins);
         Objects.requireNonNull(filePlugins);
@@ -185,14 +196,42 @@
             Objects.requireNonNull(p);
             this.filePlugins.add(p);
         }
+        for (PostProcessingPlugin p : postprocessingPlugins) {
+            Objects.requireNonNull(p);
+            this.postProcessingPlugins.add(p);
+        }
         this.imageBuilder = imageBuilder;
         this.bom = bom;
     }
 
+    public void operate(ImageProvider provider) throws Exception {
+        ExecutableImage img = provider.retrieve(this);
+        List<String> arguments;
+        // Could be autocloseable but not right behavior
+        // with InterruptedException
+        ProcessingManagerImpl manager = new ProcessingManagerImpl(img);
+        arguments = new ArrayList<>();
+        try {
+            for (PostProcessingPlugin plugin : postProcessingPlugins) {
+                List<String> lst = plugin.process(manager);
+                if (lst != null) {
+                    arguments.addAll(lst);
+                }
+            }
+        } finally {
+            manager.close();
+        }
+        provider.storeLauncherArgs(this, img, arguments);
+    }
+
     public DataOutputStream getJImageFileOutputStream() throws IOException {
         return imageBuilder.getJImageOutputStream();
     }
 
+    public ImageBuilder getImageBuilder() {
+        return imageBuilder;
+    }
+
     /**
      * Resource Plugins stack entry point. All resources are going through all the
      * plugins.
@@ -320,4 +359,8 @@
         RetrieverImpl retriever = new RetrieverImpl(resources, writer);
         imageBuilder.storeFiles(current, removed, bom, retriever);
     }
+
+    public ExecutableImage getExecutableImage() throws IOException {
+        return imageBuilder.getExecutableImage();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ProcessingManagerImpl.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.internal;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import jdk.tools.jlink.plugins.ExecutableImage;
+import jdk.tools.jlink.plugins.ProcessingManager;
+
+/**
+ * A process manager.
+ */
+class ProcessingManagerImpl implements ProcessingManager {
+
+    private static final class StreamReader implements Runnable {
+
+        private final InputStream in;
+        private final OutputStream out;
+        private final FutureTask<Void> processingTask = new FutureTask<>(this, null);
+
+        private StreamReader(InputStream in, OutputStream out) {
+            this.in = in;
+            this.out = out;
+        }
+
+        @Override
+        public void run() {
+            try (BufferedInputStream is = new BufferedInputStream(in)) {
+                byte[] buf = new byte[1024];
+                int len = 0;
+                while ((len = is.read(buf)) > 0 && !Thread.interrupted()) {
+                    out.write(buf, 0, len);
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                try {
+                    out.flush();
+                } catch (IOException e) {
+                }
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+
+        final public Future<Void> process() {
+            Thread t = new Thread(() -> {
+                processingTask.run();
+            });
+            t.setDaemon(true);
+            t.start();
+
+            return processingTask;
+        }
+    }
+
+    public static final class RunningProcessImpl implements RunningProcess {
+
+        private final ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream();
+        private final ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
+        private final Future<Void> outTask;
+        private final Future<Void> errTask;
+        private final Process p;
+
+        @Override
+        public int getExitCode() throws InterruptedException {
+            return p.waitFor();
+        }
+
+        @Override
+        public String getStdout() throws InterruptedException, ExecutionException {
+            outTask.get();
+            return stdoutBuffer.toString();
+
+        }
+
+        @Override
+        public String getStderr() throws InterruptedException, ExecutionException {
+            errTask.get();
+            return stderrBuffer.toString();
+        }
+
+        @Override
+        public void kill() {
+            p.destroyForcibly();
+        }
+
+        private RunningProcessImpl(ProcessBuilder builder) throws IOException {
+            p = builder.start();
+            StreamReader outReader = new StreamReader(p.getInputStream(), stdoutBuffer);
+            StreamReader errReader = new StreamReader(p.getErrorStream(), stderrBuffer);
+            outTask = outReader.process();
+            errTask = errReader.process();
+        }
+
+        @Override
+        public Process getProcess() {
+            return p;
+        }
+
+        void close() {
+            if (p.isAlive()) {
+                kill();
+            }
+        }
+    }
+
+    private final class ProcessingSessionImpl implements ProcessingSession {
+
+        private boolean closed;
+        private final Path dir;
+        private final String name;
+        private final List<RunningProcessImpl> processes = new ArrayList<>();
+
+        private ProcessingSessionImpl(String name, Path dir) {
+            this.name = name;
+            this.dir = dir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public Path getStorage() {
+            return dir;
+        }
+
+        @Override
+        public RunningProcess newRunningProcess(ProcessBuilder builder) throws IOException {
+            ProcessBuilder pb = builder.directory(dir.toFile());
+            RunningProcessImpl p = new RunningProcessImpl(pb);
+            processes.add(p);
+            return p;
+        }
+
+        @Override
+        public RunningProcess newImageProcess(List<String> args) throws IOException {
+            RunningProcessImpl p = new RunningProcessImpl(newJavaProcessBuilder(args));
+            processes.add(p);
+            return p;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (closed) {
+                throw new IllegalArgumentException("Session " + name
+                        + " already closed");
+            }
+            closed = true;
+            for (RunningProcessImpl r : processes) {
+                r.close();
+            }
+        }
+
+        private ProcessBuilder newJavaProcessBuilder(List<String> args) throws IOException {
+            List<String> javaArgs = new ArrayList<>();
+            javaArgs.addAll(img.getExecutionArgs());
+            javaArgs.addAll(args);
+            ProcessBuilder builder = new ProcessBuilder().command(javaArgs).
+                    directory(dir.toFile());
+            return builder;
+        }
+
+    }
+
+    private final Path tmp;
+    private final Map<String, ProcessingSession> sessions = new HashMap<>();
+    private final ExecutableImage img;
+
+    ProcessingManagerImpl(ExecutableImage img) throws IOException {
+        Objects.requireNonNull(img);
+        this.img = img;
+        String dir = "jlink_tmp_" + System.currentTimeMillis();
+        Path dirPath = img.getHome().resolve(dir);
+        tmp = Files.createDirectory(dirPath);
+    }
+
+    @Override
+    public ProcessingSession newSession(String name) throws IOException {
+        String id = name.replaceAll(" ", "_") + System.currentTimeMillis();
+        Path dirPath = tmp.resolve(id);
+        Files.createDirectory(dirPath);
+        return new ProcessingSessionImpl(name, tmp);
+    }
+
+    @Override
+    public ExecutableImage getImage() {
+        return img;
+    }
+
+    public void close() throws IOException {
+        for (ProcessingSession session : sessions.values()) {
+            session.close();
+        }
+        Files.walkFileTree(tmp, new FileVisitor<Path>() {
+
+            @Override
+            public FileVisitResult preVisitDirectory(Path t, BasicFileAttributes bfa) throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(Path t, BasicFileAttributes bfa) throws IOException {
+                Files.deleteIfExists(t);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFileFailed(Path t, IOException ioe) throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path t, IOException ioe) throws IOException {
+                Files.deleteIfExists(t);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/CmdPostProcessingPluginProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.plugins;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A PostProcessing Plugin provider that creates command line oriented plugins.
+ */
+public abstract class CmdPostProcessingPluginProvider extends PostProcessingPluginProvider
+        implements CmdPluginProvider<PostProcessingPlugin> {
+
+    protected CmdPostProcessingPluginProvider(String name, String description) {
+        super(name, description);
+    }
+
+    @Override
+    public abstract PostProcessingPlugin[] newPlugins(String[] arguments,
+            Map<String, String> otherOptions) throws IOException;
+
+    // Must be implemented, an abstract method can't be implemented with a default method
+    @Override
+    public PostProcessingPlugin[] newPlugins(Map<String, Object> conf) throws IOException {
+        return CmdPluginProvider.super.newPlugins(conf);
+    }
+}
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/DefaultImageBuilder.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/DefaultImageBuilder.java	Fri Oct 09 18:03:49 2015 +0200
@@ -44,6 +44,7 @@
 import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -62,9 +63,29 @@
  */
 public class DefaultImageBuilder implements ImageBuilder {
 
+    /**
+     * The default java executable Image.
+     */
+    public static class DefaultExecutableImage extends ExecutableImage {
+
+        public DefaultExecutableImage(Path home, Set<String> modules) {
+            super(home, modules, createArgs(home));
+        }
+
+        private static List<String> createArgs(Path home) {
+            Objects.requireNonNull(home);
+            List<String> javaArgs = new ArrayList<>();
+            javaArgs.add(home.resolve("bin").
+                    resolve(DefaultImageBuilderProvider.
+                            getJavaProcessName()).toString());
+            return javaArgs;
+        }
+    }
+
     private final Path root;
     private final Path mdir;
     private final boolean genBom;
+    private Set<String> modules;
 
     public DefaultImageBuilder(Map<String, Object> properties, Path root) throws IOException {
         Objects.requireNonNull(root);
@@ -130,7 +151,7 @@
         for (ImageFile f : files.getFiles()) {
             accept(f);
         }
-        Set<String> modules = retriever.getModules();
+        modules = retriever.getModules();
         storeFiles(modules, bom);
 
         if (Files.getFileStore(root).supportsFileAttributeView(PosixFileAttributeView.class)) {
@@ -172,9 +193,11 @@
                     StringBuilder sb = new StringBuilder();
                     sb.append("#!/bin/sh")
                             .append("\n");
+                    sb.append("JLINK_VM_OPTIONS=")
+                            .append("\n");
                     sb.append("DIR=`dirname $0`")
                             .append("\n");
-                    sb.append("$DIR/java -m ")
+                    sb.append("$DIR/java $JLINK_VM_OPTIONS -m ")
                             .append(module).append('/')
                             .append(mainClass.get())
                             .append(" $@\n");
@@ -299,4 +322,49 @@
             output.write(content);
         }
     }
+
+    @Override
+    public ExecutableImage getExecutableImage() {
+        return new DefaultExecutableImage(root, modules);
+    }
+
+    @Override
+    public void storeJavaLauncherOptions(ExecutableImage img, List<String> args) throws IOException {
+        patchScripts(img, args);
+    }
+
+    // This is experimental, we should get rid-off the scripts in a near future
+    static void patchScripts(ExecutableImage img, List<String> args) throws IOException {
+        Objects.requireNonNull(args);
+        if (!args.isEmpty()) {
+            Files.find(img.getHome().resolve("bin"), 2, (path, attrs) -> {
+                return img.getModules().contains(path.getFileName().toString());
+            }).forEach((p) -> {
+                try {
+                    String pattern = "JLINK_VM_OPTIONS=";
+                    byte[] content = Files.readAllBytes(p);
+                    String str = new String(content, StandardCharsets.UTF_8);
+                    int index = str.indexOf(pattern);
+                    StringBuilder builder = new StringBuilder();
+                    if (index != -1) {
+                        builder.append(str.substring(0, index)).
+                                append(pattern);
+                        for (String s : args) {
+                            builder.append(s).append(" ");
+                        }
+                        String remain = str.substring(index + pattern.length());
+                        builder.append(remain);
+                        str = builder.toString();
+                        try (BufferedWriter writer = Files.newBufferedWriter(p,
+                                StandardCharsets.ISO_8859_1,
+                                StandardOpenOption.WRITE)) {
+                            writer.write(str);
+                        }
+                    }
+                } catch (IOException ex) {
+                    throw new RuntimeException(ex);
+                }
+            });
+        }
+    }
 }
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/DefaultImageBuilderProvider.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/DefaultImageBuilderProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -24,12 +24,18 @@
  */
 package jdk.tools.jlink.plugins;
 
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
 import jdk.tools.jlink.internal.plugins.PluginsResourceBundle;
+import static jdk.tools.jlink.plugins.DefaultImageBuilder.isWindows;
 
 /**
  * Default Image Builder provider.
@@ -75,4 +81,45 @@
         }
         return new DefaultImageBuilder(config, imageOutDir);
     }
+
+    @Override
+    public ExecutableImage canExecute(Path root) {
+        if (Files.exists(root.resolve("bin").resolve(getJavaProcessName()))) {
+            return new DefaultImageBuilder.DefaultExecutableImage(root,
+                    retrieveModules(root));
+        }
+        return null;
+    }
+
+    static String getJavaProcessName() {
+        return isWindows() ? "java.exe" : "java";
+    }
+
+    private static Set<String> retrieveModules(Path root) {
+        Path releaseFile = root.resolve("release");
+        Set<String> modules = new HashSet<>();
+        if (Files.exists(releaseFile)) {
+            Properties release = new Properties();
+            try (FileInputStream fi = new FileInputStream(releaseFile.toFile())) {
+                release.load(fi);
+            } catch (IOException ex) {
+                System.err.println("Cant read release file " + ex);
+            }
+            String mods = release.getProperty("MODULES");
+            if (mods != null) {
+                String[] arr = mods.split(",");
+                for (String m : arr) {
+                    modules.add(m);
+                }
+
+            }
+        }
+        return modules;
+    }
+
+    @Override
+    public void storeLauncherOptions(ExecutableImage image, List<String> arguments)
+            throws IOException {
+        DefaultImageBuilder.patchScripts(image, arguments);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ExecutableImage.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.plugins;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An executable Image.
+ */
+public class ExecutableImage {
+
+    private final Path home;
+    private final List<String> args;
+    private final Set<String> modules;
+
+    public ExecutableImage(Path home, Set<String> modules,
+            List<String> args) {
+        Objects.requireNonNull(home);
+        Objects.requireNonNull(args);
+        if (!Files.exists(home)) {
+            throw new IllegalArgumentException("Invalid image home");
+        }
+        this.home = home;
+        this.modules = Collections.unmodifiableSet(modules);
+        this.args = Collections.unmodifiableList(args);
+    }
+
+    public Path getHome() {
+        return home;
+    }
+
+    /**
+     * The names of the modules located in the image.
+     *
+     * @return The set of modules.
+     */
+    public Set<String> getModules() {
+        return modules;
+    }
+
+    public List<String> getExecutionArgs() {
+        return args;
+    }
+}
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ImageBuilder.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ImageBuilder.java	Fri Oct 09 18:03:49 2015 +0200
@@ -80,4 +80,21 @@
      * @throws java.io.IOException
      */
     public DataOutputStream getJImageOutputStream() throws IOException;
+
+    /**
+     * Gets executable image.
+     *
+     * @return The executable image.
+     * @throws java.io.IOException
+     */
+    public ExecutableImage getExecutableImage() throws IOException;
+
+    /**
+     * Store the options that would have bee nadded by the post processing
+     * @param image
+     * @param args
+     * @throws java.io.IOException
+     */
+    public void storeJavaLauncherOptions(ExecutableImage image, List<String> args)
+            throws IOException;
 }
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ImageBuilderProvider.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ImageBuilderProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -26,6 +26,7 @@
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -60,6 +61,24 @@
             throws IOException;
 
     /**
+     * Image builder provider can execute the image located in the passed dir
+     *
+     * @param root The image directory.
+     * @return An ExecutableImage if runnable, otherwise null.
+     */
+    public ExecutableImage canExecute(Path root);
+
+    /**
+     * Ask the provider to store some options in the image launcher(s).
+     *
+     * @param image
+     * @param arguments The arguments to add to the launcher.
+     * @throws java.io.IOException
+     */
+    public void storeLauncherOptions(ExecutableImage image, List<String> arguments)
+            throws IOException;
+
+    /**
      * Options to configure the image builder.
      *
      * @return The option name / description mapping
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/Jlink.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/Jlink.java	Fri Oct 09 18:03:49 2015 +0200
@@ -90,7 +90,7 @@
     }
 
     /**
-     * A plugin located inside the stack of plugins. Such plugin as an index in
+     * A plugin located inside a stack of plugins. Such plugin as an index in
      * the stack.
      */
     public static final class StackedPluginConfiguration extends PluginConfiguration {
@@ -153,7 +153,8 @@
      */
     public static final class PluginsConfiguration {
 
-        private final List<StackedPluginConfiguration> pluginsConfig;
+        private final List<StackedPluginConfiguration> transformerPluginsConfig;
+        private final List<StackedPluginConfiguration> processorPluginsConfig;
         private final PluginConfiguration imageBuilder;
         private final String lastSorterPluginName;
 
@@ -161,42 +162,59 @@
          * Empty plugins configuration.
          */
         public PluginsConfiguration() {
-            this(Collections.emptyList(), null);
+            this(Collections.emptyList(), Collections.emptyList(), null);
         }
 
         /**
          * Plugins configuration.
          *
-         * @param pluginsConfig List of plugins configuration.
+         * @param transformerPluginsConfig List of transformer plugins
+         * configuration.
+         * @param processorPluginsConfig List of processor plugins
+         * configuration.
          * @param imageBuilder Image builder (null default builder).
          */
-        public PluginsConfiguration(List<StackedPluginConfiguration> pluginsConfig,
+        public PluginsConfiguration(List<StackedPluginConfiguration> transformerPluginsConfig,
+                List<StackedPluginConfiguration> processorPluginsConfig,
                 PluginConfiguration imageBuilder) {
-            this(pluginsConfig, imageBuilder, null);
+            this(transformerPluginsConfig, processorPluginsConfig, imageBuilder, null);
         }
 
         /**
          * Plugins configuration with a last sorter. No sorting can occur after
          * the last sorter plugin.
          *
-         * @param pluginsConfig List of plugins configuration.
+         * @param transformerPluginsConfig List of transformer plugins
+         * configuration.
+         * @param processorPluginsConfig List of processor plugins
+         * configuration.
          * @param imageBuilder Image builder (null default builder).
          * @param lastSorterPluginName Name of last sorter plugin, no sorting
          * can occur after it.
          */
-        public PluginsConfiguration(List<StackedPluginConfiguration> pluginsConfig,
+        public PluginsConfiguration(List<StackedPluginConfiguration> transformerPluginsConfig,
+                List<StackedPluginConfiguration> processorPluginsConfig,
                 PluginConfiguration imageBuilder, String lastSorterPluginName) {
-            this.pluginsConfig = pluginsConfig == null ? Collections.emptyList()
-                    : pluginsConfig;
+            this.transformerPluginsConfig = transformerPluginsConfig == null ? Collections.emptyList()
+                    : transformerPluginsConfig;
+            this.processorPluginsConfig = processorPluginsConfig == null ? Collections.emptyList()
+                    : processorPluginsConfig;
             this.imageBuilder = imageBuilder;
             this.lastSorterPluginName = lastSorterPluginName;
         }
 
         /**
-         * @return the pluginsConfig
+         * @return the transformer pluginsConfig
          */
-        public List<StackedPluginConfiguration> getPluginsConfig() {
-            return pluginsConfig;
+        public List<StackedPluginConfiguration> getTransformerPluginsConfig() {
+            return transformerPluginsConfig;
+        }
+
+        /**
+         * @return the post processors pluginsConfig
+         */
+        public List<StackedPluginConfiguration> getPostProcessorPluginsConfig() {
+            return processorPluginsConfig;
         }
 
         /**
@@ -218,7 +236,10 @@
             StringBuilder builder = new StringBuilder();
             builder.append("imagebuilder=").append(imageBuilder).append("\n");
             StringBuilder pluginsBuilder = new StringBuilder();
-            for (PluginConfiguration p : pluginsConfig) {
+            for (PluginConfiguration p : transformerPluginsConfig) {
+                pluginsBuilder.append(p).append(",");
+            }
+            for (PluginConfiguration p : processorPluginsConfig) {
                 pluginsBuilder.append(p).append(",");
             }
             builder.append("plugins=").append(pluginsBuilder).append("\n");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/OnOffPostProcessingPluginProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.plugins;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ *
+ * Abstract class for command line PostProcessing provider that requires ON/OFF
+ * support. Plugin created by this provider can be enabled by default (enabled
+ * although no option is provided to the command line).
+ */
+public abstract class OnOffPostProcessingPluginProvider extends PostProcessingPluginProvider
+        implements OnOffPluginProvider<PostProcessingPlugin> {
+
+    public OnOffPostProcessingPluginProvider(String name, String description) {
+        super(name, description);
+    }
+
+    // Must be implemented, an abstract method can't be implemented with a default method
+    @Override
+    public PostProcessingPlugin[] newPlugins(Map<String, Object> conf) throws IOException {
+        PostProcessingPlugin[] arr = OnOffPluginProvider.super.newPlugins(conf);
+        arr = arr == null ? new PostProcessingPlugin[0] : arr;
+        return arr;
+    }
+}
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PluginProvider.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PluginProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -39,21 +39,31 @@
  *
  * Order of known categories are:
  * <ol>
- * <li>FILTER: Filter in/out resources.</li>
- * <li>TRANSFORMER: Transform resources (eg: refactoring, bytecode
+ * <li>FILTER: Filter in/out resources or files.</li>
+ * <li>TRANSFORMER: Transform resources or files(eg: refactoring, bytecode
  * manipulation).</li>
- * <li>SORTER: Sort resources.</li>
- * <li>COMPRESSOR: Compress resources.</li>
+ * <li>SORTER: Sort resources within the resource container.</li>
+ * <li>COMPRESSOR: Compress resource within the resouce containers.</li>
+ * <li>VERIFIER: Does some image verification.</li>
+ * <li>PROCESSOR: Does some post processing on image.</li>
  * <li>PACKAGER: Final processing</li>
  * </ol>
  */
 public abstract class PluginProvider {
 
+    /**
+     * This option is present in the configuration passed at Plugin creation
+     * time.
+     */
+    public static final String PLATFORM_NAME_OPTION = "jlink.platform";
+
     public static final String COMPRESSOR = "compressor";
     public static final String SORTER = "sorter";
     public static final String TRANSFORMER = "transformer";
     public static final String FILTER = "filter";
     public static final String PACKAGER = "packager";
+    public static final String PROCESSOR = "processor";
+    public static final String VERIFIER = "verifier";
 
     public static enum ORDER {
         FIRST,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PostProcessingPlugin.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.plugins;
+
+import java.util.List;
+
+/**
+ * Implement this interface to develop your own plugin.
+ * PostProcessing can
+ * modify the image content.
+ */
+public interface PostProcessingPlugin extends Plugin {
+
+    /**
+     * Post processing on existing image.
+     *
+     * @param manager The instance in charge to manage remote processes the
+     * plugin could have launched.
+     * @return The list of arguments to add to launchers.
+     * @throws Exception
+     */
+    public List<String> process(ProcessingManager manager) throws Exception;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/PostProcessingPluginProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.plugins;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A PostProcessing Plugin provider.
+ */
+public abstract class PostProcessingPluginProvider extends PluginProvider {
+
+    protected PostProcessingPluginProvider(String name, String description) {
+        super(name, description);
+    }
+
+    @Override
+    public abstract PostProcessingPlugin[] newPlugins(Map<String, Object> config)
+            throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/plugins/ProcessingManager.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.plugins;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A process execution manager.
+ */
+public interface ProcessingManager {
+
+    /**
+     * A running process.
+     */
+    public interface RunningProcess {
+
+        public int getExitCode() throws InterruptedException;
+
+        public String getStdout() throws InterruptedException, ExecutionException;
+
+        public String getStderr() throws InterruptedException, ExecutionException;
+
+        public void kill();
+
+        public Process getProcess();
+    }
+
+    /**
+     * A Session. Each session has a private storage automatically cleared at
+     * close time. From a session one can start new processes. Direct execution
+     * of Java image is also offered.
+     */
+    public interface ProcessingSession {
+
+        public String getName();
+
+        /**
+         * Private storage.
+         *
+         * @return
+         */
+        public Path getStorage();
+
+        /**
+         * Run a process. The working directory is the private storage.
+         *
+         * @param builder
+         * @return
+         * @throws IOException
+         */
+        public RunningProcess newRunningProcess(ProcessBuilder builder) throws IOException;
+
+        /**
+         * Run the image. The working directory is the private storage.
+         *
+         * @param args
+         * @return
+         * @throws IOException
+         */
+        public RunningProcess newImageProcess(List<String> args) throws IOException;
+
+        public void close() throws IOException;
+    }
+
+    /**
+     * Create a new ProcessingSession.
+     *
+     * @param name Session name.
+     * @return A new Session.
+     * @throws java.io.IOException
+     */
+    public ProcessingSession newSession(String name) throws IOException;
+
+    /**
+     * Return the current image.
+     *
+     * @return
+     */
+    public ExecutableImage getImage();
+}
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties	Fri Oct 09 18:01:08 2015 +0200
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties	Fri Oct 09 18:03:49 2015 +0200
@@ -37,6 +37,9 @@
 \  --endian <little|big>                Byte order of the generated jimage. \
 By default native order is used.
 
+main.opt.post-process-path=\
+\  --post-process-path <path to image>  Apply post processing to an existing image.
+
 main.msg.bug=\
 An exception has occurred in jlink. \
 Please file a bug at the Java Bug Database (http://bugreport.java.com/bugreport/) \
@@ -50,6 +53,7 @@
 err.path.not.found=path not found: {0}
 err.path.not.valid=invalid path: {0}
 err.dir.not.empty=not empty: {0}
+err.existing.image.must.exist=existing image doesn't exists or is not a directory
 err.file.not.found=cannot find file: {0}
 err.file.error=cannot access file: {0}
 err.file.already.exists=file already exists: {0}
--- a/test/jdk/jigsaw/tools/jlink/ImageFileCreatorTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/ImageFileCreatorTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -38,6 +38,7 @@
 import jdk.tools.jlink.internal.Archive;
 import jdk.tools.jlink.internal.ImageFileCreator;
 import jdk.tools.jlink.internal.ImagePluginStack;
+import jdk.tools.jlink.plugins.ExecutableImage;
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageFilePool;
 import jdk.tools.jlink.plugins.ResourcePool;
@@ -208,10 +209,20 @@
             public void storeFiles(ImageFilePool files, List<ImageFilePool.ImageFile> removed,
                     String bom, ImageBuilder.ResourceRetriever retriever) throws IOException {
             }
+
+            @Override
+            public ExecutableImage getExecutableImage() throws IOException {
+                return null;
+            }
+
+            @Override
+            public void storeJavaLauncherOptions(ExecutableImage image, List<String> args) throws IOException {
+
+            }
         };
 
         ImagePluginStack stack = new ImagePluginStack(noopBuilder, Collections.emptyList(),
-                null, Collections.emptyList(), "");
+                null, Collections.emptyList(), Collections.emptyList(), "");
 
         ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack);
     }
--- a/test/jdk/jigsaw/tools/jlink/IntegrationTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/IntegrationTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -44,6 +44,9 @@
 import jdk.tools.jlink.plugins.Jlink.PluginsConfiguration;
 import jdk.tools.jlink.plugins.Jlink.StackedPluginConfiguration;
 import jdk.tools.jlink.plugins.PluginProvider;
+import jdk.tools.jlink.plugins.PostProcessingPlugin;
+import jdk.tools.jlink.plugins.PostProcessingPluginProvider;
+import jdk.tools.jlink.plugins.ProcessingManager;
 
 import tests.Helper;
 import tests.JImageGenerator;
@@ -65,11 +68,51 @@
 public class IntegrationTest {
 
     static {
-        ImagePluginProviderRepository.registerPluginProvider(new MyProvider());
+        ImagePluginProviderRepository.registerPluginProvider(new MyProvider(MyProvider.NAME + "0"));
+        ImagePluginProviderRepository.registerPluginProvider(new MyProvider(MyProvider.NAME + "1"));
+        ImagePluginProviderRepository.registerPluginProvider(new MyProvider(MyProvider.NAME + "2"));
+        ImagePluginProviderRepository.registerPluginProvider(new MyProvider(MyProvider.NAME + "3"));
+        ImagePluginProviderRepository.registerPluginProvider(new MyProvider(MyProvider.NAME + "4"));
+        ImagePluginProviderRepository.registerPluginProvider(new MyPostProcessorProvider());
     }
 
     private static final List<Integer> ordered = new ArrayList<>();
 
+    public static class MyPostProcessorProvider extends PostProcessingPluginProvider {
+
+        public class MyPostProcessor implements PostProcessingPlugin {
+
+            @Override
+            public List<String> process(ProcessingManager manager) throws Exception {
+                Files.createFile(manager.getImage().getHome().resolve("toto.txt"));
+                return null;
+            }
+
+            @Override
+            public String getName() {
+                return NAME;
+            }
+
+        }
+
+        public static final String NAME = "mypostprocessor";
+
+        public MyPostProcessorProvider() {
+            super(NAME, "");
+        }
+
+        @Override
+        public PostProcessingPlugin[] newPlugins(Map<String, Object> config) throws IOException {
+            return new PostProcessingPlugin[]{new MyPostProcessor()};
+        }
+
+        @Override
+        public String getCategory() {
+            return PROCESSOR;
+        }
+
+    }
+
     public static class MyProvider extends ImageFilePluginProvider {
         public class MyPlugin1 implements ImageFilePlugin {
 
@@ -97,8 +140,8 @@
         static final String NAME = "myprovider";
         static final String INDEX = "INDEX";
 
-        public MyProvider() {
-            super(NAME, "");
+        public MyProvider(String name) {
+            super(name, "");
         }
 
         @Override
@@ -108,7 +151,7 @@
 
         @Override
         public String getCategory() {
-            return PluginProvider.PACKAGER;
+            return PluginProvider.TRANSFORMER;
         }
     }
 
@@ -193,6 +236,7 @@
                 modulePaths, mods, limits, null);
 
         List<Jlink.StackedPluginConfiguration> lst = new ArrayList<>();
+        List<Jlink.StackedPluginConfiguration> post = new ArrayList<>();
 
         //Strip debug
         {
@@ -211,13 +255,20 @@
                     = new StackedPluginConfiguration("compress-resources", 0, false, config1);
             lst.add(compress);
         }
+        // Post processor
+        {
+            Map<String, Object> config1 = new HashMap<>();
+            StackedPluginConfiguration postprocessor
+                    = new StackedPluginConfiguration(MyPostProcessorProvider.NAME, 0, false, config1);
+            post.add(postprocessor);
+        }
         // Image builder
         Map<String, Object> config1 = new HashMap<>();
         config1.put("genbom", "true");
         PluginConfiguration imgBuilder
                 = new Jlink.PluginConfiguration("default-image-builder", config1);
         PluginsConfiguration plugins
-                = new Jlink.PluginsConfiguration(lst, imgBuilder);
+                = new Jlink.PluginsConfiguration(lst, post, imgBuilder);
 
         jlink.build(config, plugins);
 
@@ -238,6 +289,10 @@
             throw new AssertionError("release not generated");
         }
 
+        if (!Files.exists(output.resolve("toto.txt"))) {
+            throw new AssertionError("Post processing not called");
+        }
+
     }
 
     private static void testOrder() throws Exception {
@@ -261,7 +316,7 @@
             Map<String, Object> config1 = new HashMap<>();
             config1.put(MyProvider.INDEX, 2);
             StackedPluginConfiguration compress
-                    = new StackedPluginConfiguration(MyProvider.NAME, 0, false, config1);
+                    = new StackedPluginConfiguration(MyProvider.NAME + "0", 0, false, config1);
             lst.add(compress);
         }
 
@@ -270,7 +325,7 @@
             Map<String, Object> config1 = new HashMap<>();
             config1.put(MyProvider.INDEX, 0);
             StackedPluginConfiguration compress
-                    = new StackedPluginConfiguration(MyProvider.NAME, 0, true, config1);
+                    = new StackedPluginConfiguration(MyProvider.NAME + "1", 0, true, config1);
             lst.add(compress);
         }
 
@@ -279,7 +334,7 @@
             Map<String, Object> config1 = new HashMap<>();
             config1.put(MyProvider.INDEX, 1);
             StackedPluginConfiguration compress
-                    = new StackedPluginConfiguration(MyProvider.NAME, 1, true, config1);
+                    = new StackedPluginConfiguration(MyProvider.NAME + "2", 1, true, config1);
             lst.add(compress);
         }
 
@@ -288,7 +343,7 @@
             Map<String, Object> config1 = new HashMap<>();
             config1.put(MyProvider.INDEX, 3);
             StackedPluginConfiguration compress
-                    = new StackedPluginConfiguration(MyProvider.NAME, 1, false, config1);
+                    = new StackedPluginConfiguration(MyProvider.NAME + "3", 1, false, config1);
             lst.add(compress);
         }
 
@@ -297,7 +352,7 @@
         PluginConfiguration imgBuilder
                 = new Jlink.PluginConfiguration("default-image-builder", config1);
         PluginsConfiguration plugins
-                = new Jlink.PluginsConfiguration(lst, imgBuilder);
+                = new Jlink.PluginsConfiguration(lst, Collections.emptyList(), imgBuilder);
 
         jlink.build(config, plugins);
 
@@ -351,7 +406,7 @@
         PluginConfiguration imgBuilder
                 = new Jlink.PluginConfiguration("default-image-builder", config1);
         PluginsConfiguration plugins
-                = new Jlink.PluginsConfiguration(lst, imgBuilder);
+                = new Jlink.PluginsConfiguration(lst, Collections.emptyList(), imgBuilder);
         boolean failed = false;
         try {
             jlink.build(config, plugins);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jigsaw/tools/jlink/JLinkPostProcessingTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import jdk.tools.jlink.internal.ImagePluginProviderRepository;
+import jdk.tools.jlink.plugins.ExecutableImage;
+import jdk.tools.jlink.plugins.OnOffPostProcessingPluginProvider;
+import jdk.tools.jlink.plugins.PostProcessingPlugin;
+import jdk.tools.jlink.plugins.ProcessingManager;
+import jdk.tools.jlink.plugins.ProcessingManager.ProcessingSession;
+import jdk.tools.jlink.plugins.ProcessingManager.RunningProcess;
+import tests.Helper;
+import tests.JImageGenerator;
+import tests.JImageGenerator.InMemoryFile;
+
+/*
+ * @test
+ * @summary Test post processing
+ * @author Jean-Francois Denise
+ * @library ../lib
+ * @modules java.base/jdk.internal.jimage
+ *          jdk.jdeps/com.sun.tools.classfile
+ *          jdk.jlink/jdk.tools.jlink
+ *          jdk.jlink/jdk.tools.jlink.internal
+ *          jdk.jlink/jdk.tools.jmod
+ *          jdk.jlink/jdk.tools.jimage
+ * @build tests.*
+ * @run main/othervm JLinkPostProcessingTest
+ */
+public class JLinkPostProcessingTest {
+
+    private static class PostProcessingTest extends OnOffPostProcessingPluginProvider {
+
+        private static ExecutableImage called;
+        @Override
+        public PostProcessingPlugin[] createPlugins(Map<String, String> otherOptions) throws IOException {
+            return new PostProcessingPlugin[]{new PPPlugin()};
+        }
+
+        private static boolean isWindows() {
+            return System.getProperty("os.name").startsWith("Windows");
+        }
+
+        private static class PPPlugin implements PostProcessingPlugin {
+
+            @Override
+            public List<String> process(ProcessingManager manager) throws Exception {
+                called = manager.getImage();
+                List<String> args = new ArrayList<>();
+                args.add("-version");
+
+                ProcessingSession session = manager.newSession("test");
+                {
+                    RunningProcess i = session.newImageProcess(args);
+                    String str = i.getStdout();
+                    if (!str.isEmpty()) {
+                        throw new Exception("Unexpected out " + str);
+                    }
+                    String str2 = i.getStderr();
+                    if (str2.isEmpty()) {
+                        throw new Exception("Version not print ");
+                    } else {
+                        System.out.println("REMOTE PROCESS output: " + str2);
+                    }
+                    if (i.getExitCode() != 0) {
+                        throw new Exception("Not valid exit code " + i.getExitCode());
+                    }
+                }
+
+                {
+                    ProcessBuilder builder = new ProcessBuilder(manager.getImage().
+                            getHome().resolve("bin").
+                            resolve(isWindows() ? "java.exe" : "java").toString(), "-version");
+                    RunningProcess i = session.newRunningProcess(builder);
+                    String str = i.getStdout();
+                    if (!str.isEmpty()) {
+                        throw new Exception("Unexpected out " + str);
+                    }
+                    String str2 = i.getStderr();
+                    if (str2.isEmpty()) {
+                        throw new Exception("Version not print ");
+                    } else {
+                        System.out.println("REMOTE PROCESS output: " + str2);
+                    }
+                    if (i.getExitCode() != 0) {
+                        throw new Exception("Not valid exit code " + i.getExitCode());
+                    }
+                }
+
+
+                session.close();
+
+                Path gen = manager.getImage().getHome().resolve("lib").resolve("toto.txt");
+                Files.createFile(gen);
+                return null;
+            }
+
+            @Override
+            public String getName() {
+                return NAME;
+            }
+
+        }
+        private static final String NAME = "pp";
+
+        PostProcessingTest() {
+            super(NAME, "");
+        }
+
+        @Override
+        public String getToolOption() {
+            return NAME;
+        }
+
+        @Override
+        public Map<String, String> getAdditionalOptions() {
+            return null;
+        }
+
+        @Override
+        public String getCategory() {
+            return PROCESSOR;
+        }
+
+    }
+    public static void main(String[] args) throws Exception {
+
+        Helper helper = Helper.newHelper();
+        if (helper == null) {
+            System.err.println("Test not run");
+            return;
+        }
+        helper.generateDefaultModules();
+
+        ImagePluginProviderRepository.registerPluginProvider(new PostProcessingTest());
+
+        // Generate an image and post-process in same jlink execution.
+        {
+            String[] userOptions = {"--pp", "on"};
+            String moduleName = "postprocessing1";
+            helper.generateDefaultJModule(moduleName, "composite2");
+            String[] res = {};
+            String[] files = {};
+            Path imageDir = helper.generateDefaultImage(userOptions, moduleName).assertSuccess();
+            helper.checkImage(imageDir, moduleName, res, files);
+
+            test(imageDir);
+        }
+
+        // Generate an image, post-process in 2 jlink executions.
+        {
+            String[] userOptions = {};
+            String moduleName = "postprocessing2";
+            helper.generateDefaultJModule(moduleName, "composite2");
+            String[] res = {};
+            String[] files = {};
+            Path imageDir = helper.generateDefaultImage(userOptions, moduleName).assertSuccess();
+            helper.checkImage(imageDir, moduleName, res, files);
+
+            String[] ppOptions = {"--pp", "on"};
+            helper.postProcessImage(imageDir, ppOptions);
+            test(imageDir);
+        }
+    }
+
+    private static void test(Path imageDir)
+            throws Exception {
+        if (PostProcessingTest.called == null) {
+            throw new Exception("Post processor not called.");
+        }
+        if (!PostProcessingTest.called.getHome().equals(imageDir)) {
+            throw new Exception("Not right imageDir " + PostProcessingTest.called.getHome());
+        }
+        if (PostProcessingTest.called.getExecutionArgs().isEmpty()) {
+            throw new Exception("No arguments to run java...");
+        }
+        Path gen = imageDir.resolve("lib").resolve("toto.txt");
+        if (!Files.exists(gen)) {
+            throw new Exception("Generated file doesn;t exist");
+        }
+        PostProcessingTest.called = null;
+    }
+}
--- a/test/jdk/jigsaw/tools/jlink/customplugin/plugin/CustomImageBuilder.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/customplugin/plugin/CustomImageBuilder.java	Fri Oct 09 18:03:49 2015 +0200
@@ -28,9 +28,11 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import jdk.tools.jlink.plugins.ExecutableImage;
 
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageFilePool;
@@ -69,4 +71,15 @@
     public DataOutputStream getJImageOutputStream() throws IOException {
         return new DataOutputStream(Files.newOutputStream(image.resolve("image.jimage")));
     }
+
+    @Override
+    public ExecutableImage getExecutableImage() throws IOException {
+        return new ExecutableImage(image, Collections.emptySet(),
+                Collections.emptyList()) {
+                };
+    }
+
+    @Override
+    public void storeJavaLauncherOptions(ExecutableImage image, List<String> args) throws IOException {
+    }
 }
--- a/test/jdk/jigsaw/tools/jlink/customplugin/plugin/CustomImageBuilderProvider.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/customplugin/plugin/CustomImageBuilderProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -25,8 +25,11 @@
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import jdk.tools.jlink.plugins.ExecutableImage;
 
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageBuilderProvider;
@@ -68,4 +71,14 @@
     public boolean hasArgument(String option) {
         return option.equals(OPTION + "-1");
     }
+
+    @Override
+    public ExecutableImage canExecute(Path root) {
+        return new ExecutableImage(root, Collections.emptySet(), Collections.emptyList()) {
+        };
+    }
+
+    @Override
+    public void storeLauncherOptions(ExecutableImage image, List<String> arguments) throws IOException {
+    }
 }
--- a/test/jdk/jigsaw/tools/jlink/customplugin/plugin/SameNamedImageBuilderProvider.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/customplugin/plugin/SameNamedImageBuilderProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -25,7 +25,10 @@
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
+import jdk.tools.jlink.plugins.ExecutableImage;
 
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageBuilderProvider;
@@ -58,4 +61,14 @@
     public boolean hasArgument(String option) {
         return false;
     }
+
+    @Override
+    public ExecutableImage canExecute(Path root) {
+        return new ExecutableImage(root, Collections.emptySet(), Collections.emptyList());
+    }
+
+    @Override
+    public void storeLauncherOptions(ExecutableImage image, List<String> arguments) throws IOException {
+
+    }
 }
--- a/test/jdk/jigsaw/tools/jlink/customplugin/plugin/SecondImageBuilderProvider.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/customplugin/plugin/SecondImageBuilderProvider.java	Fri Oct 09 18:03:49 2015 +0200
@@ -27,8 +27,10 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import jdk.tools.jlink.plugins.ExecutableImage;
 
 import jdk.tools.jlink.plugins.ImageBuilder;
 import jdk.tools.jlink.plugins.ImageBuilderProvider;
@@ -59,6 +61,16 @@
             public DataOutputStream getJImageOutputStream() throws IOException {
                 return new DataOutputStream(Files.newOutputStream(imageOutDir.resolve("image.jimage")));
             }
+
+            @Override
+            public ExecutableImage getExecutableImage() throws IOException {
+                return new ExecutableImage(imageOutDir, Collections.emptySet(), Collections.emptyList());
+            }
+
+            @Override
+            public void storeJavaLauncherOptions(ExecutableImage image, List<String> args) throws IOException {
+
+            }
         };
     }
 
@@ -71,4 +83,14 @@
     public boolean hasArgument(String option) {
         return false;
     }
+
+    @Override
+    public ExecutableImage canExecute(Path root) {
+        return new ExecutableImage(root, Collections.emptySet(), Collections.emptyList());
+    }
+
+    @Override
+    public void storeLauncherOptions(ExecutableImage image, List<String> arguments) throws IOException {
+
+    }
 }
--- a/test/jdk/jigsaw/tools/jlink/plugins/CompressorPluginTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/CompressorPluginTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -61,6 +61,7 @@
 import jdk.tools.jlink.internal.plugins.StringSharingProvider;
 import jdk.tools.jlink.internal.plugins.ZipCompressProvider;
 import jdk.tools.jlink.plugins.CmdPluginProvider;
+import jdk.tools.jlink.plugins.OnOffPluginProvider;
 import jdk.tools.jlink.plugins.Plugin;
 import jdk.tools.jlink.plugins.PluginProvider;
 import jdk.tools.jlink.plugins.ResourcePlugin;
@@ -100,7 +101,7 @@
         // compress == ZIP + String sharing
         Properties options = new Properties();
         options.setProperty(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
-                ImagePluginConfiguration.ON_ARGUMENT);
+                OnOffPluginProvider.ON_ARGUMENT);
         checkCompress(classes, new DefaultCompressProvider(), options,
                 new ResourceDecompressorFactory[]{
                         new ZipDecompressorFactory(),
@@ -120,7 +121,7 @@
         // compress level 1 == ZIP
         Properties options1 = new Properties();
         options1.setProperty(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
-                ImagePluginConfiguration.ON_ARGUMENT);
+                OnOffPluginProvider.ON_ARGUMENT);
         options1.setProperty(DefaultCompressProvider.LEVEL_OPTION, "1");
         checkCompress(classes, new DefaultCompressProvider(),
                 options1,
@@ -141,7 +142,7 @@
         // compress level 2 == ZIP + String sharing
         Properties options2 = new Properties();
         options2.setProperty(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
-                ImagePluginConfiguration.ON_ARGUMENT);
+                OnOffPluginProvider.ON_ARGUMENT);
         options2.setProperty(DefaultCompressProvider.LEVEL_OPTION, "2");
         checkCompress(classes, new DefaultCompressProvider(),
                 options2,
@@ -164,7 +165,7 @@
         // compress level 0 == String sharing
         Properties options0 = new Properties();
         options0.setProperty(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
-                ImagePluginConfiguration.ON_ARGUMENT);
+                OnOffPluginProvider.ON_ARGUMENT);
         options0.setProperty(DefaultCompressProvider.LEVEL_OPTION, "0");
         checkCompress(classes, new DefaultCompressProvider(),
                 options0,
--- a/test/jdk/jigsaw/tools/jlink/plugins/LastSorterTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/LastSorterTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -33,6 +33,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -76,7 +77,8 @@
     private void checkTwoLastSorters() throws Exception {
         List<StackedPluginConfiguration> plugins = new ArrayList<>();
         plugins.add(createConfig("sorterplugin6", "/a", 0));
-        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins, null, "sorterplugin6");
+        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins,
+                Collections.emptyList(), null, "sorterplugin6");
 
         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(config);
 
@@ -129,7 +131,8 @@
         plugins.add(createConfig("sorterplugin2", "/b", 1));
         plugins.add(createConfig("sorterplugin3", "/a", 2));
 
-        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins, null, "sorterplugin3");
+        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins,
+                Collections.emptyList(), null, "sorterplugin3");
 
         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(config);
 
@@ -156,7 +159,8 @@
         plugins.add(createConfig("sorterplugin3", "/1", 2));
         plugins.add(createConfig("sorterplugin4", "/1", 3));
 
-        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins, null, "sorterplugin5");
+        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins,
+                Collections.emptyList(), null, "sorterplugin5");
         try {
             ImagePluginConfiguration.parseConfiguration(config);
             throw new AssertionError("Unknown plugin should have failed.");
@@ -172,7 +176,8 @@
         plugins.add(createConfig("sorterplugin3", "/a", 2));
         plugins.add(createConfig("sorterplugin4", "/d", 3));
 
-        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins, null, "sorterplugin3");
+        PluginsConfiguration config = new Jlink.PluginsConfiguration(plugins,
+                Collections.emptyList(), null, "sorterplugin3");
 
         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(config);
 
--- a/test/jdk/jigsaw/tools/jlink/plugins/OnOffProviderTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/OnOffProviderTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -35,12 +35,10 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Properties;
-
-import jdk.tools.jlink.internal.ImagePluginConfiguration;
 import jdk.tools.jlink.plugins.CmdPluginProvider;
 import jdk.tools.jlink.plugins.ImageFilePlugin;
 import jdk.tools.jlink.plugins.OnOffImageFilePluginProvider;
+import jdk.tools.jlink.plugins.OnOffPluginProvider;
 import jdk.tools.jlink.plugins.OnOffResourcePluginProvider;
 import jdk.tools.jlink.plugins.Plugin;
 import jdk.tools.jlink.plugins.PluginProvider;
@@ -80,7 +78,7 @@
     public static void test(ProviderFactory factory) throws IOException {
         {
             Map<String, Object> config = new HashMap<>();
-            config.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY, ImagePluginConfiguration.OFF_ARGUMENT);
+            config.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY, OnOffPluginProvider.OFF_ARGUMENT);
             Plugin[] plugins = factory.newProvider().newPlugins(config);
             if (plugins.length != 0) {
                 throw new AssertionError("Expected empty list of plugins");
@@ -89,7 +87,7 @@
         }
         {
             Map<String, Object> config = new HashMap<>();
-            config.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY, ImagePluginConfiguration.ON_ARGUMENT);
+            config.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY, OnOffPluginProvider.ON_ARGUMENT);
             config.put(OPTION, VALUE);
             factory.newProvider().newPlugins(config);
             if (!isNewPluginsCalled) {
@@ -100,7 +98,7 @@
         {
             Map<String, Object> config = new HashMap<>();
             config.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
-                    ImagePluginConfiguration.ON_ARGUMENT + "," + ImagePluginConfiguration.OFF_ARGUMENT);
+                    OnOffPluginProvider.ON_ARGUMENT + "," + OnOffPluginProvider.OFF_ARGUMENT);
             try {
                 factory.newProvider().newPlugins(config);
                 throw new AssertionError("IOException expected");
--- a/test/jdk/jigsaw/tools/jlink/plugins/PluginOrderTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/PluginOrderTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -170,7 +170,7 @@
             lst.add(new StackedPluginConfiguration(name, index,
                     absolute, Collections.emptyMap()));
         }
-        return new PluginsConfiguration(lst, null);
+        return new PluginsConfiguration(lst, Collections.emptyList(), null);
     }
 
     private void test1(List<String> order) throws Exception {
--- a/test/jdk/jigsaw/tools/jlink/plugins/PluginsNegativeTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/PluginsNegativeTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -98,7 +98,8 @@
     private void testEmptyOutputResource() throws Exception {
         List<Jlink.StackedPluginConfiguration> plugins = new ArrayList<>();
         plugins.add(createConfig("plugin", 0));
-        ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(new PluginsConfiguration(plugins, null));
+        ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(new PluginsConfiguration(plugins,
+                Collections.emptyList(), null));
         ResourcePoolImpl inResources = new ResourcePoolImpl(ByteOrder.nativeOrder());
         inResources.addResource(new ResourcePool.Resource("/aaa/bbb/A", ByteBuffer.allocate(10)));
         try {
@@ -121,7 +122,8 @@
     private void testEmptyInputResource() throws Exception {
         List<Jlink.StackedPluginConfiguration> plugins = new ArrayList<>();
         plugins.add(createConfig("plugin", 0));
-        ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(new PluginsConfiguration(plugins, null));
+        ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(new PluginsConfiguration(plugins,
+                Collections.emptyList(), null));
         ResourcePoolImpl inResources = new ResourcePoolImpl(ByteOrder.nativeOrder());
         ResourcePoolImpl outResources = (ResourcePoolImpl) stack.visitResources(inResources, new StringTable() {
             @Override
--- a/test/jdk/jigsaw/tools/jlink/plugins/PrevisitorTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/PrevisitorTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -66,7 +66,8 @@
         ImagePluginProviderRepository.registerPluginProvider(provider);
         List<Jlink.StackedPluginConfiguration> plugins = new ArrayList<>();
         plugins.add(createConfig("plugin", 0));
-        ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(new Jlink.PluginsConfiguration(plugins, null));
+        ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(new Jlink.PluginsConfiguration(plugins,
+                Collections.emptyList(), null));
         ResourcePoolImpl inResources = new ResourcePoolImpl(ByteOrder.nativeOrder());
         inResources.addResource(new ResourcePool.Resource("/aaa/bbb/res1.class", ByteBuffer.allocate(90)));
         inResources.addResource(new ResourcePool.Resource("/aaa/bbb/res2.class", ByteBuffer.allocate(90)));
--- a/test/jdk/jigsaw/tools/jlink/plugins/StripDebugPluginTest.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/jlink/plugins/StripDebugPluginTest.java	Fri Oct 09 18:03:49 2015 +0200
@@ -47,7 +47,6 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Properties;
 import java.util.stream.Stream;
 
 import com.sun.tools.classfile.Attribute;
@@ -57,10 +56,10 @@
 import com.sun.tools.classfile.Method;
 import java.util.HashMap;
 import java.util.Map;
-import jdk.tools.jlink.internal.ImagePluginConfiguration;
 import jdk.tools.jlink.internal.ResourcePoolImpl;
 import jdk.tools.jlink.internal.plugins.StripDebugProvider;
 import jdk.tools.jlink.plugins.CmdPluginProvider;
+import jdk.tools.jlink.plugins.OnOffPluginProvider;
 import jdk.tools.jlink.plugins.ResourcePlugin;
 import jdk.tools.jlink.plugins.ResourcePool;
 import jdk.tools.jlink.plugins.ResourcePool.Resource;
@@ -112,7 +111,7 @@
         StripDebugProvider prov = new StripDebugProvider();
         Map<String, Object> options = new HashMap<>();
         options.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
-                ImagePluginConfiguration.ON_ARGUMENT);
+                OnOffPluginProvider.ON_ARGUMENT);
         ResourcePlugin debug = (ResourcePlugin) prov.newPlugins(options)[0];
         Resource result1 = stripDebug(debug, new Resource(path, ByteBuffer.wrap(content)), path, infoPath, moduleInfo);
 
--- a/test/jdk/jigsaw/tools/lib/tests/Helper.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/lib/tests/Helper.java	Fri Oct 09 18:03:49 2015 +0200
@@ -253,6 +253,15 @@
         return jLinkTask.call();
     }
 
+    public Result postProcessImage(Path root, String[] options) {
+        JLinkTask jLinkTask = JImageGenerator.getJLinkTask()
+                .existing(root);
+        for (String option : options) {
+            jLinkTask.option(option);
+        }
+        return jLinkTask.callPostProcess();
+    }
+
     private List<String> getDefaultClasses(String module) {
         return Arrays.asList(module + ".Main", module + ".com.foo.bar.X");
     }
--- a/test/jdk/jigsaw/tools/lib/tests/JImageGenerator.java	Fri Oct 09 18:01:08 2015 +0200
+++ b/test/jdk/jigsaw/tools/lib/tests/JImageGenerator.java	Fri Oct 09 18:03:49 2015 +0200
@@ -103,6 +103,7 @@
             + "}\n";
 
     private static final String OUTPUT_OPTION = "--output";
+    private static final String POST_PROCESS_OPTION = "--post-process-path";
     private static final String MAIN_CLASS_OPTION = "--main-class";
     private static final String CLASS_PATH_OPTION = "--class-path";
     private static final String MODULE_PATH_OPTION = "--modulepath";
@@ -572,6 +573,7 @@
         private final List<String> options = new ArrayList<>();
         private String modulePath;
         private Path output;
+        private Path existing;
 
         public JLinkTask modulePath(String modulePath) {
             this.modulePath = modulePath;
@@ -608,6 +610,11 @@
             return this;
         }
 
+        public JLinkTask existing(Path existing) {
+            this.existing = existing;
+            return this;
+        }
+
         public JLinkTask option(String o) {
             this.options.add(o);
             return this;
@@ -656,6 +663,16 @@
             return options.toArray(new String[options.size()]);
         }
 
+        private String[] optionsPostProcessJLink() {
+            List<String> options = new ArrayList<>();
+            if (existing != null) {
+                options.add(POST_PROCESS_OPTION);
+                options.add(existing.toString());
+            }
+            options.addAll(this.options);
+            return options.toArray(new String[options.size()]);
+        }
+
         public Result call() {
             String[] args = optionsJLink();
             System.err.println("jlink options: " + optionsPrettyPrint(args));
@@ -663,6 +680,14 @@
             int exitCode = jdk.tools.jlink.Main.run(args, new PrintWriter(writer));
             return new Result(exitCode, writer.toString(), output);
         }
+
+        public Result callPostProcess() {
+            String[] args = optionsPostProcessJLink();
+            System.err.println("jlink options: " + optionsPrettyPrint(args));
+            StringWriter writer = new StringWriter();
+            int exitCode = jdk.tools.jlink.Main.run(args, new PrintWriter(writer));
+            return new Result(exitCode, writer.toString(), output);
+        }
     }
 
     public static class InMemorySourceFile {