Issue #66577: make EditableProperties available even without a dep on project.ant; generally useful. diff --git a/core.startup/src/org/netbeans/core/startup/preferences/NbPreferences.java b/core.startup/src/org/netbeans/core/startup/preferences/NbPreferences.java --- a/core.startup/src/org/netbeans/core/startup/preferences/NbPreferences.java +++ b/core.startup/src/org/netbeans/core/startup/preferences/NbPreferences.java @@ -42,11 +42,11 @@ package org.netbeans.core.startup.preferences; import java.io.IOException; -import java.util.Properties; import java.util.prefs.AbstractPreferences; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import org.openide.ErrorManager; +import org.openide.util.EditableProperties; import org.openide.util.RequestProcessor; /** @@ -57,7 +57,7 @@ private static Preferences USER_ROOT; private static Preferences SYSTEM_ROOT; - /*private*/Properties properties; + /*private*/EditableProperties properties; /*private*/FileStorage fileStorage; private static final RequestProcessor RP = new RequestProcessor(); @@ -181,9 +181,10 @@ } } - Properties properties() { + EditableProperties properties() { if (properties == null) { - properties = new Properties(/*loadDefaultProperties()*/); + properties = new EditableProperties(true); + //properties.putAll(loadDefaultProperties()); try { properties().putAll(fileStorage.load()); } catch (IOException ex) { @@ -264,8 +265,8 @@ boolean existsNode(); void removeNode() throws IOException; void markModified(); - Properties load() throws IOException; - void save(final Properties properties) throws IOException; + EditableProperties load() throws IOException; + void save(final EditableProperties properties) throws IOException; void runAtomic(Runnable run); } } diff --git a/core.startup/src/org/netbeans/core/startup/preferences/PropertiesStorage.java b/core.startup/src/org/netbeans/core/startup/preferences/PropertiesStorage.java --- a/core.startup/src/org/netbeans/core/startup/preferences/PropertiesStorage.java +++ b/core.startup/src/org/netbeans/core/startup/preferences/PropertiesStorage.java @@ -49,13 +49,13 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; -import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileSystem.AtomicAction; import org.openide.filesystems.FileUtil; +import org.openide.util.EditableProperties; import org.openide.util.Exceptions; /** @@ -91,8 +91,8 @@ return new String[0]; } - public @Override final Properties load() throws IOException { - return new Properties(); + public @Override final EditableProperties load() throws IOException { + return new EditableProperties(true); } protected @Override FileObject toPropertiesFile(boolean create) throws IOException { @@ -191,10 +191,10 @@ } } - public Properties load() throws IOException { + public EditableProperties load() throws IOException { Statistics.StopWatch sw = Statistics.getStopWatch(Statistics.LOAD, true); try { - Properties retval = new Properties(); + EditableProperties retval = new EditableProperties(true); FileObject file = toPropertiesFile(false); if (file != null) { try { @@ -215,7 +215,7 @@ } } - public void save(final Properties properties) throws IOException { + public void save(final EditableProperties properties) throws IOException { if (isModified) { Statistics.StopWatch sw = Statistics.getStopWatch(Statistics.FLUSH, true); try { @@ -224,7 +224,7 @@ OutputStream os = null; try { os = outputStream(); - properties.store(os, null); + properties.store(os); } finally { if (os != null) os.close(); } diff --git a/core.startup/test/unit/src/org/netbeans/core/startup/preferences/TestFileStorage.java b/core.startup/test/unit/src/org/netbeans/core/startup/preferences/TestFileStorage.java --- a/core.startup/test/unit/src/org/netbeans/core/startup/preferences/TestFileStorage.java +++ b/core.startup/test/unit/src/org/netbeans/core/startup/preferences/TestFileStorage.java @@ -42,7 +42,7 @@ package org.netbeans.core.startup.preferences; import java.io.IOException; -import java.util.Properties; +import org.openide.util.EditableProperties; /** * @@ -70,7 +70,7 @@ noFileRepresentationAssertion(); //load doesn't change file layout - Properties p = instance.load(); + EditableProperties p = instance.load(); p.put("key", "value");//NOI18N noFileRepresentationAssertion(); diff --git a/openide.util/apichanges.xml b/openide.util/apichanges.xml --- a/openide.util/apichanges.xml +++ b/openide.util/apichanges.xml @@ -49,6 +49,22 @@ Actions API + + + Copied API from project.ant for EditableProperties. + + + + + +

+ EditableProperties now available in Utilities API + so it can be used more broadly. +

+
+ + +
Added way to request that the platform restart. diff --git a/openide.util/nbproject/project.properties b/openide.util/nbproject/project.properties --- a/openide.util/nbproject/project.properties +++ b/openide.util/nbproject/project.properties @@ -42,7 +42,7 @@ module.jar.dir=lib cp.extra=${nb_all}/apisupport.harness/external/openjdk-javac-6-b12.jar -spec.version.base=7.25.0 +spec.version.base=7.26.0 # For XMLSerializer, needed for XMLUtil.write to work w/ namespaces under JDK 1.4: diff --git a/project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java b/openide.util/src/org/openide/util/EditableProperties.java copy from project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java copy to openide.util/src/org/openide/util/EditableProperties.java --- a/project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java +++ b/openide.util/src/org/openide/util/EditableProperties.java @@ -39,7 +39,7 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.spi.project.support.ant; +package org.openide.util; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -79,6 +79,7 @@ * Only (non-null) String is supported for keys and values. * This class is not thread-safe; use only from a single thread, or use {@link java.util.Collections#synchronizedMap}. * @author Jesse Glick, David Konecny + * @since org.openide.util 7.26 */ public final class EditableProperties extends AbstractMap implements Cloneable { @@ -106,15 +107,6 @@ private static final int READING_KEY_VALUE = 2; /** - * Creates empty instance whose items will not be alphabetized. - * @deprecated Use {@link #EditableProperties(boolean)} for clarity instead. - */ - @Deprecated - public EditableProperties() { - this(/* mentioned in #64174 - documented default */false); - } - - /** * Creates empty instance. * @param alphabetize alphabetize new items according to key or not */ @@ -125,17 +117,6 @@ } /** - * Creates instance from an existing map. No comments will be defined. - * Any order from the existing map will be retained, - * and further additions will not be alphabetized. - * @param map a map from String to String - */ - public EditableProperties(Map map) { - this(false); - putAll(map); - } - - /** * Creates new instance from an existing one. * @param ep an instance of EditableProperties */ diff --git a/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java b/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java copy from project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java copy to openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java --- a/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java +++ b/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java @@ -39,18 +39,15 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.spi.project.support.ant; +package org.openide.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; 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.net.URI; import java.net.URL; import java.util.Arrays; @@ -138,67 +135,6 @@ assertFile("Saved cloned properties must be the same as original one", filenameOfTestProperties(), dest, (String)null); } - // test that modifications changes only necessary parts - public void testVersionability() throws Exception { - clearWorkDir(); - - EditableProperties ep = loadTestProperties(); - - EditableProperties ep2 = ep.cloneProperties(); - ep2.setProperty("key24", "new value of key 24"); - String dest = getWorkDirPath()+File.separatorChar+"mod1.properties"; - saveProperties(ep2, dest); - int res[] = compare(filenameOfTestProperties(), dest); - assertEquals("One line modified", 1, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - ep2.setProperty("key23", "new value of key23"); - dest = getWorkDirPath()+File.separatorChar+"mod2.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("Four lines modified", 4, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - ep2.put("newkey", "new value"); - dest = getWorkDirPath()+File.separatorChar+"mod3.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("No lines modified", 0, res[0]); - assertEquals("One line added", 1, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - assertNotNull(ep2.get("key14")); - ep2.remove("key14"); - assertNull(ep2.get("key14")); - dest = getWorkDirPath()+File.separatorChar+"mod4.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("No lines modified", 0, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("Two lines removed", 2, res[2]); - - ep2 = ep.cloneProperties(); - ep2.setProperty("key21", new String[]{"first line;", "second line;", "third line"}); - dest = getWorkDirPath()+File.separatorChar+"mod5.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("Four lines modified", 4, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("No lines removed", 0, res[2]); - ep2.setProperty("key21", "first line;second line;third line"); - String dest2 = getWorkDirPath()+File.separatorChar+"mod6.properties"; - saveProperties(ep2, dest2); - res = compare(dest, dest2); - assertEquals("Four lines modified", 4, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("No lines removed", 0, res[2]); - } - // test that array values are stored correctly public void testArrayValues() throws Exception { EditableProperties ep = new EditableProperties(false); @@ -274,58 +210,6 @@ } // test that changing comments work and modify only comments - public void testComment() throws Exception { - clearWorkDir(); - - EditableProperties ep = loadTestProperties(); - - EditableProperties ep2 = ep.cloneProperties(); - ep2.setComment("key10", new String[]{"# this is new comment for property key 10"}, false); - String dest = getWorkDirPath()+File.separatorChar+"comment1.properties"; - saveProperties(ep2, dest); - int res[] = compare(filenameOfTestProperties(), dest); - assertEquals("No lines modified", 0, res[0]); - assertEquals("One line added", 1, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - ep2.setComment("key1", new String[]{"# new comment", "# new comment second line"}, true); - dest = getWorkDirPath()+File.separatorChar+"comment2.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("No lines modified", 0, res[0]); - assertEquals("Two lines added", 2, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - ep2.setComment("key26", new String[]{"# changed comment"}, false); - dest = getWorkDirPath()+File.separatorChar+"comment3.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("One line modified", 1, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - ep2.setComment("key25", new String[]{"# one line comment"}, false); - dest = getWorkDirPath()+File.separatorChar+"comment4.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("Two lines modified", 2, res[0]); - assertEquals("No lines added", 0, res[1]); - assertEquals("No lines removed", 0, res[2]); - - ep2 = ep.cloneProperties(); - ep2.setComment("key26", ep2.getComment("key26"), true); - dest = getWorkDirPath()+File.separatorChar+"comment5.properties"; - saveProperties(ep2, dest); - res = compare(filenameOfTestProperties(), dest); - assertEquals("No line modified", 0, res[0]); - assertEquals("One line added", 1, res[1]); - assertEquals("No lines removed", 0, res[2]); - - } - // test that misc chars are correctly escaped, unicode encoded, etc. public void testEscaping() throws Exception { String umlaut = "" + (char)252; @@ -472,23 +356,6 @@ } } - private int[] compare(String f1, String f2) throws Exception { - Reader r1 = null; - Reader r2 = null; - try { - r1 = new InputStreamReader(new FileInputStream(f1), "ISO-8859-1"); - r2 = new InputStreamReader(new FileInputStream(f2), "ISO-8859-1"); - return AntBasedTestUtil.countTextDiffs(r1, r2); - } finally { - if (r1 != null) { - r1.close(); - } - if (r2 != null) { - r2.close(); - } - } - } - private String getAsString(EditableProperties ep) throws Exception { ByteArrayOutputStream os = new ByteArrayOutputStream(); ep.store(os); diff --git a/project.ant/nbproject/project.xml b/project.ant/nbproject/project.xml --- a/project.ant/nbproject/project.xml +++ b/project.ant/nbproject/project.xml @@ -166,7 +166,7 @@ - 7.15 + 7.26 diff --git a/project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java b/project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java --- a/project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java +++ b/project.ant/src/org/netbeans/spi/project/support/ant/EditableProperties.java @@ -41,29 +41,13 @@ package org.netbeans.spi.project.support.ant; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; -// XXX: consider adding getInitialComment() and setInitialComment() methods -// (useful e.g. for GeneratedFilesHelper) - /** * Similar to {@link java.util.Properties} but designed to retain additional * information needed for safe hand-editing. @@ -78,32 +62,15 @@ * The file format (including encoding etc.) is compatible with the regular JRE implementation. * Only (non-null) String is supported for keys and values. * This class is not thread-safe; use only from a single thread, or use {@link java.util.Collections#synchronizedMap}. - * @author Jesse Glick, David Konecny + *

This class exists here only for historical reasons. It delegates to {@link org.openide.util.EditableProperties}. */ public final class EditableProperties extends AbstractMap implements Cloneable { - - /** List of Item instances as read from the properties file. Order is important. - * Saving properties will save then in this order. */ - private final LinkedList items; - /** Map of [property key, Item instance] for faster access. */ - private final Map itemIndex; + private final org.openide.util.EditableProperties delegate; - private final boolean alphabetize; - - private static final String keyValueSeparators = "=: \t\r\n\f"; - - private static final String strictKeyValueSeparators = "=:"; - - private static final String whiteSpaceChars = " \t\r\n\f"; - - private static final String commentChars = "#!"; - - private static final String INDENT = " "; - - // parse states: - private static final int WAITING_FOR_KEY_VALUE = 1; - private static final int READING_KEY_VALUE = 2; + private EditableProperties(org.openide.util.EditableProperties delegate) { + this.delegate = delegate; + } /** * Creates empty instance whose items will not be alphabetized. @@ -119,9 +86,7 @@ * @param alphabetize alphabetize new items according to key or not */ public EditableProperties(boolean alphabetize) { - this.alphabetize = alphabetize; - items = new LinkedList(); - itemIndex = new HashMap(); + this(new org.openide.util.EditableProperties(alphabetize)); } /** @@ -136,29 +101,13 @@ } /** - * Creates new instance from an existing one. - * @param ep an instance of EditableProperties - */ - private EditableProperties(EditableProperties ep) { - // #64174: use a simple deep copy for speed - alphabetize = ep.alphabetize; - items = new LinkedList(); - itemIndex = new HashMap(ep.items.size() * 4 / 3 + 1); - for (Item _i : ep.items) { - Item i = (Item) _i.clone(); - items.add(i); - itemIndex.put(i.getKey(), i); - } - } - - /** * Returns a set view of the mappings ordered according to their file * position. Each element in this set is a Map.Entry. See * {@link AbstractMap#entrySet} for more details. * @return set with Map.Entry instances. */ public Set> entrySet() { - return new SetImpl(this); + return delegate.entrySet(); } /** @@ -167,45 +116,7 @@ * @throws IOException if the contents are malformed or the stream could not be read */ public void load(InputStream stream) throws IOException { - int state = WAITING_FOR_KEY_VALUE; - BufferedReader input = new BufferedReader(new InputStreamReader(stream, "ISO-8859-1")); - List tempList = new LinkedList(); - String line; - int commentLinesCount = 0; - // Read block of lines and create instance of Item for each. - // Separator is: either empty line or valid end of proeprty declaration - while (null != (line = input.readLine())) { - tempList.add(line); - boolean empty = isEmpty(line); - boolean comment = isComment(line); - if (state == WAITING_FOR_KEY_VALUE) { - if (empty) { - // empty line: create Item without any key - createNonKeyItem(tempList); - commentLinesCount = 0; - } else { - if (comment) { - commentLinesCount++; - } else { - state = READING_KEY_VALUE; - } - } - } - if (state == READING_KEY_VALUE && !isContinue(line)) { - // valid end of property declaration: create Item for it - createKeyItem(tempList, commentLinesCount); - state = WAITING_FOR_KEY_VALUE; - commentLinesCount = 0; - } - } - if (tempList.size() > 0) { - if (state == READING_KEY_VALUE) { - // value was not ended correctly? ignore. - createKeyItem(tempList, commentLinesCount); - } else { - createNonKeyItem(tempList); - } - } + delegate.load(stream); } /** @@ -214,41 +125,12 @@ * @throws IOException if the stream could not be written to */ public void store(OutputStream stream) throws IOException { - boolean previousLineWasEmpty = true; - BufferedWriter output = new BufferedWriter(new OutputStreamWriter(stream, "ISO-8859-1")); - for (Item item : items) { - if (item.isSeparate() && !previousLineWasEmpty) { - output.newLine(); - } - String line = null; - Iterator it = item.getRawData().iterator(); - while (it.hasNext()) { - line = it.next(); - output.write(line); - output.newLine(); - } - if (line != null) { - previousLineWasEmpty = isEmpty(line); - } - } - output.flush(); + delegate.store(stream); } @Override public String put(String key, String value) { - if (key == null || value == null) { - throw new NullPointerException(); - } - Item item = itemIndex.get(key); - String result = null; - if (item != null) { - result = item.getValue(); - item.setValue(value); - } else { - item = new Item(key, value); - addItem(item, alphabetize); - } - return result; + return delegate.put(key, value); } /** @@ -258,7 +140,7 @@ * @return the property value, or null if it was not defined */ public String getProperty(String key) { - return get(key); + return delegate.getProperty(key); } /** @@ -269,7 +151,7 @@ * @return previous value of the property or null if there was not any */ public String setProperty(String key, String value) { - return put(key, value); + return delegate.setProperty(key, value); } /** @@ -284,18 +166,7 @@ * @return previous value of the property or null if there was not any */ public String setProperty(String key, String[] value) { - String result = get(key); - if (key == null || value == null) { - throw new NullPointerException(); - } - List valueList = Arrays.asList(value); - Item item = itemIndex.get(key); - if (item != null) { - item.setValue(valueList); - } else { - addItem(new Item(key, valueList), alphabetize); - } - return result; + return delegate.setProperty(key, value); } /** @@ -309,11 +180,7 @@ * delimiter character is included */ public String[] getComment(String key) { - Item item = itemIndex.get(key); - if (item == null) { - return new String[0]; - } - return item.getComment(); + return delegate.getComment(key); } /** @@ -329,12 +196,7 @@ * item by empty line */ public void setComment(String key, String[] comment, boolean separate) { - // XXX: check validity of comment parameter - Item item = itemIndex.get(key); - if (item == null) { - throw new IllegalArgumentException("Cannot set comment for non-existing property "+key); - } - item.setComment(comment, separate); + delegate.setComment(key, comment, separate); } @Override @@ -347,535 +209,7 @@ * @return a clone of this object */ public EditableProperties cloneProperties() { - return new EditableProperties(this); + return new EditableProperties(delegate.cloneProperties()); } - // non-key item is block of empty lines/comment not associated with any property - private void createNonKeyItem(List lines) { - // First check that previous item is not non-key item. - if (!items.isEmpty()) { - Item item = items.getLast(); - if (item.getKey() == null) { - // it is non-key item: merge them - item.addCommentLines(lines); - lines.clear(); - return; - } - } - // create new non-key item - Item item = new Item(lines); - addItem(item, false); - lines.clear(); - } - - // opposite to non-key item: item with valid property declaration and - // perhaps some comment lines - private void createKeyItem(List lines, int commentLinesCount) { - Item item = new Item(lines.subList(0, commentLinesCount), lines.subList(commentLinesCount, lines.size())); - addItem(item, false); - lines.clear(); - } - - private void addItem(Item item, boolean sort) { - String key = item.getKey(); - if (sort) { - assert key != null; - ListIterator it = items.listIterator(); - while (it.hasNext()) { - String k = it.next().getKey(); - if (k != null && k.compareToIgnoreCase(key) > 0) { - it.previous(); - it.add(item); - itemIndex.put(key, item); - return; - } - } - } - items.add(item); - if (key != null) { - itemIndex.put(key, item); - } - } - - // does property declaration continue on next line? - private boolean isContinue(String line) { - int index = line.length() - 1; - int slashCount = 0; - while (index >= 0 && line.charAt(index) == '\\') { - slashCount++; - index--; - } - // if line ends with odd number of backslash then property definition - // continues on next line - return (slashCount % 2 != 0); - } - - // does line start with comment delimiter? (whitespaces are ignored) - private static boolean isComment(String line) { - line = trimLeft(line); - if (line.length() != 0 && commentChars.indexOf(line.charAt(0)) != -1) { - return true; - } else { - return false; - } - } - - // is line empty? (whitespaces are ignored) - private static boolean isEmpty(String line) { - return trimLeft(line).length() == 0; - } - - // remove all whitespaces from left - private static String trimLeft(String line) { - int start = 0; - while (start < line.length()) { - if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) { - break; - } - start++; - } - return line.substring(start); - } - - /** - * Representation of one item read from properties file. It can be either - * valid property declaration with associated comment or chunk of empty - * lines or lines with comment which are not associated with any property. - */ - private static class Item implements Cloneable { - - /** Lines of comment as read from properties file and as they will be - * written back to properties file. */ - private List commentLines; - - /** Lines with property name and value declaration as read from - * properties file and as they will be written back to properties file. */ - private List keyValueLines; - - /** Property key */ - private String key; - - /** Property value */ - private String value; - - /** Should this property be separated from previous one by at least - * one empty line. */ - private boolean separate; - - // constructor only for cloning - private Item() { - } - - /** - * Create instance which does not have any key and value - just - * some empty or comment lines. This item is READ-ONLY. - */ - public Item(List commentLines) { - this.commentLines = new ArrayList(commentLines); - } - - /** - * Create instance from the lines of comment and property declaration. - * Property name and value will be split. - */ - public Item(List commentLines, List keyValueLines) { - this.commentLines = new ArrayList(commentLines); - this.keyValueLines = new ArrayList(keyValueLines); - parse(keyValueLines); - } - - /** - * Create new instance with key and value. - */ - public Item(String key, String value) { - this.key = key; - this.value = value; - } - - /** - * Create new instance with key and value. - */ - public Item(String key, List value) { - this.key = key; - setValue(value); - } - - // backdoor for merging non-key items - void addCommentLines(List lines) { - assert key == null; - commentLines.addAll(lines); - } - - public String[] getComment() { - String[] res = new String[commentLines.size()]; - for (int i = 0; i < res.length; i++) { - // #60249: the comment might have Unicode chars in escapes. - res[i] = decodeUnicode(commentLines.get(i)); - } - return res; - } - - public void setComment(String[] commentLines, boolean separate) { - this.separate = separate; - this.commentLines = new ArrayList(commentLines.length); - for (int i = 0; i < commentLines.length; i++) { - // #60249 again - write only ISO-8859-1. - this.commentLines.add(encodeUnicode(commentLines[i])); - } - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - keyValueLines = null; - } - - public void setValue(List value) { - StringBuffer val = new StringBuffer(); - List l = new ArrayList(); - if (!value.isEmpty()) { - l.add(encode(key, true) + "=\\"); // NOI18N - Iterator it = value.iterator(); - while (it.hasNext()) { - String s = it.next(); - val.append(s); - s = encode(s, false); - l.add(it.hasNext() ? INDENT + s + '\\' : INDENT + s); // NOI18N - } - } else { - // #45061: for no vals, use just "prop=" - l.add(encode(key, true) + '='); // NOI18N - } - this.value = val.toString(); - keyValueLines = l; - } - - public boolean isSeparate() { - return separate; - } - - /** - * Returns persistent image of this property. - */ - public List getRawData() { - List l = new ArrayList(); - if (commentLines != null) { - l.addAll(commentLines); - } - if (keyValueLines == null) { - keyValueLines = new ArrayList(); - if (key != null && value != null) { - keyValueLines.add(encode(key, true)+"="+encode(value, false)); - } - } - l.addAll(keyValueLines); - return l; - } - - private void parse(List keyValueLines) { - // merge lines into one: - String line = mergeLines(keyValueLines); - // split key and value - splitKeyValue(line); - } - - private String mergeLines(List lines) { - String line = ""; // XXX use StringBuilder instead - Iterator it = lines.iterator(); - while (it.hasNext()) { - String l = trimLeft(it.next()); - // if this is not the last line then remove last backslash - if (it.hasNext()) { - assert l.endsWith("\\") : lines; - l = l.substring(0, l.length()-1); - } - line += l; - } - return line; - } - - private void splitKeyValue(String line) { - int separatorIndex = 0; - while (separatorIndex < line.length()) { - char ch = line.charAt(separatorIndex); - if (ch == '\\') { - // ignore next one character - separatorIndex++; - } else { - if (keyValueSeparators.indexOf(ch) != -1) { - break; - } - } - separatorIndex++; - } - key = decode(line.substring(0, separatorIndex)); - line = trimLeft(line.substring(separatorIndex)); - if (line.length() == 0) { - value = ""; - return; - } - if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) { - line = trimLeft(line.substring(1)); - } - value = decode(line); - } - - private static String decode(String input) { - char ch; - int len = input.length(); - StringBuffer output = new StringBuffer(len); - for (int x=0; xlen) { - // unicode character not finished? syntax error: ignore - output.append(input.substring(x-1)); - x += 4; - continue; - } - String val = input.substring(x+1, x+5); - try { - output.append((char)Integer.parseInt(val, 16)); - } catch (NumberFormatException e) { - // #46234: handle gracefully - output.append(input.substring(x - 1, x + 5)); - } - x += 4; - } else { - if (ch == 't') ch = '\t'; - else if (ch == 'r') ch = '\r'; - else if (ch == 'n') ch = '\n'; - else if (ch == 'f') ch = '\f'; - output.append(ch); - } - } - return output.toString(); - } - - private static String encode(String input, boolean escapeSpace) { - int len = input.length(); - StringBuffer output = new StringBuffer(len*2); - - for(int x=0; x 0x007e)) { - output.append("\\u"); - String hex = Integer.toHexString(ch); - for (int i = 0; i < 4 - hex.length(); i++) { - output.append('0'); - } - output.append(hex); - } else { - output.append(ch); - } - } - } - return output.toString(); - } - - private static String decodeUnicode(String input) { - char ch; - int len = input.length(); - StringBuffer output = new StringBuffer(len); - for (int x = 0; x < len; x++) { - ch = input.charAt(x); - if (ch != '\\') { - output.append(ch); - continue; - } - x++; - if (x==len) { - // backslash at the end? syntax error: ignore it - continue; - } - ch = input.charAt(x); - if (ch == 'u') { - if (x+5>len) { - // unicode character not finished? syntax error: ignore - output.append(input.substring(x-1)); - x += 4; - continue; - } - String val = input.substring(x+1, x+5); - try { - output.append((char)Integer.parseInt(val, 16)); - } catch (NumberFormatException e) { - // #46234: handle gracefully - output.append(input.substring(x - 1, x + 5)); - } - x += 4; - } else { - output.append(ch); - } - } - return output.toString(); - } - - private static String encodeUnicode(String input) { - int len = input.length(); - StringBuffer output = new StringBuffer(len * 2); - for (int x = 0; x < len; x++) { - char ch = input.charAt(x); - if ((ch < 0x0020) || (ch > 0x007e)) { - output.append("\\u"); // NOI18N - String hex = Integer.toHexString(ch); - for (int i = 0; i < 4 - hex.length(); i++) { - output.append('0'); - } - output.append(hex); - } else { - output.append(ch); - } - } - return output.toString(); - } - - @Override - public Object clone() { - Item item = new Item(); - if (keyValueLines != null) { - item.keyValueLines = new ArrayList(keyValueLines); - } - if (commentLines != null) { - item.commentLines = new ArrayList(commentLines); - } - item.key = key; - item.value = value; - item.separate = separate; - return item; - } - - } - - private static class SetImpl extends AbstractSet> { - - private EditableProperties props; - - public SetImpl(EditableProperties props) { - this.props = props; - } - - public Iterator> iterator() { - return new IteratorImpl(props); - } - - public int size() { - return props.items.size(); - } - - } - - private static class IteratorImpl implements Iterator> { - - private final EditableProperties props; - private ListIterator delegate; - - public IteratorImpl(EditableProperties props) { - this.props = props; - delegate = props.items.listIterator(); - } - - public boolean hasNext() { - return findNext() != null; - } - - public Map.Entry next() { - Item item = findNext(); - if (item == null) { - throw new NoSuchElementException(); - } - delegate.next(); - return new MapEntryImpl(item); - } - - public void remove() { - delegate.previous(); - Item item = findNext(); - if (item == null) { - throw new IllegalStateException(); - } - int index = delegate.nextIndex(); - props.items.remove(item); - props.itemIndex.remove(item.getKey()); - delegate = props.items.listIterator(index); - } - - private Item findNext() { - while (delegate.hasNext()) { - Item item = delegate.next(); - if (item.getKey() != null && item.getValue() != null) { - // Found one. Back up! - delegate.previous(); - return item; - } - } - return null; - } - - } - - private static class MapEntryImpl implements Map.Entry { - - private Item item; - - public MapEntryImpl(Item item) { - this.item = item; - } - - public String getKey() { - return item.getKey(); - } - - public String getValue() { - return item.getValue(); - } - - public String setValue(String value) { - String result = item.getValue(); - item.setValue(value); - return result; - } - - } - } diff --git a/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java b/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java --- a/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java +++ b/project.ant/test/unit/src/org/netbeans/spi/project/support/ant/EditablePropertiesTest.java @@ -41,8 +41,6 @@ package org.netbeans.spi.project.support.ant; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -53,91 +51,15 @@ import java.io.Reader; import java.net.URI; import java.net.URL; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; import org.netbeans.junit.NbTestCase; +// here because AntBasedTestUtil.countTextDiffs cannot be moved to openide.util public class EditablePropertiesTest extends NbTestCase { public EditablePropertiesTest(String name) { super(name); } - public void testLoad() throws Exception { - Map content = new HashMap(); - for (int i=1; i<=26; i++) { - content.put("key"+i, "value"+i); - } - content.put("@!#$%^keyA", "valueA!@#$%^&*(){}"); - content.put(" =:keyB", "valueB =:"); - content.put(""+(char)0x1234+"keyC", "valueC"+(char)0x9876); - content.put("keyD", ""); - content.put("keyE", ""); - content.put("keyF", ""); - content.put("keyG", ""); - content.put("keyH", "value#this is not comment"); - content.put("keyI", "incorrect end: \\u123"); - // #46234: does not handle bad Unicode escapes well - content.put("keyJ", "malformed Unicode escape: \\uabyz"); - - EditableProperties ep = loadTestProperties(); - - for (Map.Entry entry : content.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - String epValue = ep.getProperty(key); - assertEquals("Expected value for key "+key+" is different", value, epValue); - } - int count = 0; - for (Map.Entry entry : ep.entrySet()) { - if (entry.getKey() != null) { - count++; - } - } - assertEquals("Number of items in property file", content.keySet().size(), count); - } - - /* Doesn't work; java.util.Properties throws IAE for malformed Unicode escapes: - public void testJavaUtilPropertiesEquivalence() throws Exception { - Properties p = loadTestJavaUtilProperties(); - EditableProperties ep = loadTestProperties(); - Iterator it = p.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - String key = (String) entry.getKey(); - String val = (String) entry.getValue(); - assertEquals("right value for " + key, val, ep.getProperty(key)); - } - assertEquals("right number of items", p.size(), ep.size()); - } - */ - - public void testSave() throws Exception { - clearWorkDir(); - EditableProperties ep = loadTestProperties(); - String dest = getWorkDirPath()+File.separatorChar+"new.properties"; - saveProperties(ep, dest); - assertFile("Saved properties must be the same as original one", filenameOfTestProperties(), dest, (String)null); - } - - public void testClonability() throws Exception { - clearWorkDir(); - EditableProperties ep = loadTestProperties(); - - EditableProperties ep2 = ep.cloneProperties(); - String dest = getWorkDirPath()+File.separatorChar+"new2.properties"; - saveProperties(ep2, dest); - assertFile("Saved cloned properties must be the same as original one", filenameOfTestProperties(), dest, (String)null); - - EditableProperties ep3 = (EditableProperties)ep.clone(); - dest = getWorkDirPath()+File.separatorChar+"new3.properties"; - saveProperties(ep3, dest); - assertFile("Saved cloned properties must be the same as original one", filenameOfTestProperties(), dest, (String)null); - } - // test that modifications changes only necessary parts public void testVersionability() throws Exception { clearWorkDir(); @@ -199,80 +121,6 @@ assertEquals("No lines removed", 0, res[2]); } - // test that array values are stored correctly - public void testArrayValues() throws Exception { - EditableProperties ep = new EditableProperties(false); - ep.setProperty("key1", new String[]{"1. line;", "2. line;", "3. line"}); - ep.setProperty("key2", "1. line;2. line;3. line"); - String output = getAsString(ep); - String expected = - "key1=\\"+System.getProperty("line.separator")+ - " 1. line;\\"+System.getProperty("line.separator")+ - " 2. line;\\"+System.getProperty("line.separator")+ - " 3. line"+System.getProperty("line.separator")+ - "key2=1. line;2. line;3. line"+System.getProperty("line.separator"); - assertEquals(expected, output); - assertEquals(ep.getProperty("key1"), "1. line;2. line;3. line"); - assertEquals(ep.getProperty("key2"), "1. line;2. line;3. line"); - ep.setProperty("key1", "one; two; three"); - output = getAsString(ep); - expected = - "key1=one; two; three"+System.getProperty("line.separator")+ - "key2=1. line;2. line;3. line"+System.getProperty("line.separator"); - assertEquals(expected, output); - assertEquals(ep.getProperty("key1"), "one; two; three"); - assertEquals(ep.getProperty("key2"), "1. line;2. line;3. line"); - ep.setProperty("key2", new String[]{"1. line;", "2. line;", "3. line", "one;", "more;", "line;"}); - ep.setProperty("key", new String[0]); - output = getAsString(ep); - expected = - "key1=one; two; three"+System.getProperty("line.separator")+ - "key2=\\"+System.getProperty("line.separator")+ - " 1. line;\\"+System.getProperty("line.separator")+ - " 2. line;\\"+System.getProperty("line.separator")+ - " 3. line\\"+System.getProperty("line.separator")+ - " one;\\"+System.getProperty("line.separator")+ - " more;\\"+System.getProperty("line.separator")+ - " line;"+System.getProperty("line.separator")+ - "key="+System.getProperty("line.separator"); // #45061 - assertEquals(expected, output); - assertEquals(ep.getProperty("key1"), "one; two; three"); - assertEquals(ep.getProperty("key2"), "1. line;2. line;3. lineone;more;line;"); - assertEquals(ep.getProperty("key"), ""); - } - - public void testSorting() throws Exception { - EditableProperties ep = new EditableProperties(false); - ep.setProperty("a", "val-a"); - ep.setProperty("c", "val-c"); - ep.put("b", "val-b"); - String output = getAsString(ep); - String expected = "a=val-a"+System.getProperty("line.separator")+"c=val-c"+ - System.getProperty("line.separator")+"b=val-b"+ - System.getProperty("line.separator"); - assertEquals(expected, output); - - ep = new EditableProperties(false); - ep.setProperty("a", "val-a"); - ep.setProperty("c", "val-c"); - ep.put("b", "val-b"); - output = getAsString(ep); - expected = "a=val-a"+System.getProperty("line.separator")+"c=val-c"+ - System.getProperty("line.separator")+"b=val-b"+ - System.getProperty("line.separator"); - assertEquals(expected, output); - - ep = new EditableProperties(true); - ep.setProperty("a", "val-a"); - ep.setProperty("c", "val-c"); - ep.put("b", "val-b"); - output = getAsString(ep); - expected = "a=val-a"+System.getProperty("line.separator")+"b=val-b"+ - System.getProperty("line.separator")+"c=val-c"+ - System.getProperty("line.separator"); - assertEquals(expected, output); - } - // test that changing comments work and modify only comments public void testComment() throws Exception { clearWorkDir(); @@ -326,108 +174,6 @@ } - // test that misc chars are correctly escaped, unicode encoded, etc. - public void testEscaping() throws Exception { - String umlaut = "" + (char)252; - EditableProperties ep = new EditableProperties(false); - ep.setProperty("a a", "a space a"); - ep.setProperty("b"+(char)0x4567, "val"+(char)0x1234); - ep.setProperty("@!#$%^\\", "!@#$%^&*(){}\\"); - ep.setProperty("d\nd", "d\nnewline\nd"); - ep.setProperty("umlaut", umlaut); - ep.setProperty("_a a", new String[]{"a space a"}); - ep.setProperty("_b"+(char)0x4567, new String[]{"val"+(char)0x1234}); - ep.setProperty("_@!#$%^\\", new String[]{"!@#$%^&*\\", "(){}\\"}); - ep.setProperty("_d\nd", new String[]{"d\nnew","line\nd", "\n", "end"}); - ep.setProperty("_umlaut", new String[]{umlaut, umlaut}); - String output = getAsString(ep); - String expected = "a\\ a=a space a"+System.getProperty("line.separator")+ - "b\\u4567=val\\u1234"+System.getProperty("line.separator")+ - "@!#$%^\\\\=!@#$%^&*(){}\\\\"+System.getProperty("line.separator")+ - "d\\nd=d\\nnewline\\nd"+System.getProperty("line.separator")+ - "umlaut=\\u00fc"+System.getProperty("line.separator")+ - "_a\\ a=\\"+System.getProperty("line.separator")+" a space a"+System.getProperty("line.separator")+ - "_b\\u4567=\\"+System.getProperty("line.separator")+" val\\u1234"+System.getProperty("line.separator")+ - "_@!#$%^\\\\=\\"+System.getProperty("line.separator")+" !@#$%^&*\\\\\\"+System.getProperty("line.separator")+ - " (){}\\\\"+System.getProperty("line.separator")+ - "_d\\nd=\\"+System.getProperty("line.separator")+" d\\nnew\\"+System.getProperty("line.separator")+ - " line\\nd\\"+System.getProperty("line.separator")+ - " \\n\\"+System.getProperty("line.separator")+ - " end"+System.getProperty("line.separator")+ - "_umlaut=\\" +System.getProperty("line.separator")+" \\u00fc\\"+System.getProperty("line.separator")+ - " \\u00fc"+System.getProperty("line.separator"); - assertEquals(expected, output); - assertEquals("a space a", ep.getProperty("a a")); - assertEquals("val"+(char)0x1234, ep.getProperty("b"+(char)0x4567)); - assertEquals("!@#$%^&*(){}\\", ep.getProperty("@!#$%^\\")); - assertEquals("d\nnewline\nd", ep.getProperty("d\nd")); - assertEquals(umlaut, ep.getProperty("umlaut")); - assertEquals("a space a", ep.getProperty("_a a")); - assertEquals("val"+(char)0x1234, ep.getProperty("_b"+(char)0x4567)); - assertEquals("!@#$%^&*\\(){}\\", ep.getProperty("_@!#$%^\\")); - assertEquals("d\nnewline\nd\nend", ep.getProperty("_d\nd")); - assertEquals(umlaut+umlaut, ep.getProperty("_umlaut")); - } - - // test that iterator implementation is OK - public void testIterator() throws Exception { - EditableProperties ep = loadTestProperties(); - Iterator> it1 = ep.entrySet().iterator(); - while (it1.hasNext()) { - it1.next(); - } - Iterator it2 = ep.keySet().iterator(); - while (it2.hasNext()) { - it2.next(); - } - it2 = ep.keySet().iterator(); - while (it2.hasNext()) { - it2.next(); - it2.remove(); - } - ep.put("a", "aval"); - ep.remove("a"); - ep = loadTestProperties(); - it1 = ep.entrySet().iterator(); - while (it1.hasNext()) { - Map.Entry entry = it1.next(); - assertNotNull("Property key cannot be null", entry.getKey()); - assertNotNull("Property value cannot be null", entry.getValue()); - entry.setValue(entry.getValue()+"-something-new"); - } - it1 = ep.entrySet().iterator(); - while (it1.hasNext()) { - it1.next(); - it1.remove(); - } - } - - // test that syntax errors are survived - public void testInvalidPropertiesFile() throws Exception { - String invalidProperty = "key=value without correct end\\"; - ByteArrayInputStream is = new ByteArrayInputStream(invalidProperty.getBytes()); - EditableProperties ep = new EditableProperties(false); - ep.load(is); - assertEquals("Syntax error should be resolved", 1, ep.keySet().size()); - assertEquals("value without correct end", ep.getProperty("key")); - } - - public void testNonLatinComments() throws Exception { - // #60249. - String lsep = System.getProperty("line.separator"); - EditableProperties p = new EditableProperties(false); - p.setProperty("k", "v"); - p.setComment("k", new String[] {"# \u0158ekni koment teda!"}, false); - String expected = "# \\u0158ekni koment teda!" + lsep + "k=v" + lsep; - assertEquals("Storing non-Latin chars in comments works", expected, getAsString(p)); - p = new EditableProperties(false); - p.load(new ByteArrayInputStream(expected.getBytes("ISO-8859-1"))); - assertEquals("Reading non-Latin chars in comments works", Collections.singletonList("# \u0158ekni koment teda!"), Arrays.asList(p.getComment("k"))); - p.setProperty("k", "v2"); - expected = "# \\u0158ekni koment teda!" + lsep + "k=v2" + lsep; - assertEquals("Reading and re-writing non-Latin chars in comments works", expected, getAsString(p)); - } - // helper methods: @@ -449,20 +195,6 @@ return ep; } - /* - private Properties loadTestJavaUtilProperties() throws IOException { - URL u = EditablePropertiesTest.class.getResource("data/test.properties"); - Properties p = new Properties(); - InputStream is = u.openStream(); - try { - p.load(is); - } finally { - is.close(); - } - return p; - } - */ - private void saveProperties(EditableProperties ep, String path) throws Exception { OutputStream os = new FileOutputStream(path); try { @@ -489,11 +221,4 @@ } } - private String getAsString(EditableProperties ep) throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - ep.store(os); - os.close(); - return os.toString("ISO-8859-1"); - } - }