package org.bndtools.utils.copy.bundleresource; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import org.osgi.framework.Bundle; import aQute.lib.io.IO; public class BundleResourceCopier { /** the bundle holding the resources */ private Bundle bundle = null; /** * Constructor * * @param bundle * the bundle holding the resources */ public BundleResourceCopier(Bundle bundle) { super(); this.bundle = bundle; } private void addOrRemoveDirectoryRecursive(File dstDir, String bundleDir, String relativePath, CopyMode mode, List<File> affected) throws IOException { String resourcePath = formatBundleEntryPath(new File(bundleDir, relativePath).getPath()); Enumeration<String> resourcePathEntries = bundle.getEntryPaths(resourcePath); if (resourcePathEntries != null) { while (resourcePathEntries.hasMoreElements()) { String resourcePathEntry = resourcePathEntries.nextElement(); if (resourcePathEntry.startsWith(bundleDir)) { resourcePathEntry = resourcePathEntry.substring(bundleDir.length()); } if (resourcePathEntry.endsWith("/")) { addOrRemoveDirectoryRecursive(dstDir, bundleDir, resourcePathEntry, mode, affected); } else { affected.addAll(addOrRemoveFile(dstDir, bundleDir, resourcePathEntry, mode)); } } } } /** * Add/remove a file (backed by a bundle resource) to/from a directory. * * @param dstDir * the destination directory under which to add/remove a file * @param bundleDir * the bundle directory under which the resource is located * @param relativePath * the path of the resource (relative to bundleDir) in the bundle. The resource will be added/removed * to/from the same path relative to dstDir. This parameter must only hold the path of a file. * @param mode * @throws IOException * when relativePath is null or empty, when the resource could not be found in the bundle, when the * directory holding the file could not be created (when add is true), or when the file could not be * removed (when add is false) */ public Collection<File> addOrRemoveFile(File dstDir, String bundleDir, String relativePath, CopyMode mode) throws IOException { List<File> affected = new LinkedList<>(); if (relativePath == null || relativePath.length() == 0) { throw new IOException("Resource relative path can't be empty"); } File dstFile = new File(dstDir, relativePath); File relativeDstFile = new File(relativePath); if (mode == null) { throw new IllegalArgumentException("Copy mode may not be null"); } else if (mode == CopyMode.REMOVE) { Files.deleteIfExists(dstFile.toPath()); } else if (mode == CopyMode.CHECK) { if (dstFile.exists()) affected.add(relativeDstFile); } else { if (dstFile.exists() && !dstFile.isFile()) throw new IOException("Target path exists and is not a plain file: " + dstFile); if (dstFile.exists() && mode == CopyMode.ADD) { affected.add(relativeDstFile); } else { String resourcePath = formatBundleEntryPath(new File(bundleDir, relativePath).getPath()); URL resourceUrl = bundle.getEntry(resourcePath); if (resourceUrl == null) throw new IOException("Resource " + resourcePath + " not found in bundle " + bundle.getSymbolicName()); if (mode == CopyMode.REPLACE || !dstFile.exists()) { File dstFileDir = dstFile.getParentFile(); if (dstFileDir != null) Files.createDirectories(dstFileDir.toPath()); IO.copy(resourceUrl, dstFile); } } } return affected; } /** * Add/remove files (backed by bundle resources) to/from a directory. * * @param dstDir * the destination directory under which to add/remove files * @param bundleDir * the bundle directory under which the resources are located * @param relativePaths * the paths of the resources (relative to bundleDir) in the bundle. The resources will be added/removed * to/from the same paths relative to dstDir. This parameter must only hold paths of files. * @param mode * @throws IOException * when a relative path is null or empty, when a resource could not be found in the bundle, when a * directory holding a file could not be created (when add is true), or when a file could not be removed * (when add is false) */ public Collection<File> addOrRemoveFiles(File dstDir, String bundleDir, String[] relativePaths, CopyMode mode) throws IOException { List<File> affected = new LinkedList<>(); for (String templatePath : relativePaths) { affected.addAll(addOrRemoveFile(dstDir, bundleDir, templatePath, mode)); } return affected; } /** * Recursively add/remove a directory and its files (backed by bundle resources) to/from a directory. * * @param dstDir * the destination directory under which to add/remove the directory and its files * @param bundleDir * the bundle directory under which the resources are located * @param relativePath * the path of the resources (relative to bundleDir) in the bundle. The resources will be recursively * added/removed to/from the same paths relative to dstDir. This parameter must only hold a paths of a * directory. When null then "/" will be used. * @param mode * @return A list of existing files that were/would have been affected. * @throws IOException * when a relative path is null or empty, when a resource could not be found in the bundle, when a * directory holding a file could not be created (if add is true), or when a file could not be removed * (when add is false) */ public Collection<File> addOrRemoveDirectory(File dstDir, String bundleDir, String relativePath, CopyMode mode) throws IOException { List<File> affected = new LinkedList<>(); String bundleDirFixed = bundleDir.replaceAll("^/+", ""); if (!bundleDirFixed.endsWith("/")) { bundleDirFixed = bundleDirFixed + "/"; } String relativePathFixed = relativePath; if (relativePathFixed == null) { relativePathFixed = "/"; } if (!relativePathFixed.endsWith("/")) { relativePathFixed = relativePathFixed + "/"; } addOrRemoveDirectoryRecursive(dstDir, bundleDirFixed, relativePathFixed, mode, affected); return affected; } /** * Recursively add/remove directories and their files (backed by bundle resources) to/from a directory. * * @param dstDir * the destination directory under which to add/remove directories and files * @param bundleDir * the bundle directory under which the resources are located * @param relativePaths * the paths of the resources (relative to bundleDir) in the bundle. The resources will be recursively * added/removed to/from the same paths relative to dstDir. This parameter must only hold paths of * directories. * @param mode * @throws IOException * when a relative path is null or empty, when a resource could not be found in the bundle, when a * directory holding a file could not be created (when add is true), or when a file could not be removed * (when add is false) */ public void addOrRemoveDirectories(File dstDir, String bundleDir, String[] relativePaths, CopyMode mode) throws IOException { for (String templatePath : relativePaths) { addOrRemoveDirectory(dstDir, bundleDir, templatePath, mode); } } private String formatBundleEntryPath(String path) { // Bundle.getEntry* doesn't grok backslashes if (File.separatorChar != '\\') { return path; } return path.replace('\\', '/'); } }