diff --git a/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java b/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java --- a/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java +++ b/o.n.upgrader/src/org/netbeans/upgrade/AutoUpgrade.java @@ -92,8 +92,6 @@ //equal or greater than 6.5 copyToUserdir(sourceFolder); - //#75324 NBplatform settings are not imported - upgradeBuildProperties(sourceFolder, version); //migrates SystemOptions, converts them as a Preferences Importer.doImport(); } @@ -253,19 +251,7 @@ File netBeansDir = InstalledFileLocator.getDefault().locate("modules", null, false).getParentFile().getParentFile(); //NOI18N File importFile = new File(netBeansDir, "etc/netbeans.import"); //NOI18N LOGGER.fine("Import file: " + importFile); - IncludeExclude includeExclude; - try { - InputStream is = new FileInputStream(importFile); - Reader r = new InputStreamReader(is, "utf-8"); // NOI18N - includeExclude = IncludeExclude.create(r); - r.close(); - } catch (IOException ex) { - // show error message and continue - JDialog dialog = Util.createJOptionDialog(new JOptionPane(ex, JOptionPane.ERROR_MESSAGE), ex.getMessage()); - dialog.setVisible(true); - return; - } LOGGER.info("Importing from " + source + " to " + userdir); // NOI18N - CopyFiles.copyDeep(source, userdir, includeExclude); + CopyFiles.copyDeep(source, userdir, importFile); } } diff --git a/o.n.upgrader/src/org/netbeans/upgrade/CopyFiles.java b/o.n.upgrader/src/org/netbeans/upgrade/CopyFiles.java --- a/o.n.upgrader/src/org/netbeans/upgrade/CopyFiles.java +++ b/o.n.upgrader/src/org/netbeans/upgrade/CopyFiles.java @@ -40,14 +40,24 @@ */ package org.netbeans.upgrade; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.Reader; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import org.netbeans.util.Util; import org.openide.filesystems.FileUtil; +import org.openide.util.EditableProperties; /** Does copy of files according to include/exclude patterns. * @@ -57,17 +67,29 @@ private File sourceRoot; private File targetRoot; - private IncludeExclude includeExclude; + private EditableProperties currentProperties; + private Set includePatterns = new HashSet(); + private Set excludePatterns = new HashSet(); private static final Logger LOGGER = Logger.getLogger(CopyFiles.class.getName()); - private CopyFiles(File source, File target, IncludeExclude includeExclude) { + private CopyFiles(File source, File target, File patternsFile) { this.sourceRoot = source; this.targetRoot = target; - this.includeExclude = includeExclude; + try { + InputStream is = new FileInputStream(patternsFile); + Reader reader = new InputStreamReader(is, "utf-8"); // NOI18N + readPatterns(reader); + reader.close(); + } catch (IOException ex) { + // show error message and continue + JDialog dialog = Util.createJOptionDialog(new JOptionPane(ex, JOptionPane.ERROR_MESSAGE), ex.getMessage()); + dialog.setVisible(true); + return; + } } - public static void copyDeep(File source, File target, IncludeExclude includeExclude) throws IOException { - CopyFiles copyFiles = new CopyFiles(source, target, includeExclude); + public static void copyDeep(File source, File target, File patternsFile) throws IOException { + CopyFiles copyFiles = new CopyFiles(source, target, patternsFile); LOGGER.fine("Copying from: " + copyFiles.sourceRoot + "\nto: " + copyFiles.targetRoot); //NOI18N copyFiles.copyFolder(copyFiles.sourceRoot); } @@ -78,11 +100,7 @@ if (child.isDirectory()) { copyFolder(child); } else { - String relativePath = getRelativePath(this.sourceRoot, child); - if (includeExclude.contains(relativePath)) { - LOGGER.fine("Path: " + relativePath); - copyFile(child, new File(targetRoot, relativePath)); - } + copyFile(child); } } } @@ -120,6 +138,116 @@ } } + /** Copy given file to target root dir if matches include/exclude patterns. + * If properties pattern is applicable, it copies only matching keys. + * @param sourceFile source file + * @throws java.io.IOException if copying fails + */ + private void copyFile(File sourceFile) throws IOException { + String relativePath = getRelativePath(sourceRoot, sourceFile); + currentProperties = null; + boolean includeFile = false; + Set includeKeys = new HashSet(); + Set excludeKeys = new HashSet(); + for (String pattern : includePatterns) { + if (pattern.contains("#")) { //NOI18N + includeKeys.addAll(matchingKeys(relativePath, pattern)); + } else { + if (relativePath.matches(pattern)) { + includeFile = true; + includeKeys.clear(); // include entire file + break; + } + } + } + if (includeFile || !includeKeys.isEmpty()) { + // check excludes + for (String pattern : excludePatterns) { + if (pattern.contains("#")) { //NOI18N + excludeKeys.addAll(matchingKeys(relativePath, pattern)); + } else { + if (relativePath.matches(pattern)) { + includeFile = false; + includeKeys.clear(); // exclude entire file + break; + } + } + } + } + LOGGER.log(Level.FINEST, "{0}, includeFile={1}, includeKeys={2}, excludeKeys={3}", new Object[]{relativePath, includeFile, includeKeys, excludeKeys}); //NOI18N + if (!includeFile && includeKeys.isEmpty()) { + // nothing matches + return; + } + + File targetFile = new File(targetRoot, relativePath); + LOGGER.log(Level.FINE, "Path: {0}", relativePath); //NOI18N + if (includeKeys.isEmpty() && excludeKeys.isEmpty()) { + // copy entire file + copyFile(sourceFile, targetFile); + } else { + if (!includeKeys.isEmpty()) { + currentProperties.keySet().retainAll(includeKeys); + } + currentProperties.keySet().removeAll(excludeKeys); + // copy just selected keys + LOGGER.log(Level.FINE, " Only keys: {0}", currentProperties.keySet()); + OutputStream out = null; + try { + ensureParent(targetFile); + out = new FileOutputStream(targetFile); + currentProperties.store(out); + } finally { + if (out != null) { + out.close(); + } + } + } + } + + /** Returns set of keys matching given pattern. + * @param relativePath path relative to sourceRoot + * @param propertiesPattern pattern like file.properties#keyPattern + * @return set of matching keys, never null + * @throws IOException if properties cannot be loaded + */ + private Set matchingKeys(String relativePath, String propertiesPattern) throws IOException { + Set matchingKeys = new HashSet(); + String[] patterns = propertiesPattern.split("#", 2); + String filePattern = patterns[0]; + String keyPattern = patterns[1]; + if (relativePath.matches(filePattern)) { + if (currentProperties == null) { + currentProperties = getProperties(relativePath); + } + for (String key : currentProperties.keySet()) { + if (key.matches(keyPattern)) { + matchingKeys.add(key); + } + } + } + return matchingKeys; + } + + /** Returns properties from relative path. + * @param relativePath relative path + * @return properties from relative path. + * @throws IOException if cannot open stream + */ + private EditableProperties getProperties(String relativePath) throws IOException { + EditableProperties properties = new EditableProperties(false); + InputStream in = null; + try { + in = new FileInputStream(new File(sourceRoot, relativePath)); + properties.load(in); + } finally { + if (in != null) { + in.close(); + } + } + return properties; + } + /** Creates parent of given file, if doesn't exist. */ private static void ensureParent(File file) throws IOException { final File parent = file.getParentFile(); @@ -129,4 +257,89 @@ } } } + + /** Reads the include/exclude set from a given reader. + * @param r reader + */ + private void readPatterns(Reader r) throws IOException { + BufferedReader buf = new BufferedReader(r); + for (;;) { + String line = buf.readLine(); + if (line == null) { + break; + } + line = line.trim(); + if (line.length() == 0 || line.startsWith("#")) { //NOI18N + continue; + } + if (line.startsWith("include ")) { //NOI18N + line = line.substring(8); + if (line.length() > 0) { + includePatterns.addAll(parsePattern(line)); + } + } else if (line.startsWith("exclude ")) { //NOI18N + line = line.substring(8); + if (line.length() > 0) { + excludePatterns.addAll(parsePattern(line)); + } + } else { + throw new java.io.IOException("Wrong line: " + line); //NOI18N + } + } + } + + enum ParserState { + + START, + IN_KEY_PATTERN, + AFTER_KEY_PATTERN + } + + /** Parses given compound string pattern into set of single patterns. + * @param pattern compound pattern in form filePattern1#keyPattern1#|filePattern2#keyPattern2#|filePattern3 + * @return set of single patterns containing just one # (e.g. [filePattern1#keyPattern1, filePattern2#keyPattern2, filePattern3]) + */ + private static Set parsePattern(String pattern) { + Set patterns = new HashSet(); + if (pattern.contains("#")) { //NOI18N + StringBuilder partPattern = new StringBuilder(); + ParserState state = ParserState.START; + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + switch (state) { + case START: + if (c == '#') { + state = ParserState.IN_KEY_PATTERN; + partPattern.append(c); + } else if (c == '|') { + patterns.add(partPattern.toString()); + partPattern = new StringBuilder(); + } else { + partPattern.append(c); + } + break; + case IN_KEY_PATTERN: + if (c == '#') { + state = ParserState.AFTER_KEY_PATTERN; + } else { + partPattern.append(c); + } + break; + case AFTER_KEY_PATTERN: + if (c == '|') { + state = ParserState.START; + patterns.add(partPattern.toString()); + partPattern = new StringBuilder(); + } else { + assert false : "Wrong OptionsExport pattern " + pattern + ". Only format like filePattern1#keyPattern#|filePattern2 is supported."; //NOI18N + } + break; + } + } + patterns.add(partPattern.toString()); + } else { + patterns.add(pattern); + } + return patterns; + } } diff --git a/options.api/arch.xml b/options.api/arch.xml --- a/options.api/arch.xml +++ b/options.api/arch.xml @@ -1114,16 +1114,32 @@ <attr name="displayName" bundlevalue="org.netbeans.modules.mymodule.options.Bundle#Category_Display_Name"/> <!-- item --> - <file name="MyItem"> - <attr name="displayName" stringvalue="org.netbeans.modules.mymodule.options.Bundle#Item_Display_Name"/> + <file name="MyItem1"> + <attr name="displayName" bundlevalue="org.netbeans.modules.mymodule.options.Bundle#Item1_Display_Name"/> <!-- include regex pattern rooted to userdir --> <attr name="include" stringvalue="config/Preferences/org/netbeans/modules/mymodule/.*|config/mymodule/.*"/> <!-- exclude regex pattern rooted to userdir --> <attr name="exclude" stringvalue="config/mymodule/obsolete/.*"/> </file> + <!-- item --> + <file name="MyItem2"> + <attr name="displayName" bundlevalue="org.netbeans.modules.mymodule.options.Bundle#Item2_Display_Name"/> + <!-- include pattern with properties constrain --> + <attr name="include" stringvalue="config/mymodule[.]properties#key[1-9].*|keyA.*#|config/mymodule[.]xml"/> + <!-- exclude pattern with properties constrain --> + <attr name="exclude" stringvalue="config/obsolete[.]properties#key5"/> + </file> </folder> </folder> + Include/exclude patterns may contain either a regular expression defining + files relatively to userdir (see MyItem1) or a compund pattern defining + files and property keys (see MyItem2). A compound pattern consists of + file regex pattern followed by hash delimiter (#) then property key + regex pattern followed by another hash delimiter. Hash delimiter can be + ommited at the end of compound pattern. For example, a compound pattern + can have the following structure + filePattern1#keyPattern1#|filePattern2|filePattern3#keyPattern3.

diff --git a/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java b/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java --- a/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java +++ b/options.api/src/org/netbeans/modules/options/export/OptionsExportModel.java @@ -53,6 +53,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -61,6 +62,7 @@ import java.util.zip.ZipOutputStream; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; +import org.openide.util.EditableProperties; import org.openide.util.Exceptions; import org.openide.util.NbBundle; @@ -83,6 +85,18 @@ private List categories; /** Cache of paths relative to source root */ List relativePaths; + /** Target ZipOutputStream for export. */ + private ZipOutputStream zipOutputStream; + /** Target userdir for import. */ + private File targetUserdir; + /** Include patterns. */ + private Set includePatterns; + /** Exclude patterns. */ + private Set excludePatterns; + /** Properties currently being copied. */ + private EditableProperties currentProperties; + /** List of ignored folders in userdir. It speeds up folder scanning. */ + private static final List IGNORED_FOLDERS = Arrays.asList("var/cache"); // NOI18N /** Returns instance of export options model. * @param source source of export/import. It is either zip file or userdir @@ -144,13 +158,9 @@ * @param targetUserdir target userdir */ void doImport(File targetUserdir) throws IOException { - if (source.isFile()) { - // zip file - extractZipFile(targetUserdir); - } else { - // userdir - copy(targetUserdir); - } + LOGGER.fine("Copying from: " + source + "\n to: " + targetUserdir); //NOI18N + this.targetUserdir = targetUserdir; + copyFiles(); } /** Creates zip file according to current state of model, i.e. only @@ -159,65 +169,94 @@ */ void doExport(File targetZipFile) { try { - createZipFile(targetZipFile); + ensureParent(targetZipFile); + // Create the ZIP file + zipOutputStream = new ZipOutputStream(new FileOutputStream(targetZipFile)); + copyFiles(); + createProductInfo(zipOutputStream); + // Complete the ZIP file + zipOutputStream.close(); } catch (IOException ex) { Exceptions.attachLocalizedMessage(ex, NbBundle.getMessage(OptionsExportModel.class, "OptionsExportModel.export.zip.error", targetZipFile)); Exceptions.printStackTrace(ex); + } finally { + if (zipOutputStream != null) { + try { + zipOutputStream.close(); + } catch (IOException ex) { + // ignore + } + } } } - /** Copies files from source dir to target dir according to current state - * of model, i.e. only include/exclude patterns from enabled items are - * considered. - * @param targetUserdir target userdir - * @throws java.io.IOException - */ - private void copy(File targetUserdir) throws IOException { - LOGGER.fine("Copying from: " + source + "\nto:" + targetUserdir); //NOI18N - List applicablePaths = getApplicablePaths(getIncludePatterns(), getExcludePatterns()); - if (!applicablePaths.isEmpty() && !targetUserdir.exists()) { - if (!targetUserdir.mkdirs()) { - throw new IOException("Cannot create folder: " + targetUserdir.getAbsolutePath()); //NOI18N - } - } - for (String path : applicablePaths) { - LOGGER.fine("Path=" + path); //NOI18N - copyFile(new File(source, path), new File(targetUserdir, path)); - } + private static enum ParserState { + + START, + IN_KEY_PATTERN, + AFTER_KEY_PATTERN } - /** Extracts files from source zip file to target dir according to current state - * of model, i.e. only include/exclude patterns from enabled items are - * considered. - * @param targetUserdir target userdir + /** Parses given compound string pattern into set of single patterns. + * @param pattern compound pattern in form filePattern1#keyPattern1#|filePattern2#keyPattern2#|filePattern3 + * @return set of single patterns containing just one # (e.g. [filePattern1#keyPattern1, filePattern2#keyPattern2, filePattern3]) */ - private void extractZipFile(File targetUserdir) throws IOException { - LOGGER.fine("Extracting from:" + source + " to:" + targetUserdir); //NOI18N - List applicablePaths = getApplicablePaths(getIncludePatterns(), getExcludePatterns()); - extractZipFile(source, targetUserdir, applicablePaths); + static Set parsePattern(String pattern) { + Set patterns = new HashSet(); + if (pattern.contains("#")) { //NOI18N + StringBuilder partPattern = new StringBuilder(); + ParserState state = ParserState.START; + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + switch(state) { + case START: + if (c == '#') { + state = ParserState.IN_KEY_PATTERN; + partPattern.append(c); + } else if (c == '|') { + patterns.add(partPattern.toString()); + partPattern = new StringBuilder(); + } else { + partPattern.append(c); + } + break; + case IN_KEY_PATTERN: + if (c == '#') { + state = ParserState.AFTER_KEY_PATTERN; + } else { + partPattern.append(c); + } + break; + case AFTER_KEY_PATTERN: + if (c == '|') { + state = ParserState.START; + patterns.add(partPattern.toString()); + partPattern = new StringBuilder(); + } else { + assert false : "Wrong OptionsExport pattern " + pattern + ". Only format like filePattern1#keyPattern#|filePattern2 is supported."; //NOI18N + } + break; + } + } + patterns.add(partPattern.toString()); + } else { + patterns.add(pattern); + } + return patterns; } - /** Creates zip file from source userdir according to current state - * of model, i.e. only include/exclude patterns from enabled items are - * considered. - * @param targetFile target zip file - */ - private void createZipFile(File targetFile) throws IOException { - LOGGER.fine("Creating file:" + targetFile + " from:" + source); //NOI18N - List applicablePaths = getApplicablePaths(getIncludePatterns(), getExcludePatterns()); - createZipFile(targetFile, source, applicablePaths); - } - - /** Returns set of include patterns in this model. */ - private Set getIncludePatterns() { - Set includePatterns = new HashSet(); - for (OptionsExportModel.Category category : getCategories()) { - for (OptionsExportModel.Item item : category.getItems()) { - if (item.isEnabled()) { - String include = item.getInclude(); - if (include != null && include.length() > 0) { - includePatterns.add(Pattern.compile(include)); + /** Returns set of include patterns. */ + private Set getIncludePatterns() { + if (includePatterns == null) { + includePatterns = new HashSet(); + for (OptionsExportModel.Category category : getCategories()) { + for (OptionsExportModel.Item item : category.getItems()) { + if (item.isEnabled()) { + String include = item.getInclude(); + if (include != null && include.length() > 0) { + includePatterns.addAll(parsePattern(include)); + } } } } @@ -225,15 +264,17 @@ return includePatterns; } - /** Returns set of exclude patterns in this model. */ - private Set getExcludePatterns() { - Set excludePatterns = new HashSet(); - for (OptionsExportModel.Category category : getCategories()) { - for (OptionsExportModel.Item item : category.getItems()) { - if (item.isEnabled()) { - String exclude = item.getExclude(); - if (exclude != null && exclude.length() > 0) { - excludePatterns.add(Pattern.compile(exclude)); + /** Returns set of exclude patterns. */ + private Set getExcludePatterns() { + if (excludePatterns == null) { + excludePatterns = new HashSet(); + for (OptionsExportModel.Category category : getCategories()) { + for (OptionsExportModel.Item item : category.getItems()) { + if (item.isEnabled()) { + String exclude = item.getExclude(); + if (exclude != null && exclude.length() > 0) { + excludePatterns.addAll(parsePattern(exclude)); + } } } } @@ -281,11 +322,9 @@ * @return true if at least one path in current source * matches include/exclude patterns, false otherwise */ - public boolean isApplicable() { + public boolean isApplicable() { if (!applicableInitialized) { - List applicablePaths = getApplicablePaths( - Collections.singleton(Pattern.compile(include)), - Collections.singleton(Pattern.compile(exclude))); + List applicablePaths = getApplicablePaths(Collections.singleton(include), Collections.singleton(exclude)); LOGGER.fine(" applicablePaths=" + applicablePaths); //NOI18N applicable = !applicablePaths.isEmpty(); applicableInitialized = true; @@ -305,7 +344,12 @@ * @param newState if selected or not */ public void setEnabled(boolean newState) { - enabled = newState; + if (enabled != newState) { + enabled = newState; + // reset cached patterns + includePatterns = null; + excludePatterns = null; + } } /** Just for debugging. */ @@ -368,8 +412,8 @@ private void resolveGroups(String dispName, String include, String exclude) { LOGGER.fine("resolveGroups include=" + include); //NOI18N List applicablePaths = getApplicablePaths( - Collections.singleton(Pattern.compile(include)), - Collections.singleton(Pattern.compile(exclude))); + Collections.singleton(include), + Collections.singleton(exclude)); Set groups = new HashSet(); Pattern p = Pattern.compile(include); for (String path : applicablePaths) { @@ -478,16 +522,80 @@ * @param excludePatterns exclude patterns * @return relative patsh which match include/exclude patterns */ - private List getApplicablePaths(Set includePatterns, Set excludePatterns) { + private List getApplicablePaths(Set includePatterns, Set excludePatterns) { List applicablePaths = new ArrayList(); for (String relativePath : getRelativePaths()) { - if (include(relativePath, includePatterns, excludePatterns)) { + if (matches(relativePath, includePatterns, excludePatterns)) { applicablePaths.add(relativePath); } } return applicablePaths; } + /** Copy files from source (zip or userdir) into target userdir or fip file + * according to current state of model. i.e. only include/exclude patterns from + * enabled items are considered. + * @throws IOException if copying fails + */ + private void copyFiles() throws IOException { + if (source.isFile()) { + try { + // zip file + copyZipFile(); + } catch (IOException ex) { + Exceptions.attachLocalizedMessage(ex, NbBundle.getMessage(OptionsExportModel.class, "OptionsExportModel.invalid.zipfile", source)); + Exceptions.printStackTrace(ex); + } + } else { + // userdir + copyFolder(source); + } + } + + /** Copy source zip file to target userdir obeying include/exclude patterns. + * @throws IOException if copying fails + */ + private void copyZipFile() throws IOException { + // Open the ZIP file + ZipFile zipFile = new ZipFile(source); + try { + // Enumerate each entry + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + if (!zipEntry.isDirectory()) { + copyFile(zipEntry.getName()); + } + } + } finally { + if (zipFile != null) { + zipFile.close(); + } + } + } + + /** Copy given folder to target userdir or zip file obeying include/exclude patterns. + * @param file folder to copy + * @throws IOException if copying fails + */ + private void copyFolder(File file) throws IOException { + String relativePath = getRelativePath(source, file); + if (IGNORED_FOLDERS.contains(relativePath)) { + return; + } + File[] children = file.listFiles(); + if (children == null) { + return; + } + for (File child : children) { + if (child.isDirectory()) { + copyFolder(child); + } else { + copyFile(getRelativePath(source, child)); + } + } + } + /** Returns list of file path relative to current source root. The source is * either zip file or userdir. It scans sub folders recursively. * @return list of file path relative to current source root @@ -517,24 +625,28 @@ * @param sourceRoot source root * @return list of file path relative to given source root */ - private static List getRelativePaths(File sourceRoot) { - List relativePaths = new ArrayList(); - getRelativePaths(sourceRoot, sourceRoot, relativePaths); - return relativePaths; + static List getRelativePaths(File sourceRoot) { + return getRelativePaths(sourceRoot, sourceRoot); } - private static void getRelativePaths(File source, File sourceRoot, List relativePaths) { - if (source.isDirectory()) { - File[] children = source.listFiles(); + private static List getRelativePaths(File root, File file) { + String relativePath = getRelativePath(root, file); + List result = new ArrayList(); + if (file.isDirectory()) { + if (IGNORED_FOLDERS.contains(relativePath)) { + return result; + } + File[] children = file.listFiles(); if (children == null) { - return; + return Collections.emptyList(); } for (File child : children) { - getRelativePaths(child, sourceRoot, relativePaths); + result.addAll(getRelativePaths(root, child)); } } else { - relativePaths.add(getRelativePath(sourceRoot, source)); + result.add(relativePath); } + return result; } /** Returns slash separated path relative to given root. */ @@ -555,20 +667,18 @@ * @return true if given relative path matches at least one of given include * patterns and doesn't match all exclude patterns, false otherwise */ - private static boolean include(String relativePath, Set includePatterns, Set excludePatterns) { + private static boolean matches(String relativePath, Set includePatterns, Set excludePatterns) { boolean include = false; - for (Pattern pattern : includePatterns) { - Matcher matcher = pattern.matcher(relativePath); - if (matcher.matches()) { + for (String pattern : includePatterns) { + if (matches(relativePath, pattern)) { include = true; break; } } if (include) { // check excludes - for (Pattern pattern : excludePatterns) { - Matcher matcher = pattern.matcher(relativePath); - if (matcher.matches()) { + for (String pattern : excludePatterns) { + if (matches(relativePath, pattern)) { return false; } } @@ -576,25 +686,180 @@ return include; } - /** Copy source file to target file. It creates necessary sub folders. - * @param sourceFile source file - * @param targetFile target file + /** Returns true if given relative path matches pattern. + * @param relativePath relative path + * @param pattern regex pattern. If contains #, only part before # is taken + * into account + * @return true if given relative path matches pattern. + */ + private static boolean matches(String relativePath, String pattern) { + if (pattern.contains("#")) { //NOI18N + pattern = pattern.split("#", 2)[0]; //NOI18N + } + return relativePath.matches(pattern); + } + + /** Returns set of keys matching given pattern. + * @param relativePath path relative to sourceRoot + * @param propertiesPattern pattern like file.properties#keyPattern + * @return set of matching keys, never null + * @throws IOException if properties cannot be loaded + */ + private Set matchingKeys(String relativePath, String propertiesPattern) throws IOException { + Set matchingKeys = new HashSet(); + String[] patterns = propertiesPattern.split("#", 2); + String filePattern = patterns[0]; + String keyPattern = patterns[1]; + if (relativePath.matches(filePattern)) { + if (currentProperties == null) { + currentProperties = getProperties(relativePath); + } + for (String key : currentProperties.keySet()) { + if (key.matches(keyPattern)) { + matchingKeys.add(key); + } + } + } + return matchingKeys; + } + + /** Copy file given by relative path from source zip or userdir to target + * userdir or zip file. It creates necessary sub folders. + * @param relativePath relative path * @throws java.io.IOException if copying fails */ - private static void copyFile(File sourceFile, File targetFile) throws IOException { - ensureParent(targetFile); - InputStream ins = null; - OutputStream out = null; + private void copyFile(String relativePath) throws IOException { + currentProperties = null; + boolean includeFile = false; // include? entire file + Set includeKeys = new HashSet(); + Set excludeKeys = new HashSet(); + for (String pattern : getIncludePatterns()) { + if (pattern.contains("#")) { //NOI18N + includeKeys.addAll(matchingKeys(relativePath, pattern)); + } else { + if (relativePath.matches(pattern)) { + includeFile = true; + includeKeys.clear(); // include entire file + break; + } + } + } + if (includeFile || !includeKeys.isEmpty()) { + // check excludes + for (String pattern : getExcludePatterns()) { + if (pattern.contains("#")) { //NOI18N + excludeKeys.addAll(matchingKeys(relativePath, pattern)); + } else { + if (relativePath.matches(pattern)) { + includeFile = false; + includeKeys.clear(); // exclude entire file + break; + } + } + } + } + LOGGER.log(Level.FINEST, "{0}, includeFile={1}, includeKeys={2}, excludeKeys={3}", new Object[]{relativePath, includeFile, includeKeys, excludeKeys}); //NOI18N + if (!includeFile && includeKeys.isEmpty()) { + // nothing matches + return; + } + + if (zipOutputStream != null) { // export to zip + LOGGER.log(Level.FINE, "Adding to zip: {0}", relativePath); //NOI18N + // Add ZIP entry to output stream. + zipOutputStream.putNextEntry(new ZipEntry(relativePath)); + // Transfer bytes from the file to the ZIP file + copyFileOrProperties(relativePath, includeKeys, excludeKeys, zipOutputStream); + // Complete the entry + zipOutputStream.closeEntry(); + } else { // import to userdir + OutputStream out = null; + try { + File targetFile = new File(targetUserdir, relativePath); + LOGGER.log(Level.FINE, "Path: {0}", relativePath); //NOI18N + ensureParent(targetFile); + out = new FileOutputStream(targetFile); + copyFileOrProperties(relativePath, includeKeys, excludeKeys, out); + } finally { + if (out != null) { + out.close(); + } + } + } + } + + /** Copy file from relative path in zip file or userdir to target OutputStream. + * It copies either entire file or just selected properties. + * @param relativePath relative path + * @param includeKeys keys to include + * @param excludeKeys keys to exclude + * @param out output stream + * @throws IOException if coping fails + */ + private void copyFileOrProperties(String relativePath, Set includeKeys, Set excludeKeys, OutputStream out) throws IOException { + if (includeKeys.isEmpty() && excludeKeys.isEmpty()) { + // copy entire file + copyFile(relativePath, out); + } else { + if (!includeKeys.isEmpty()) { + currentProperties.keySet().retainAll(includeKeys); + } + currentProperties.keySet().removeAll(excludeKeys); + // copy just selected properties + LOGGER.log(Level.FINE, " Only keys: {0}", currentProperties.keySet()); + currentProperties.store(out); + } + } + + /** Returns properties from relative path in zip or userdir. + * @param relativePath relative path + * @return properties from relative path in zip or userdir. + * @throws IOException if cannot open stream + */ + private EditableProperties getProperties(String relativePath) throws IOException { + EditableProperties properties = new EditableProperties(false); + InputStream in = null; try { - ins = new FileInputStream(sourceFile); - out = new FileOutputStream(targetFile); - FileUtil.copy(ins, out); + in = getInputStream(relativePath); + properties.load(in); } finally { - if (ins != null) { - ins.close(); + if (in != null) { + in.close(); } - if (out != null) { - out.close(); + } + return properties; + } + + /** Returns InputStream from relative path in zip file or userdir. + * @param relativePath relative path + * @return InputStream from relative path in zip file or userdir. + * @throws IOException if stream cannot be open + */ + private InputStream getInputStream(String relativePath) throws IOException { + if (source.isFile()) { + //zip file + ZipFile zipFile = new ZipFile(source); + ZipEntry zipEntry = zipFile.getEntry(relativePath); + return zipFile.getInputStream(zipEntry); + } else { + // userdir + return new FileInputStream(new File(source, relativePath)); + } + } + + /** Copy file from relative path in zip file or userdir to target OutputStream. + * @param relativePath relative path + * @param out output stream + * @throws java.io.IOException if copying fails + */ + private void copyFile(String relativePath, OutputStream out) throws IOException { + InputStream in = null; + try { + in = getInputStream(relativePath); + FileUtil.copy(in, out); + } finally { + if (in != null) { + in.close(); } } } @@ -609,52 +874,17 @@ } } - /** Extracts given zip file to target directory but only those files which - * match given list. - * @param sourceFile source zip file - * @param targetDir target directory - * @param applicablePaths list of files to be extracted - */ - private static void extractZipFile(File sourceFile, File targetDir, List applicablePaths) throws IOException { - ZipFile zipFile = new ZipFile(sourceFile); - Enumeration enumeration = zipFile.entries(); - while (enumeration.hasMoreElements()) { - ZipEntry zipEntry = (ZipEntry) enumeration.nextElement(); - if (zipEntry.isDirectory() || !applicablePaths.contains(zipEntry.getName())) { - // skip directories and not matching entries - continue; - } - LOGGER.fine("Extracting:" + zipEntry.getName()); //NOI18N - InputStream in = null; - FileOutputStream out = null; - try { - in = zipFile.getInputStream(zipEntry); - File outFile = new File(targetDir, zipEntry.getName()); - ensureParent(outFile); - out = new FileOutputStream(outFile); - FileUtil.copy(in, out); - } finally { - if (in != null) { - in.close(); - } - if (out != null) { - out.close(); - } - } - } - } - /** Returns list of paths from given zip file. * @param file zip file * @return list of paths from given zip file * @throws java.io.IOException */ - private static List listZipFile(File file) throws IOException { + static List listZipFile(File file) throws IOException { List relativePaths = new ArrayList(); // Open the ZIP file ZipFile zipFile = new ZipFile(file); // Enumerate each entry - Enumeration entries = zipFile.entries(); + Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = (ZipEntry) entries.nextElement(); if (!zipEntry.isDirectory()) { @@ -670,7 +900,7 @@ * @param relativePaths paths to be added to zip file * @throws java.io.IOException */ - private static void createZipFile(File targetFile, File sourceDir, List relativePaths) throws IOException { + static void createZipFile(File targetFile, File sourceDir, List relativePaths) throws IOException { ensureParent(targetFile); ZipOutputStream out = null; try { @@ -678,7 +908,7 @@ out = new ZipOutputStream(new FileOutputStream(targetFile)); // Compress the files for (String relativePath : relativePaths) { - LOGGER.fine("Adding to zip: " + relativePath); //NOI18N + LOGGER.finest("Adding to zip: " + relativePath); //NOI18N // Add ZIP entry to output stream. out.putNextEntry(new ZipEntry(relativePath)); // Transfer bytes from the file to the ZIP file diff --git a/options.api/test/unit/src/org/netbeans/modules/options/export/OptionsExportModelTest.java b/options.api/test/unit/src/org/netbeans/modules/options/export/OptionsExportModelTest.java new file mode 100644 --- /dev/null +++ b/options.api/test/unit/src/org/netbeans/modules/options/export/OptionsExportModelTest.java @@ -0,0 +1,482 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.modules.options.export; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.options.export.OptionsExportModel.Category; +import org.netbeans.modules.options.export.OptionsExportModel.Item; +import org.netbeans.modules.options.export.OptionsExportModel.State; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * @author Jiri Skrivanek + */ +public class OptionsExportModelTest extends NbTestCase { + + static OptionsExportModel model; + static File sourceUserdir; + static FileObject optionsExportFolder; + private static final Logger LOGGER = Logger.getLogger(OptionsExportModel.class.getName()); + + public OptionsExportModelTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + /** create source userdir with files + + sourceUserdir\dir0\subdir0\file0.properties + sourceUserdir\dir0\subdir0\file1.properties + sourceUserdir\dir0\subdir1\file0.properties + sourceUserdir\dir0\subdir1\file1.properties + sourceUserdir\dir1\subdir0\file0.properties + sourceUserdir\dir1\subdir0\file1.properties + sourceUserdir\dir1\subdir1\file0.properties + sourceUserdir\dir1\subdir1\file1.properties + */ + sourceUserdir = new File(getWorkDir(), "sourceUserdir"); + sourceUserdir.mkdir(); + for (int i = 0; i < 2; i++) { + File dir = new File(sourceUserdir, "dir" + i); + for (int j = 0; j < 2; j++) { + File subdir = new File(dir, "subdir" + j); + subdir.mkdirs(); + for (int k = 0; k < 2; k++) { + File file = new File(subdir, "file" + k + ".properties"); + file.createNewFile(); + Properties properties = new Properties(); + for (int l = 0; l < 3; l++) { + properties.setProperty("key" + l, "value" + l); + } + OutputStream out = new FileOutputStream(file); + properties.store(out, null); + out.close(); + } + } + } + } + + public void testExport() throws Exception { + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/.*", null} + }); + File targetZipFile = new File(getWorkDir(), "export.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + List expected = new ArrayList(); + expected.add("dir0/subdir0/file0.properties"); + expected.add("dir0/subdir0/file1.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/.*", null}, + {"Category0", "Item01", "dir1/subdir0/.*", null} + }); + targetZipFile = new File(getWorkDir(), "export1.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected.add("dir1/subdir0/file0.properties"); + expected.add("dir1/subdir0/file1.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/.*", null}, + {"Category0", "Item01", null, "dir0/subdir0/file1.*"} + }); + targetZipFile = new File(getWorkDir(), "export2.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected.clear(); + expected.add("dir0/subdir0/file0.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + } + + public void testExportProperties() throws Exception { + // only key1 from file0.properties and entire file file1.properties + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key1#|dir1/subdir1/file1[.]properties", null} + }); + File targetZipFile = new File(getWorkDir(), "export.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + List expected = new ArrayList(); + expected.add("dir0/subdir0/file0.properties"); + expected.add("dir1/subdir1/file1.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + List expectedKeys = new ArrayList(); + expectedKeys.add("key1"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetZipFile); + + // all keys from file0.properties inspite one pattern defines only key1 + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key1#|dir0/subdir0/file1[.]properties", null}, + {"Category0", "Item01", "dir0/subdir0/file0[.]properties|dir0/subdir1/file0[.]properties#key2", null} + }); + targetZipFile = new File(getWorkDir(), "export1.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected = Arrays.asList("dir0/subdir0/file0.properties", "dir0/subdir0/file1.properties", "dir0/subdir1/file0.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + expectedKeys = Arrays.asList("key0", "key1", "key2"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetZipFile); + assertProperties(expectedKeys, "dir0/subdir0/file1.properties", targetZipFile); + expectedKeys = Arrays.asList("key2"); + assertProperties(expectedKeys, "dir0/subdir1/file0.properties", targetZipFile); + + // 2 keys from 2 different files + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key1#|dir0/subdir0/file1[.]properties#key2", null},}); + targetZipFile = new File(getWorkDir(), "export2.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected = Arrays.asList("dir0/subdir0/file0.properties", "dir0/subdir0/file1.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + expectedKeys = Arrays.asList("key1"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetZipFile); + expectedKeys = Arrays.asList("key2"); + assertProperties(expectedKeys, "dir0/subdir0/file1.properties", targetZipFile); + + // 2 keys in the same file from different patterns + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key1#|dummy.*", null}, + {"Category0", "Item01", "dummy.*|dir0/subdir0/file0[.]properties#key2#", null} + }); + targetZipFile = new File(getWorkDir(), "export3.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected = Arrays.asList("dir0/subdir0/file0.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + expectedKeys = Arrays.asList("key1", "key2"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetZipFile); + + // key wildcard + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key.*#|dummy.*", null} + }); + targetZipFile = new File(getWorkDir(), "export4.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected = Arrays.asList("dir0/subdir0/file0.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + expectedKeys = Arrays.asList("key0", "key1", "key2"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetZipFile); + + // key exclude + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key.*", "dir0/subdir0/file0[.]properties#key1"}, + {"Category0", "Item01", "dir1/subdir1/file1[.]properties", null}, + {"Category0", "Item02", null, "dir1/subdir1/file1[.]properties#key1"} + }); + targetZipFile = new File(getWorkDir(), "export5.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected = Arrays.asList("dir0/subdir0/file0.properties", "dir1/subdir1/file1.properties"); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + expectedKeys = Arrays.asList("key0", "key2"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetZipFile); + assertProperties(expectedKeys, "dir1/subdir1/file1.properties", targetZipFile); + + // include key but exclude entire file => nothing exported + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key0", null}, + {"Category0", "Item01", null, "dir0/subdir0/file0[.]properties"} + }); + targetZipFile = new File(getWorkDir(), "export6.zip"); + model.doExport(targetZipFile); + LOGGER.finest(OptionsExportModel.listZipFile(targetZipFile).toString()); + expected = Collections.emptyList(); + assertFiles(expected, OptionsExportModel.listZipFile(targetZipFile)); + } + + public void testImport() throws Exception { + // import from source userdir to current userdir + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/.*", null} + }); + File targetUserdir = new File(getWorkDir(), "userdir"); + targetUserdir.mkdir(); + model.doImport(targetUserdir); + List expected = new ArrayList(); + expected.add("dir0/subdir0/file0.properties"); + expected.add("dir0/subdir0/file1.properties"); + assertFiles(expected, OptionsExportModel.getRelativePaths(targetUserdir)); + + // import from zipped userdir to current userdir + File sourceZipFile = new File(getWorkDir(), "sourceUserdir.zip"); + OptionsExportModel.createZipFile(sourceZipFile, sourceUserdir, OptionsExportModel.getRelativePaths(sourceUserdir)); + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/.*", null} + }, sourceZipFile); + targetUserdir = new File(getWorkDir(), "userdir1"); + model.doImport(targetUserdir); + assertFiles(expected, OptionsExportModel.getRelativePaths(targetUserdir)); + } + + public void testImportProperties() throws Exception { + // import from source userdir to current userdir + // only key1 from file0.properties and entire file file1.properties + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key1#|dir1/subdir1/file1[.]properties", null} + }); + File targetUserdir = new File(getWorkDir(), "userdir"); + targetUserdir.mkdir(); + model.doImport(targetUserdir); + List expected = new ArrayList(); + expected.add("dir0/subdir0/file0.properties"); + expected.add("dir1/subdir1/file1.properties"); + assertFiles(expected, OptionsExportModel.getRelativePaths(targetUserdir)); + List expectedKeys = new ArrayList(); + expectedKeys.add("key1"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetUserdir); + + // import from zipped userdir to current userdir + File sourceZipFile = new File(getWorkDir(), "sourceUserdir.zip"); + OptionsExportModel.createZipFile(sourceZipFile, sourceUserdir, OptionsExportModel.getRelativePaths(sourceUserdir)); + createModel(new String[][]{ + {"Category0", "Item00", "dir0/subdir0/file0[.]properties#key1#|dir1/subdir1/file1[.]properties", null} + }, sourceZipFile); + targetUserdir = new File(getWorkDir(), "userdir1"); + model.doImport(targetUserdir); + assertFiles(expected, OptionsExportModel.getRelativePaths(targetUserdir)); + expectedKeys = new ArrayList(); + expectedKeys.add("key1"); + assertProperties(expectedKeys, "dir0/subdir0/file0.properties", targetUserdir); + } + + /** Tests that categories and items properly change enabled/disabled state. */ + public void testEnablingDisabling() throws Exception { + createModel(new String[][]{ + {"Category0", "Item00", ".*", null}, + {"Category1", "Item00", ".*", null} + }); + Category category0 = model.getCategories().get(0); + category0.setState(State.DISABLED); + Category category1 = model.getCategories().get(1); + category1.setState(State.DISABLED); + assertEquals(OptionsExportModel.State.DISABLED, model.getState()); + + category0.setState(OptionsExportModel.State.ENABLED); + assertEquals(OptionsExportModel.State.ENABLED, category0.getState()); + assertEquals(OptionsExportModel.State.PARTIAL, model.getState()); + for (Item item : category0.getItems()) { + assertTrue(item.isEnabled()); + } + + category1.setState(OptionsExportModel.State.ENABLED); + assertEquals(OptionsExportModel.State.ENABLED, model.getState()); + + category0.setState(OptionsExportModel.State.DISABLED); + assertEquals(OptionsExportModel.State.DISABLED, category0.getState()); + assertEquals(OptionsExportModel.State.PARTIAL, model.getState()); + for (Item item : category0.getItems()) { + assertFalse(item.isEnabled()); + } + + category1.setState(OptionsExportModel.State.DISABLED); + assertEquals(OptionsExportModel.State.DISABLED, model.getState()); + } + + /** Tests patterns with properties key parts. */ + public void testKeyPattern() { + String pattern = "config/org-netbeans-api-project-libraries/Libraries/.*\\.xml|build\\.properties#nbplatform[.](?!default[.]netbeans[.]dest[.]dir).+[.].+=.+|var[.].*#"; + Set expected = new TreeSet(); + expected.add("config/org-netbeans-api-project-libraries/Libraries/.*\\.xml"); + expected.add("build\\.properties#nbplatform[.](?!default[.]netbeans[.]dest[.]dir).+[.].+=.+|var[.].*"); + assertPatterns(expected, OptionsExportModel.parsePattern(pattern)); + + pattern = "build\\.properties#nbplatform[.](?!default[.]netbeans[.]dest[.]dir).+[.].+=.+|var[.].*#|config/org-netbeans-api-project-libraries/Libraries/.*\\.xml"; + assertPatterns(expected, OptionsExportModel.parsePattern(pattern)); + + pattern = "build\\.properties#nbplatform|var|aaa#|BBB|CCC|DDD#EEE|FFF#"; + expected.clear(); + expected.add("build\\.properties#nbplatform|var|aaa"); + expected.add("BBB"); + expected.add("CCC"); + expected.add("DDD#EEE|FFF"); + assertPatterns(expected, OptionsExportModel.parsePattern(pattern)); + + // missing ending # never minds + pattern = "build\\.properties#nbplatform|var|aaa#|BBB|CCC|DDD#EEE|FFF"; + assertPatterns(expected, OptionsExportModel.parsePattern(pattern)); + } + + private void createModel(String[][] modelTemplate) throws Exception { + createModel(modelTemplate, sourceUserdir); + } + + /** Creates a new OptionsExportModel based on given template. Model looks + * like this: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param modelTemplate array of category display name, item display name, + * include pattern, exclude pattern + * @param source source userdir or zip file + * @throws Exception + */ + private void createModel(String[][] modelTemplate, File source) throws Exception { + FileObject root = FileUtil.getConfigRoot(); + optionsExportFolder = root.getFileObject("OptionsExport"); + if (optionsExportFolder != null) { + optionsExportFolder.delete(); + } + optionsExportFolder = FileUtil.createFolder(root, "OptionsExport"); + for (String[] fields : modelTemplate) { + String categoryName = fields[0]; + String itemName = fields[1]; + String include = fields[2]; + String exclude = fields[3]; + createCategory(categoryName, 0); + createItem(categoryName, itemName, include, exclude); + } + model = new OptionsExportModel(source); + // enable all and print model + for (OptionsExportModel.Category category : model.getCategories()) { + LOGGER.fine("category=" + category); //NOI18N + List items = category.getItems(); + for (OptionsExportModel.Item item : items) { + item.setEnabled(true); + LOGGER.fine(" item=" + item); //NOI18N + } + } + } + + /** Creates a new category in model. */ + private FileObject createCategory(String name, int position) throws Exception { + FileObject folder = FileUtil.createFolder(optionsExportFolder, name); + folder.setAttribute("displayName", name); + folder.setAttribute("position", position); + return folder; + } + + /** Creates a new item in model. */ + private FileObject createItem(String categoryName, String name, String include, String exclude) throws Exception { + FileObject categoryFO = optionsExportFolder.getFileObject(categoryName); + FileObject file = FileUtil.createData(categoryFO, name); + if (include != null) { + file.setAttribute("include", include); + } + if (exclude != null) { + file.setAttribute("exclude", exclude); + } + file.setAttribute("displayName", name); + return file; + } + + /** Assert files for export/import.*/ + private void assertFiles(List expected, List actual) { + if (actual.size() > 0 && actual.get(actual.size() - 1).equals("build.info")) { + // Skip build.info at the last position. + actual.remove(actual.size() - 1); + } + assertEquals("Wrong number of files filtered.", expected.size(), actual.size()); + Iterator iter = actual.iterator(); + for (String file : expected) { + assertEquals("Wrong file.", file, iter.next()); + } + } + + /** Assert properties for export/import. */ + private void assertProperties(List expectedKeys, String relativePath, File zipOrRootFile) throws Exception { + Properties properties = new Properties(); + properties.load(getInputStream(relativePath, zipOrRootFile)); + assertEquals("Wrong number of property keys.", expectedKeys.size(), properties.size()); + Iterator iter = new TreeSet(properties.keySet()).iterator(); + for (String key : expectedKeys) { + assertEquals("Wrong key.", key, iter.next()); + } + } + + private InputStream getInputStream(String relativePath, File zipOrRootFile) throws Exception { + if (zipOrRootFile.isFile()) { + //zip file + ZipFile zipFile = new ZipFile(zipOrRootFile); + ZipEntry zipEntry = zipFile.getEntry(relativePath); + return zipFile.getInputStream(zipEntry); + } else { + // userdir + return new FileInputStream(new File(zipOrRootFile, relativePath)); + } + } + + private void assertPatterns(Set expected, Set actual) { + assertEquals("Wrong number of patterns", expected.size(), actual.size()); + Iterator iter = new TreeSet(actual).iterator(); + for (String pattern : expected) { + assertEquals("Wrong pattern.", pattern, iter.next()); + } + } +} diff --git a/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml b/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml --- a/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml +++ b/project.libraries/src/org/netbeans/modules/project/libraries/resources/mf-layer.xml @@ -82,7 +82,7 @@ - +