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 extends ZipEntry> 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 extends ZipEntry> 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