Issue #66577: make EditableProperties available even without a dep on project.ant; generally useful. release68_m1_base
authorJesse Glick <jglick@netbeans.org>
Wed, 29 Jul 2009 11:49:18 -0400
changeset 80824416cc11045
parent 807 51a1c9914d38
child 809 3070cec745eb
child 930 4b93a80b1aef
Issue #66577: make EditableProperties available even without a dep on project.ant; generally useful.
openide.util/apichanges.xml
openide.util/nbproject/project.properties
openide.util/src/org/openide/util/EditableProperties.java
openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java
openide.util/test/unit/src/org/openide/util/data/test.properties
     1.1 --- a/openide.util/apichanges.xml	Tue Jul 28 10:13:45 2009 -0400
     1.2 +++ b/openide.util/apichanges.xml	Wed Jul 29 11:49:18 2009 -0400
     1.3 @@ -49,6 +49,22 @@
     1.4      <apidef name="actions">Actions API</apidef>
     1.5  </apidefs>
     1.6  <changes>
     1.7 +    <change id="EditableProperties">
     1.8 +        <api name="util"/>
     1.9 +        <summary>Copied API from <code>project.ant</code> for <code>EditableProperties</code>.</summary>
    1.10 +        <version major="7" minor="26"/>
    1.11 +        <date day="29" month="7" year="2009"/>
    1.12 +        <author login="jglick"/>
    1.13 +        <compatibility addition="yes"/>
    1.14 +        <description>
    1.15 +            <p>
    1.16 +                 <code>EditableProperties</code> now available in Utilities API
    1.17 +                 so it can be used more broadly.
    1.18 +            </p>
    1.19 +        </description>
    1.20 +        <class package="org.openide.util" name="EditableProperties"/>
    1.21 +        <issue number="66577"/>
    1.22 +    </change>
    1.23      <change id="LifecycleManager.markForRestart">
    1.24          <api name="util"/>
    1.25          <summary>Added way to request that the platform restart.</summary>
     2.1 --- a/openide.util/nbproject/project.properties	Tue Jul 28 10:13:45 2009 -0400
     2.2 +++ b/openide.util/nbproject/project.properties	Wed Jul 29 11:49:18 2009 -0400
     2.3 @@ -42,7 +42,7 @@
     2.4  module.jar.dir=lib
     2.5  cp.extra=${nb_all}/apisupport.harness/external/openjdk-javac-6-b12.jar
     2.6  
     2.7 -spec.version.base=7.25.0
     2.8 +spec.version.base=7.26.0
     2.9  
    2.10  # For XMLSerializer, needed for XMLUtil.write to work w/ namespaces under JDK 1.4:
    2.11  
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/openide.util/src/org/openide/util/EditableProperties.java	Wed Jul 29 11:49:18 2009 -0400
     3.3 @@ -0,0 +1,861 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
     3.8 + *
     3.9 + * The contents of this file are subject to the terms of either the GNU
    3.10 + * General Public License Version 2 only ("GPL") or the Common
    3.11 + * Development and Distribution License("CDDL") (collectively, the
    3.12 + * "License"). You may not use this file except in compliance with the
    3.13 + * License. You can obtain a copy of the License at
    3.14 + * http://www.netbeans.org/cddl-gplv2.html
    3.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.16 + * specific language governing permissions and limitations under the
    3.17 + * License.  When distributing the software, include this License Header
    3.18 + * Notice in each file and include the License file at
    3.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    3.20 + * particular file as subject to the "Classpath" exception as provided
    3.21 + * by Sun in the GPL Version 2 section of the License file that
    3.22 + * accompanied this code. If applicable, add the following below the
    3.23 + * License Header, with the fields enclosed by brackets [] replaced by
    3.24 + * your own identifying information:
    3.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    3.26 + *
    3.27 + * Contributor(s):
    3.28 + *
    3.29 + * The Original Software is NetBeans. The Initial Developer of the Original
    3.30 + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
    3.31 + * Microsystems, Inc. All Rights Reserved.
    3.32 + *
    3.33 + * If you wish your version of this file to be governed by only the CDDL
    3.34 + * or only the GPL Version 2, indicate your decision by adding
    3.35 + * "[Contributor] elects to include this software in this distribution
    3.36 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.37 + * single choice of license, a recipient has the option to distribute
    3.38 + * your version of this file under either the CDDL, the GPL Version 2 or
    3.39 + * to extend the choice of license to its licensees as provided above.
    3.40 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    3.41 + * Version 2 license, then the option applies only if the new code is
    3.42 + * made subject to such option by the copyright holder.
    3.43 + */
    3.44 +
    3.45 +package org.openide.util;
    3.46 +
    3.47 +import java.io.BufferedReader;
    3.48 +import java.io.BufferedWriter;
    3.49 +import java.io.IOException;
    3.50 +import java.io.InputStream;
    3.51 +import java.io.InputStreamReader;
    3.52 +import java.io.OutputStream;
    3.53 +import java.io.OutputStreamWriter;
    3.54 +import java.util.AbstractMap;
    3.55 +import java.util.AbstractSet;
    3.56 +import java.util.ArrayList;
    3.57 +import java.util.Arrays;
    3.58 +import java.util.HashMap;
    3.59 +import java.util.Iterator;
    3.60 +import java.util.LinkedList;
    3.61 +import java.util.List;
    3.62 +import java.util.ListIterator;
    3.63 +import java.util.Map;
    3.64 +import java.util.NoSuchElementException;
    3.65 +import java.util.Set;
    3.66 +
    3.67 +// XXX: consider adding getInitialComment() and setInitialComment() methods
    3.68 +// (useful e.g. for GeneratedFilesHelper)
    3.69 +
    3.70 +/**
    3.71 + * Similar to {@link java.util.Properties} but designed to retain additional
    3.72 + * information needed for safe hand-editing.
    3.73 + * Useful for various <samp>*.properties</samp> in a project:
    3.74 + * <ol>
    3.75 + * <li>Can associate comments with particular entries.
    3.76 + * <li>Order of entries preserved during modifications whenever possible.
    3.77 + * <li>VCS-friendly: lines which are not semantically modified are not textually modified.
    3.78 + * <li>Can automatically insert line breaks in new or modified values at positions
    3.79 + *     that are likely to be semantically meaningful, e.g. between path components
    3.80 + * </ol>
    3.81 + * The file format (including encoding etc.) is compatible with the regular JRE implementation.
    3.82 + * Only (non-null) String is supported for keys and values.
    3.83 + * This class is not thread-safe; use only from a single thread, or use {@link java.util.Collections#synchronizedMap}.
    3.84 + * @author Jesse Glick, David Konecny
    3.85 + * @since org.openide.util 7.26
    3.86 + */
    3.87 +public final class EditableProperties extends AbstractMap<String,String> implements Cloneable {
    3.88 +    
    3.89 +    /** List of Item instances as read from the properties file. Order is important.
    3.90 +     * Saving properties will save then in this order. */
    3.91 +    private final LinkedList<Item> items;
    3.92 +
    3.93 +    /** Map of [property key, Item instance] for faster access. */
    3.94 +    private final Map<String,Item> itemIndex;
    3.95 +
    3.96 +    private final boolean alphabetize;
    3.97 +    
    3.98 +    private static final String keyValueSeparators = "=: \t\r\n\f";
    3.99 +
   3.100 +    private static final String strictKeyValueSeparators = "=:";
   3.101 +
   3.102 +    private static final String whiteSpaceChars = " \t\r\n\f";
   3.103 +
   3.104 +    private static final String commentChars = "#!";
   3.105 +    
   3.106 +    private static final String INDENT = "    ";
   3.107 +
   3.108 +    // parse states:
   3.109 +    private static final int WAITING_FOR_KEY_VALUE = 1;
   3.110 +    private static final int READING_KEY_VALUE = 2;
   3.111 +    
   3.112 +    /**
   3.113 +     * Creates empty instance.
   3.114 +     * @param alphabetize alphabetize new items according to key or not
   3.115 +     */
   3.116 +    public EditableProperties(boolean alphabetize) {
   3.117 +        this.alphabetize = alphabetize;
   3.118 +        items = new LinkedList<Item>();
   3.119 +        itemIndex = new HashMap<String,Item>();
   3.120 +    }
   3.121 +    
   3.122 +    /**
   3.123 +     * Creates new instance from an existing one.
   3.124 +     * @param ep an instance of EditableProperties
   3.125 +     */
   3.126 +    private EditableProperties(EditableProperties ep) {
   3.127 +        // #64174: use a simple deep copy for speed
   3.128 +        alphabetize = ep.alphabetize;
   3.129 +        items = new LinkedList<Item>();
   3.130 +        itemIndex = new HashMap<String,Item>(ep.items.size() * 4 / 3 + 1);
   3.131 +        for (Item _i : ep.items) {
   3.132 +            Item i = (Item) _i.clone();
   3.133 +            items.add(i);
   3.134 +            itemIndex.put(i.getKey(), i);
   3.135 +        }
   3.136 +    }
   3.137 +    
   3.138 +    /**
   3.139 +     * Returns a set view of the mappings ordered according to their file 
   3.140 +     * position.  Each element in this set is a Map.Entry. See
   3.141 +     * {@link AbstractMap#entrySet} for more details.
   3.142 +     * @return set with Map.Entry instances.
   3.143 +     */
   3.144 +    public Set<Map.Entry<String,String>> entrySet() {
   3.145 +        return new SetImpl(this);
   3.146 +    }
   3.147 +    
   3.148 +    /**
   3.149 +     * Load properties from a stream.
   3.150 +     * @param stream an input stream
   3.151 +     * @throws IOException if the contents are malformed or the stream could not be read
   3.152 +     */
   3.153 +    public void load(InputStream stream) throws IOException {
   3.154 +        int state = WAITING_FOR_KEY_VALUE;
   3.155 +        BufferedReader input = new BufferedReader(new InputStreamReader(stream, "ISO-8859-1"));
   3.156 +        List<String> tempList = new LinkedList<String>();
   3.157 +        String line;
   3.158 +        int commentLinesCount = 0;
   3.159 +        // Read block of lines and create instance of Item for each.
   3.160 +        // Separator is: either empty line or valid end of proeprty declaration
   3.161 +        while (null != (line = input.readLine())) {
   3.162 +            tempList.add(line);
   3.163 +            boolean empty = isEmpty(line);
   3.164 +            boolean comment = isComment(line);
   3.165 +            if (state == WAITING_FOR_KEY_VALUE) {
   3.166 +                if (empty) {
   3.167 +                    // empty line: create Item without any key
   3.168 +                    createNonKeyItem(tempList);
   3.169 +                    commentLinesCount = 0;
   3.170 +                } else {
   3.171 +                    if (comment) {
   3.172 +                        commentLinesCount++;
   3.173 +                    } else {
   3.174 +                        state = READING_KEY_VALUE;
   3.175 +                    }
   3.176 +                }
   3.177 +            }
   3.178 +            if (state == READING_KEY_VALUE && !isContinue(line)) {
   3.179 +                // valid end of property declaration: create Item for it
   3.180 +                createKeyItem(tempList, commentLinesCount);
   3.181 +                state = WAITING_FOR_KEY_VALUE;
   3.182 +                commentLinesCount = 0;
   3.183 +            }
   3.184 +        }
   3.185 +        if (tempList.size() > 0) {
   3.186 +            if (state == READING_KEY_VALUE) {
   3.187 +                // value was not ended correctly? ignore.
   3.188 +                createKeyItem(tempList, commentLinesCount);
   3.189 +            } else {
   3.190 +                createNonKeyItem(tempList);
   3.191 +            }
   3.192 +        }
   3.193 +    }
   3.194 +
   3.195 +    /**
   3.196 +     * Store properties to a stream.
   3.197 +     * @param stream an output stream
   3.198 +     * @throws IOException if the stream could not be written to
   3.199 +     */
   3.200 +    public void store(OutputStream stream) throws IOException {
   3.201 +        boolean previousLineWasEmpty = true;
   3.202 +        BufferedWriter output = new BufferedWriter(new OutputStreamWriter(stream, "ISO-8859-1"));
   3.203 +        for (Item item : items) {
   3.204 +            if (item.isSeparate() && !previousLineWasEmpty) {
   3.205 +                output.newLine();
   3.206 +            }
   3.207 +            String line = null;
   3.208 +            Iterator<String> it = item.getRawData().iterator();
   3.209 +            while (it.hasNext()) {
   3.210 +                line = it.next();
   3.211 +                output.write(line);
   3.212 +                output.newLine();
   3.213 +            }
   3.214 +            if (line != null) {
   3.215 +                previousLineWasEmpty = isEmpty(line);
   3.216 +            }
   3.217 +        }
   3.218 +        output.flush();
   3.219 +    }
   3.220 +
   3.221 +    @Override
   3.222 +    public String put(String key, String value) {
   3.223 +        Parameters.notNull("key", key);
   3.224 +        Parameters.notNull(key, value);
   3.225 +        Item item = itemIndex.get(key);
   3.226 +        String result = null;
   3.227 +        if (item != null) {
   3.228 +            result = item.getValue();
   3.229 +            item.setValue(value);
   3.230 +        } else {
   3.231 +            item = new Item(key, value);
   3.232 +            addItem(item, alphabetize);
   3.233 +        }
   3.234 +        return result;
   3.235 +    }
   3.236 +
   3.237 +    /**
   3.238 +     * Convenience method to get a property as a string.
   3.239 +     * Same as {@link #get}; only here because of pre-generic code.
   3.240 +     * @param key a property name; cannot be null nor empty
   3.241 +     * @return the property value, or null if it was not defined
   3.242 +     */
   3.243 +    public String getProperty(String key) {
   3.244 +        return get(key);
   3.245 +    }
   3.246 +    
   3.247 +    /**
   3.248 +     * Convenience method to set a property.
   3.249 +     * Same as {@link #put}; only here because of pre-generic code.
   3.250 +     * @param key a property name; cannot be null nor empty
   3.251 +     * @param value the desired value; cannot be null
   3.252 +     * @return previous value of the property or null if there was not any
   3.253 +     */
   3.254 +    public String setProperty(String key, String value) {
   3.255 +        return put(key, value);
   3.256 +    }
   3.257 +
   3.258 +    /**
   3.259 +     * Sets a property to a value broken into segments for readability.
   3.260 +     * Same behavior as {@link #setProperty(String,String)} with the difference that each item
   3.261 +     * will be stored on its own line of text. {@link #getProperty} will simply concatenate
   3.262 +     * all the items into one string, so generally separators
   3.263 +     * (such as <samp>:</samp> for path-like properties) must be included in
   3.264 +     * the items (for example, at the end of all but the last item).
   3.265 +     * @param key a property name; cannot be null nor empty
   3.266 +     * @param value the desired value; cannot be null; can be empty array
   3.267 +     * @return previous value of the property or null if there was not any
   3.268 +     */
   3.269 +    public String setProperty(String key, String[] value) {
   3.270 +        String result = get(key);
   3.271 +        if (key == null || value == null) {
   3.272 +            throw new NullPointerException();
   3.273 +        }
   3.274 +        List<String> valueList = Arrays.asList(value);
   3.275 +        Item item = itemIndex.get(key);
   3.276 +        if (item != null) {
   3.277 +            item.setValue(valueList);
   3.278 +        } else {
   3.279 +            addItem(new Item(key, valueList), alphabetize);
   3.280 +        }
   3.281 +        return result;
   3.282 +    }
   3.283 +
   3.284 +    /**
   3.285 +     * Returns comment associated with the property. The comment lines are
   3.286 +     * returned as defined in properties file, that is comment delimiter is
   3.287 +     * included. Comment for property is defined as: continuous block of lines
   3.288 +     * starting with comment delimiter which are followed by property
   3.289 +     * declaration (no empty line separator allowed).
   3.290 +     * @param key a property name; cannot be null nor empty
   3.291 +     * @return array of String lines as specified in properties file; comment
   3.292 +     *    delimiter character is included
   3.293 +     */
   3.294 +    public String[] getComment(String key) {
   3.295 +        Item item = itemIndex.get(key);
   3.296 +        if (item == null) {
   3.297 +            return new String[0];
   3.298 +        }
   3.299 +        return item.getComment();
   3.300 +    }
   3.301 +
   3.302 +    /**
   3.303 +     * Create comment for the property.
   3.304 +     * <p>Note: if a comment includes non-ISO-8859-1 characters, they will be written
   3.305 +     * to disk using Unicode escapes (and {@link #getComment} will interpret
   3.306 +     * such escapes), but of course they will be unreadable for humans.
   3.307 +     * @param key a property name; cannot be null nor empty
   3.308 +     * @param comment lines of comment which will be written just above
   3.309 +     *    the property; no reformatting; comment lines must start with 
   3.310 +     *    comment delimiter; cannot be null; cannot be emty array
   3.311 +     * @param separate whether the comment should be separated from previous
   3.312 +     *    item by empty line
   3.313 +     */
   3.314 +    public void setComment(String key, String[] comment, boolean separate) {
   3.315 +        // XXX: check validity of comment parameter
   3.316 +        Item item = itemIndex.get(key);
   3.317 +        if (item == null) {
   3.318 +            throw new IllegalArgumentException("Cannot set comment for non-existing property "+key);
   3.319 +        }
   3.320 +        item.setComment(comment, separate);
   3.321 +    }
   3.322 +
   3.323 +    @Override
   3.324 +    public Object clone() {
   3.325 +        return cloneProperties();
   3.326 +    }
   3.327 +    
   3.328 +    /**
   3.329 +     * Create an exact copy of this properties object.
   3.330 +     * @return a clone of this object
   3.331 +     */
   3.332 +    public EditableProperties cloneProperties() {
   3.333 +        return new EditableProperties(this);
   3.334 +    }
   3.335 +
   3.336 +    // non-key item is block of empty lines/comment not associated with any property
   3.337 +    private void createNonKeyItem(List<String> lines) {
   3.338 +        // First check that previous item is not non-key item.
   3.339 +        if (!items.isEmpty()) {
   3.340 +            Item item = items.getLast();
   3.341 +            if (item.getKey() == null) {
   3.342 +                // it is non-key item:  merge them
   3.343 +                item.addCommentLines(lines);
   3.344 +                lines.clear();
   3.345 +                return;
   3.346 +            }
   3.347 +        }
   3.348 +        // create new non-key item
   3.349 +        Item item = new Item(lines);
   3.350 +        addItem(item, false);
   3.351 +        lines.clear();
   3.352 +    }
   3.353 +
   3.354 +    // opposite to non-key item: item with valid property declaration and 
   3.355 +    // perhaps some comment lines
   3.356 +    private void createKeyItem(List<String> lines, int commentLinesCount) {
   3.357 +        Item item = new Item(lines.subList(0, commentLinesCount), lines.subList(commentLinesCount, lines.size()));
   3.358 +        addItem(item, false);
   3.359 +        lines.clear();
   3.360 +    }
   3.361 +    
   3.362 +    private void addItem(Item item, boolean sort) {
   3.363 +        String key = item.getKey();
   3.364 +        if (sort) {
   3.365 +            assert key != null;
   3.366 +            ListIterator<Item> it = items.listIterator();
   3.367 +            while (it.hasNext()) {
   3.368 +                String k = it.next().getKey();
   3.369 +                if (k != null && k.compareToIgnoreCase(key) > 0) {
   3.370 +                    it.previous();
   3.371 +                    it.add(item);
   3.372 +                    itemIndex.put(key, item);
   3.373 +                    return;
   3.374 +                }
   3.375 +            }
   3.376 +        }
   3.377 +        items.add(item);
   3.378 +        if (key != null) {
   3.379 +            itemIndex.put(key, item);
   3.380 +        }
   3.381 +    }
   3.382 +    
   3.383 +    // does property declaration continue on next line?
   3.384 +    private boolean isContinue(String line) {
   3.385 +        int index = line.length() - 1;
   3.386 +        int slashCount = 0;
   3.387 +        while (index >= 0 && line.charAt(index) == '\\') {
   3.388 +            slashCount++;
   3.389 +            index--;
   3.390 +        }
   3.391 +        // if line ends with odd number of backslash then property definition 
   3.392 +        // continues on next line
   3.393 +        return (slashCount % 2 != 0);
   3.394 +    }
   3.395 +    
   3.396 +    // does line start with comment delimiter? (whitespaces are ignored)
   3.397 +    private static boolean isComment(String line) {
   3.398 +        line = trimLeft(line);
   3.399 +        if (line.length() != 0 && commentChars.indexOf(line.charAt(0)) != -1) {
   3.400 +            return true;
   3.401 +        } else {
   3.402 +            return false;
   3.403 +        }
   3.404 +    }
   3.405 +
   3.406 +    // is line empty? (whitespaces are ignored)
   3.407 +    private static boolean isEmpty(String line) {
   3.408 +        return trimLeft(line).length() == 0;
   3.409 +    }
   3.410 +
   3.411 +    // remove all whitespaces from left
   3.412 +    private static String trimLeft(String line) {
   3.413 +        int start = 0;
   3.414 +        while (start < line.length()) {
   3.415 +            if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) {
   3.416 +                break;
   3.417 +            }
   3.418 +            start++;
   3.419 +        }
   3.420 +        return line.substring(start);
   3.421 +    }
   3.422 +    
   3.423 +    /**
   3.424 +     * Representation of one item read from properties file. It can be either
   3.425 +     * valid property declaration with associated comment or chunk of empty
   3.426 +     * lines or lines with comment which are not associated with any property.
   3.427 +     */
   3.428 +    private static class Item implements Cloneable {
   3.429 +
   3.430 +        /** Lines of comment as read from properties file and as they will be
   3.431 +         * written back to properties file. */
   3.432 +        private List<String> commentLines;
   3.433 +
   3.434 +        /** Lines with property name and value declaration as read from 
   3.435 +         * properties file and as they will be written back to properties file. */
   3.436 +        private List<String> keyValueLines;
   3.437 +
   3.438 +        /** Property key */
   3.439 +        private String key;
   3.440 +        
   3.441 +        /** Property value */
   3.442 +        private String value;
   3.443 +
   3.444 +        /** Should this property be separated from previous one by at least
   3.445 +         * one empty line. */
   3.446 +        private boolean separate;
   3.447 +        
   3.448 +        // constructor only for cloning
   3.449 +        private Item() {
   3.450 +        }
   3.451 +        
   3.452 +        /**
   3.453 +         * Create instance which does not have any key and value - just 
   3.454 +         * some empty or comment lines. This item is READ-ONLY.
   3.455 +         */
   3.456 +        public Item(List<String> commentLines) {
   3.457 +            this.commentLines = new ArrayList<String>(commentLines);
   3.458 +        }
   3.459 +
   3.460 +        /**
   3.461 +         * Create instance from the lines of comment and property declaration.
   3.462 +         * Property name and value will be split.
   3.463 +         */
   3.464 +        public Item(List<String> commentLines, List<String> keyValueLines) {
   3.465 +            this.commentLines = new ArrayList<String>(commentLines);
   3.466 +            this.keyValueLines = new ArrayList<String>(keyValueLines);
   3.467 +            parse(keyValueLines);
   3.468 +        }
   3.469 +
   3.470 +        /**
   3.471 +         * Create new instance with key and value.
   3.472 +         */
   3.473 +        public Item(String key, String value) {
   3.474 +            this.key = key;
   3.475 +            this.value = value;
   3.476 +        }
   3.477 +
   3.478 +        /**
   3.479 +         * Create new instance with key and value.
   3.480 +         */
   3.481 +        public Item(String key, List<String> value) {
   3.482 +            this.key = key;
   3.483 +            setValue(value);
   3.484 +        }
   3.485 +
   3.486 +        // backdoor for merging non-key items
   3.487 +        void addCommentLines(List<String> lines) {
   3.488 +            assert key == null;
   3.489 +            commentLines.addAll(lines);
   3.490 +        }
   3.491 +        
   3.492 +        public String[] getComment() {
   3.493 +            String[] res = new String[commentLines.size()];
   3.494 +            for (int i = 0; i < res.length; i++) {
   3.495 +                // #60249: the comment might have Unicode chars in escapes.
   3.496 +                res[i] = decodeUnicode(commentLines.get(i));
   3.497 +            }
   3.498 +            return res;
   3.499 +        }
   3.500 +        
   3.501 +        public void setComment(String[] commentLines, boolean separate) {
   3.502 +            this.separate = separate;
   3.503 +            this.commentLines = new ArrayList<String>(commentLines.length);
   3.504 +            for (int i = 0; i < commentLines.length; i++) {
   3.505 +                // #60249 again - write only ISO-8859-1.
   3.506 +                this.commentLines.add(encodeUnicode(commentLines[i]));
   3.507 +            }
   3.508 +        }
   3.509 +        
   3.510 +        public String getKey() {
   3.511 +            return key;
   3.512 +        }
   3.513 +        
   3.514 +        public String getValue() {
   3.515 +            return value;
   3.516 +        }
   3.517 +
   3.518 +        public void setValue(String value) {
   3.519 +            this.value = value;
   3.520 +            keyValueLines = null;
   3.521 +        }
   3.522 +
   3.523 +        public void setValue(List<String> value) {
   3.524 +            StringBuffer val = new StringBuffer();
   3.525 +            List<String> l = new ArrayList<String>();
   3.526 +            if (!value.isEmpty()) {
   3.527 +                l.add(encode(key, true) + "=\\"); // NOI18N
   3.528 +                Iterator<String> it = value.iterator();
   3.529 +                while (it.hasNext()) {
   3.530 +                    String s = it.next();
   3.531 +                    val.append(s);
   3.532 +                    s = encode(s, false);
   3.533 +                    l.add(it.hasNext() ? INDENT + s + '\\' : INDENT + s); // NOI18N
   3.534 +                }
   3.535 +            } else {
   3.536 +                // #45061: for no vals, use just "prop="
   3.537 +                l.add(encode(key, true) + '='); // NOI18N
   3.538 +            }
   3.539 +            this.value = val.toString();
   3.540 +            keyValueLines = l;
   3.541 +        }
   3.542 +
   3.543 +        public boolean isSeparate() {
   3.544 +            return separate;
   3.545 +        }
   3.546 +
   3.547 +        /**
   3.548 +         * Returns persistent image of this property.
   3.549 +         */
   3.550 +        public List<String> getRawData() {
   3.551 +            List<String> l = new ArrayList<String>();
   3.552 +            if (commentLines != null) {
   3.553 +                l.addAll(commentLines);
   3.554 +            }
   3.555 +            if (keyValueLines == null) {
   3.556 +                keyValueLines = new ArrayList<String>();
   3.557 +                if (key != null && value != null) {
   3.558 +                    keyValueLines.add(encode(key, true)+"="+encode(value, false));
   3.559 +                }
   3.560 +            }
   3.561 +            l.addAll(keyValueLines);
   3.562 +            return l;
   3.563 +        }
   3.564 +        
   3.565 +        private void parse(List<String> keyValueLines) {
   3.566 +            // merge lines into one:
   3.567 +            String line = mergeLines(keyValueLines);
   3.568 +            // split key and value
   3.569 +            splitKeyValue(line);
   3.570 +        }
   3.571 +        
   3.572 +        private String mergeLines(List<String> lines) {
   3.573 +            StringBuilder line = new StringBuilder();
   3.574 +            Iterator<String> it = lines.iterator();
   3.575 +            while (it.hasNext()) {
   3.576 +                String l = trimLeft(it.next());
   3.577 +                // if this is not the last line then remove last backslash
   3.578 +                if (it.hasNext()) {
   3.579 +                    assert l.endsWith("\\") : lines;
   3.580 +                    l = l.substring(0, l.length()-1);
   3.581 +                }
   3.582 +                line.append(l);
   3.583 +            }
   3.584 +            return line.toString();
   3.585 +        }
   3.586 +        
   3.587 +        private void splitKeyValue(String line) {
   3.588 +            int separatorIndex = 0;
   3.589 +            while (separatorIndex < line.length()) {
   3.590 +                char ch = line.charAt(separatorIndex);
   3.591 +                if (ch == '\\') {
   3.592 +                    // ignore next one character
   3.593 +                    separatorIndex++;
   3.594 +                } else {
   3.595 +                    if (keyValueSeparators.indexOf(ch) != -1) {
   3.596 +                        break;
   3.597 +                    }
   3.598 +                }
   3.599 +                separatorIndex++;
   3.600 +            }
   3.601 +            key = decode(line.substring(0, separatorIndex));
   3.602 +            line = trimLeft(line.substring(separatorIndex));
   3.603 +            if (line.length() == 0) {
   3.604 +                value = "";
   3.605 +                return;
   3.606 +            }
   3.607 +            if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) {
   3.608 +                line = trimLeft(line.substring(1));
   3.609 +            }
   3.610 +            value = decode(line);
   3.611 +        }
   3.612 +        
   3.613 +        private static String decode(String input) {
   3.614 +            char ch;
   3.615 +            int len = input.length();
   3.616 +            StringBuffer output = new StringBuffer(len);
   3.617 +            for (int x=0; x<len; x++) {
   3.618 +                ch = input.charAt(x);
   3.619 +                if (ch != '\\') {
   3.620 +                    output.append(ch);
   3.621 +                    continue;
   3.622 +                }
   3.623 +                x++;
   3.624 +                if (x==len) {
   3.625 +                    // backslash at the end? syntax error: ignore it
   3.626 +                    continue;
   3.627 +                }
   3.628 +                ch = input.charAt(x);
   3.629 +                if (ch == 'u') {
   3.630 +                    if (x+5>len) {
   3.631 +                        // unicode character not finished? syntax error: ignore
   3.632 +                        output.append(input.substring(x-1));
   3.633 +                        x += 4;
   3.634 +                        continue;
   3.635 +                    }
   3.636 +                    String val = input.substring(x+1, x+5);
   3.637 +                    try {
   3.638 +                        output.append((char)Integer.parseInt(val, 16));
   3.639 +                    } catch (NumberFormatException e) {
   3.640 +                        // #46234: handle gracefully
   3.641 +                        output.append(input.substring(x - 1, x + 5));
   3.642 +                    }
   3.643 +                    x += 4;
   3.644 +                } else {
   3.645 +                    if (ch == 't') ch = '\t';
   3.646 +                    else if (ch == 'r') ch = '\r';
   3.647 +                    else if (ch == 'n') ch = '\n';
   3.648 +                    else if (ch == 'f') ch = '\f';
   3.649 +                    output.append(ch);
   3.650 +                }
   3.651 +            }
   3.652 +            return output.toString();
   3.653 +        }
   3.654 +
   3.655 +        private static String encode(String input, boolean escapeSpace) {
   3.656 +            int len = input.length();
   3.657 +            StringBuffer output = new StringBuffer(len*2);
   3.658 +
   3.659 +            for(int x=0; x<len; x++) {
   3.660 +                char ch = input.charAt(x);
   3.661 +                switch(ch) {
   3.662 +                    case ' ':
   3.663 +                        if (x == 0 || escapeSpace)  {
   3.664 +                            output.append('\\');
   3.665 +                        }
   3.666 +                        output.append(' ');
   3.667 +                        break;
   3.668 +                    case '\\':
   3.669 +                        output.append("\\\\");
   3.670 +                        break;
   3.671 +                    case '\t':
   3.672 +                        output.append("\\t");
   3.673 +                        break;
   3.674 +                    case '\n':
   3.675 +                        output.append("\\n");
   3.676 +                        break;
   3.677 +                    case '\r':
   3.678 +                        output.append("\\r");
   3.679 +                        break;
   3.680 +                    case '\f':
   3.681 +                        output.append("\\f");
   3.682 +                        break;
   3.683 +                    default:
   3.684 +                        if ((ch < 0x0020) || (ch > 0x007e)) {
   3.685 +                            output.append("\\u");
   3.686 +                            String hex = Integer.toHexString(ch);
   3.687 +                            for (int i = 0; i < 4 - hex.length(); i++) {
   3.688 +                                output.append('0');
   3.689 +                            }
   3.690 +                            output.append(hex);
   3.691 +                        } else {
   3.692 +                            output.append(ch);
   3.693 +                        }
   3.694 +                }
   3.695 +            }
   3.696 +            return output.toString();
   3.697 +        }
   3.698 +        
   3.699 +        private static String decodeUnicode(String input) {
   3.700 +            char ch;
   3.701 +            int len = input.length();
   3.702 +            StringBuffer output = new StringBuffer(len);
   3.703 +            for (int x = 0; x < len; x++) {
   3.704 +                ch = input.charAt(x);
   3.705 +                if (ch != '\\') {
   3.706 +                    output.append(ch);
   3.707 +                    continue;
   3.708 +                }
   3.709 +                x++;
   3.710 +                if (x==len) {
   3.711 +                    // backslash at the end? syntax error: ignore it
   3.712 +                    continue;
   3.713 +                }
   3.714 +                ch = input.charAt(x);
   3.715 +                if (ch == 'u') {
   3.716 +                    if (x+5>len) {
   3.717 +                        // unicode character not finished? syntax error: ignore
   3.718 +                        output.append(input.substring(x-1));
   3.719 +                        x += 4;
   3.720 +                        continue;
   3.721 +                    }
   3.722 +                    String val = input.substring(x+1, x+5);
   3.723 +                    try {
   3.724 +                        output.append((char)Integer.parseInt(val, 16));
   3.725 +                    } catch (NumberFormatException e) {
   3.726 +                        // #46234: handle gracefully
   3.727 +                        output.append(input.substring(x - 1, x + 5));
   3.728 +                    }
   3.729 +                    x += 4;
   3.730 +                } else {
   3.731 +                    output.append(ch);
   3.732 +                }
   3.733 +            }
   3.734 +            return output.toString();
   3.735 +        }
   3.736 +
   3.737 +        private static String encodeUnicode(String input) {
   3.738 +            int len = input.length();
   3.739 +            StringBuffer output = new StringBuffer(len * 2);
   3.740 +            for (int x = 0; x < len; x++) {
   3.741 +                char ch = input.charAt(x);
   3.742 +                if ((ch < 0x0020) || (ch > 0x007e)) {
   3.743 +                    output.append("\\u"); // NOI18N
   3.744 +                    String hex = Integer.toHexString(ch);
   3.745 +                    for (int i = 0; i < 4 - hex.length(); i++) {
   3.746 +                        output.append('0');
   3.747 +                    }
   3.748 +                    output.append(hex);
   3.749 +                } else {
   3.750 +                    output.append(ch);
   3.751 +                }
   3.752 +            }
   3.753 +            return output.toString();
   3.754 +        }
   3.755 +
   3.756 +        @Override
   3.757 +        public Object clone() {
   3.758 +            Item item = new Item();
   3.759 +            if (keyValueLines != null) {
   3.760 +                item.keyValueLines = new ArrayList<String>(keyValueLines);
   3.761 +            }
   3.762 +            if (commentLines != null) {
   3.763 +                item.commentLines = new ArrayList<String>(commentLines);
   3.764 +            }
   3.765 +            item.key = key;
   3.766 +            item.value = value;
   3.767 +            item.separate = separate;
   3.768 +            return item;
   3.769 +        }
   3.770 +    
   3.771 +    }
   3.772 +    
   3.773 +    private static class SetImpl extends AbstractSet<Map.Entry<String,String>> {
   3.774 +
   3.775 +        private EditableProperties props;
   3.776 +        
   3.777 +        public SetImpl(EditableProperties props) {
   3.778 +            this.props = props;
   3.779 +        }
   3.780 +        
   3.781 +        public Iterator<Map.Entry<String,String>> iterator() {
   3.782 +            return new IteratorImpl(props);
   3.783 +        }
   3.784 +        
   3.785 +        public int size() {
   3.786 +            return props.items.size();
   3.787 +        }
   3.788 +        
   3.789 +    }
   3.790 +    
   3.791 +    private static class IteratorImpl implements Iterator<Map.Entry<String,String>> {
   3.792 +
   3.793 +        private final EditableProperties props;
   3.794 +        private ListIterator<Item> delegate;
   3.795 +        
   3.796 +        public IteratorImpl(EditableProperties props) {
   3.797 +            this.props = props;
   3.798 +            delegate = props.items.listIterator();
   3.799 +        }
   3.800 +        
   3.801 +        public boolean hasNext() {
   3.802 +            return findNext() != null;
   3.803 +        }
   3.804 +        
   3.805 +        public Map.Entry<String,String> next() {
   3.806 +            Item item = findNext();
   3.807 +            if (item == null) {
   3.808 +                throw new NoSuchElementException();
   3.809 +            }
   3.810 +            delegate.next();
   3.811 +            return new MapEntryImpl(item);
   3.812 +        }
   3.813 +        
   3.814 +        public void remove() {
   3.815 +            delegate.previous();
   3.816 +            Item item = findNext();
   3.817 +            if (item == null) {
   3.818 +                throw new IllegalStateException();
   3.819 +            }
   3.820 +            int index = delegate.nextIndex();
   3.821 +            props.items.remove(item);
   3.822 +            props.itemIndex.remove(item.getKey());
   3.823 +            delegate = props.items.listIterator(index);
   3.824 +        }
   3.825 +        
   3.826 +        private Item findNext() {
   3.827 +            while (delegate.hasNext()) {
   3.828 +                Item item = delegate.next();
   3.829 +                if (item.getKey() != null && item.getValue() != null) {
   3.830 +                    // Found one. Back up!
   3.831 +                    delegate.previous();
   3.832 +                    return item;
   3.833 +                }
   3.834 +            }
   3.835 +            return null;
   3.836 +        }
   3.837 +        
   3.838 +    }
   3.839 +    
   3.840 +    private static class MapEntryImpl implements Map.Entry<String,String> {
   3.841 +        
   3.842 +        private Item item;
   3.843 +        
   3.844 +        public MapEntryImpl(Item item) {
   3.845 +            this.item = item;
   3.846 +        }
   3.847 +        
   3.848 +        public String getKey() {
   3.849 +            return item.getKey();
   3.850 +        }
   3.851 +        
   3.852 +        public String getValue() {
   3.853 +            return item.getValue();
   3.854 +        }
   3.855 +        
   3.856 +        public String setValue(String value) {
   3.857 +            String result = item.getValue();
   3.858 +            item.setValue(value);
   3.859 +            return result;
   3.860 +        }
   3.861 +        
   3.862 +    }
   3.863 +    
   3.864 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java	Wed Jul 29 11:49:18 2009 -0400
     4.3 @@ -0,0 +1,366 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
     4.8 + *
     4.9 + * The contents of this file are subject to the terms of either the GNU
    4.10 + * General Public License Version 2 only ("GPL") or the Common
    4.11 + * Development and Distribution License("CDDL") (collectively, the
    4.12 + * "License"). You may not use this file except in compliance with the
    4.13 + * License. You can obtain a copy of the License at
    4.14 + * http://www.netbeans.org/cddl-gplv2.html
    4.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    4.16 + * specific language governing permissions and limitations under the
    4.17 + * License.  When distributing the software, include this License Header
    4.18 + * Notice in each file and include the License file at
    4.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    4.20 + * particular file as subject to the "Classpath" exception as provided
    4.21 + * by Sun in the GPL Version 2 section of the License file that
    4.22 + * accompanied this code. If applicable, add the following below the
    4.23 + * License Header, with the fields enclosed by brackets [] replaced by
    4.24 + * your own identifying information:
    4.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    4.26 + *
    4.27 + * Contributor(s):
    4.28 + *
    4.29 + * The Original Software is NetBeans. The Initial Developer of the Original
    4.30 + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
    4.31 + * Microsystems, Inc. All Rights Reserved.
    4.32 + *
    4.33 + * If you wish your version of this file to be governed by only the CDDL
    4.34 + * or only the GPL Version 2, indicate your decision by adding
    4.35 + * "[Contributor] elects to include this software in this distribution
    4.36 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    4.37 + * single choice of license, a recipient has the option to distribute
    4.38 + * your version of this file under either the CDDL, the GPL Version 2 or
    4.39 + * to extend the choice of license to its licensees as provided above.
    4.40 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    4.41 + * Version 2 license, then the option applies only if the new code is
    4.42 + * made subject to such option by the copyright holder.
    4.43 + */
    4.44 +
    4.45 +package org.openide.util;
    4.46 +
    4.47 +import java.io.ByteArrayInputStream;
    4.48 +import java.io.ByteArrayOutputStream;
    4.49 +import java.io.File;
    4.50 +import java.io.FileOutputStream;
    4.51 +import java.io.IOException;
    4.52 +import java.io.InputStream;
    4.53 +import java.io.OutputStream;
    4.54 +import java.net.URI;
    4.55 +import java.net.URL;
    4.56 +import java.util.Arrays;
    4.57 +import java.util.Collections;
    4.58 +import java.util.HashMap;
    4.59 +import java.util.Iterator;
    4.60 +import java.util.Map;
    4.61 +import org.netbeans.junit.NbTestCase;
    4.62 +
    4.63 +public class EditablePropertiesTest extends NbTestCase {
    4.64 +
    4.65 +    public EditablePropertiesTest(String name) {
    4.66 +        super(name);
    4.67 +    }
    4.68 +    
    4.69 +    public void testLoad() throws Exception {
    4.70 +        Map<String,String> content = new HashMap<String,String>();
    4.71 +        for (int i=1; i<=26; i++) {
    4.72 +            content.put("key"+i, "value"+i);
    4.73 +        }
    4.74 +        content.put("@!#$%^keyA", "valueA!@#$%^&*(){}");
    4.75 +        content.put(" =:keyB", "valueB =:");
    4.76 +        content.put(""+(char)0x1234+"keyC", "valueC"+(char)0x9876);
    4.77 +        content.put("keyD", "");
    4.78 +        content.put("keyE", "");
    4.79 +        content.put("keyF", "");
    4.80 +        content.put("keyG", "");
    4.81 +        content.put("keyH", "value#this is not comment");
    4.82 +        content.put("keyI", "incorrect end: \\u123");
    4.83 +        // #46234: does not handle bad Unicode escapes well
    4.84 +        content.put("keyJ", "malformed Unicode escape: \\uabyz");
    4.85 +        
    4.86 +        EditableProperties ep = loadTestProperties();
    4.87 +        
    4.88 +        for (Map.Entry<String,String> entry : content.entrySet()) {
    4.89 +            String key = entry.getKey();
    4.90 +            String value = entry.getValue();
    4.91 +            String epValue = ep.getProperty(key);
    4.92 +            assertEquals("Expected value for key "+key+" is different", value, epValue);
    4.93 +        }
    4.94 +        int count = 0;
    4.95 +        for (Map.Entry<String,String> entry : ep.entrySet()) {
    4.96 +            if (entry.getKey() != null) {
    4.97 +                count++;
    4.98 +            }
    4.99 +        }
   4.100 +        assertEquals("Number of items in property file", content.keySet().size(), count);
   4.101 +    }
   4.102 +    
   4.103 +    /* Doesn't work; java.util.Properties throws IAE for malformed Unicode escapes:
   4.104 +    public void testJavaUtilPropertiesEquivalence() throws Exception {
   4.105 +        Properties p = loadTestJavaUtilProperties();
   4.106 +        EditableProperties ep = loadTestProperties();
   4.107 +        Iterator it = p.entrySet().iterator();
   4.108 +        while (it.hasNext()) {
   4.109 +            Map.Entry entry = (Map.Entry) it.next();
   4.110 +            String key = (String) entry.getKey();
   4.111 +            String val = (String) entry.getValue();
   4.112 +            assertEquals("right value for " + key, val, ep.getProperty(key));
   4.113 +        }
   4.114 +        assertEquals("right number of items", p.size(), ep.size());
   4.115 +    }
   4.116 +     */
   4.117 +
   4.118 +    public void testSave() throws Exception {
   4.119 +        clearWorkDir();
   4.120 +        EditableProperties ep = loadTestProperties();
   4.121 +        String dest = getWorkDirPath()+File.separatorChar+"new.properties";
   4.122 +        saveProperties(ep, dest);
   4.123 +        assertFile("Saved properties must be the same as original one", filenameOfTestProperties(), dest, (String)null);
   4.124 +    }
   4.125 +    
   4.126 +    public void testClonability() throws Exception {
   4.127 +        clearWorkDir();
   4.128 +        EditableProperties ep = loadTestProperties();
   4.129 +        
   4.130 +        EditableProperties ep2 = ep.cloneProperties();
   4.131 +        String dest = getWorkDirPath()+File.separatorChar+"new2.properties";
   4.132 +        saveProperties(ep2, dest);
   4.133 +        assertFile("Saved cloned properties must be the same as original one", filenameOfTestProperties(), dest, (String)null);
   4.134 +        
   4.135 +        EditableProperties ep3 = (EditableProperties)ep.clone();
   4.136 +        dest = getWorkDirPath()+File.separatorChar+"new3.properties";
   4.137 +        saveProperties(ep3, dest);
   4.138 +        assertFile("Saved cloned properties must be the same as original one", filenameOfTestProperties(), dest, (String)null);
   4.139 +    }
   4.140 +
   4.141 +    // test that array values are stored correctly
   4.142 +    public void testArrayValues() throws Exception {
   4.143 +        EditableProperties ep = new EditableProperties(false);
   4.144 +        ep.setProperty("key1", new String[]{"1. line;", "2. line;", "3. line"});
   4.145 +        ep.setProperty("key2", "1. line;2. line;3. line");
   4.146 +        String output = getAsString(ep);
   4.147 +        String expected = 
   4.148 +            "key1=\\"+System.getProperty("line.separator")+
   4.149 +            "    1. line;\\"+System.getProperty("line.separator")+
   4.150 +            "    2. line;\\"+System.getProperty("line.separator")+
   4.151 +            "    3. line"+System.getProperty("line.separator")+
   4.152 +            "key2=1. line;2. line;3. line"+System.getProperty("line.separator");
   4.153 +        assertEquals(expected, output);
   4.154 +        assertEquals(ep.getProperty("key1"), "1. line;2. line;3. line");
   4.155 +        assertEquals(ep.getProperty("key2"), "1. line;2. line;3. line");
   4.156 +        ep.setProperty("key1", "one; two; three");
   4.157 +        output = getAsString(ep);
   4.158 +        expected = 
   4.159 +            "key1=one; two; three"+System.getProperty("line.separator")+
   4.160 +            "key2=1. line;2. line;3. line"+System.getProperty("line.separator");
   4.161 +        assertEquals(expected, output);
   4.162 +        assertEquals(ep.getProperty("key1"), "one; two; three");
   4.163 +        assertEquals(ep.getProperty("key2"), "1. line;2. line;3. line");
   4.164 +        ep.setProperty("key2", new String[]{"1. line;", "2. line;", "3. line", "one;", "more;", "line;"});
   4.165 +        ep.setProperty("key", new String[0]);
   4.166 +        output = getAsString(ep);
   4.167 +        expected = 
   4.168 +            "key1=one; two; three"+System.getProperty("line.separator")+
   4.169 +            "key2=\\"+System.getProperty("line.separator")+
   4.170 +            "    1. line;\\"+System.getProperty("line.separator")+
   4.171 +            "    2. line;\\"+System.getProperty("line.separator")+
   4.172 +            "    3. line\\"+System.getProperty("line.separator")+
   4.173 +            "    one;\\"+System.getProperty("line.separator")+
   4.174 +            "    more;\\"+System.getProperty("line.separator")+
   4.175 +            "    line;"+System.getProperty("line.separator")+
   4.176 +            "key="+System.getProperty("line.separator"); // #45061
   4.177 +        assertEquals(expected, output);
   4.178 +        assertEquals(ep.getProperty("key1"), "one; two; three");
   4.179 +        assertEquals(ep.getProperty("key2"), "1. line;2. line;3. lineone;more;line;");
   4.180 +        assertEquals(ep.getProperty("key"), "");
   4.181 +    }
   4.182 +        
   4.183 +    public void testSorting() throws Exception {
   4.184 +        EditableProperties ep = new EditableProperties(false);
   4.185 +        ep.setProperty("a", "val-a");
   4.186 +        ep.setProperty("c", "val-c");
   4.187 +        ep.put("b", "val-b");
   4.188 +        String output = getAsString(ep);
   4.189 +        String expected = "a=val-a"+System.getProperty("line.separator")+"c=val-c"+
   4.190 +                System.getProperty("line.separator")+"b=val-b"+
   4.191 +                System.getProperty("line.separator");
   4.192 +        assertEquals(expected, output);
   4.193 +        
   4.194 +        ep = new EditableProperties(false);
   4.195 +        ep.setProperty("a", "val-a");
   4.196 +        ep.setProperty("c", "val-c");
   4.197 +        ep.put("b", "val-b");
   4.198 +        output = getAsString(ep);
   4.199 +        expected = "a=val-a"+System.getProperty("line.separator")+"c=val-c"+
   4.200 +                System.getProperty("line.separator")+"b=val-b"+
   4.201 +                System.getProperty("line.separator");
   4.202 +        assertEquals(expected, output);
   4.203 +        
   4.204 +        ep = new EditableProperties(true);
   4.205 +        ep.setProperty("a", "val-a");
   4.206 +        ep.setProperty("c", "val-c");
   4.207 +        ep.put("b", "val-b");
   4.208 +        output = getAsString(ep);
   4.209 +        expected = "a=val-a"+System.getProperty("line.separator")+"b=val-b"+
   4.210 +                System.getProperty("line.separator")+"c=val-c"+
   4.211 +                System.getProperty("line.separator");
   4.212 +        assertEquals(expected, output);
   4.213 +    }
   4.214 +
   4.215 +    // test that changing comments work and modify only comments
   4.216 +    // test that misc chars are correctly escaped, unicode encoded, etc.
   4.217 +    public void testEscaping() throws Exception {
   4.218 +        String umlaut = "" + (char)252;
   4.219 +        EditableProperties ep = new EditableProperties(false);
   4.220 +        ep.setProperty("a a", "a space a");
   4.221 +        ep.setProperty("b"+(char)0x4567, "val"+(char)0x1234);
   4.222 +        ep.setProperty("@!#$%^\\", "!@#$%^&*(){}\\");
   4.223 +        ep.setProperty("d\nd", "d\nnewline\nd");
   4.224 +        ep.setProperty("umlaut", umlaut);
   4.225 +        ep.setProperty("_a a", new String[]{"a space a"});
   4.226 +        ep.setProperty("_b"+(char)0x4567, new String[]{"val"+(char)0x1234});
   4.227 +        ep.setProperty("_@!#$%^\\", new String[]{"!@#$%^&*\\", "(){}\\"});
   4.228 +        ep.setProperty("_d\nd", new String[]{"d\nnew","line\nd", "\n", "end"});
   4.229 +        ep.setProperty("_umlaut", new String[]{umlaut, umlaut});
   4.230 +        String output = getAsString(ep);
   4.231 +        String expected = "a\\ a=a space a"+System.getProperty("line.separator")+
   4.232 +                "b\\u4567=val\\u1234"+System.getProperty("line.separator")+
   4.233 +                "@!#$%^\\\\=!@#$%^&*(){}\\\\"+System.getProperty("line.separator")+
   4.234 +                "d\\nd=d\\nnewline\\nd"+System.getProperty("line.separator")+
   4.235 +                "umlaut=\\u00fc"+System.getProperty("line.separator")+
   4.236 +                "_a\\ a=\\"+System.getProperty("line.separator")+"    a space a"+System.getProperty("line.separator")+
   4.237 +                "_b\\u4567=\\"+System.getProperty("line.separator")+"    val\\u1234"+System.getProperty("line.separator")+
   4.238 +                "_@!#$%^\\\\=\\"+System.getProperty("line.separator")+"    !@#$%^&*\\\\\\"+System.getProperty("line.separator")+
   4.239 +                    "    (){}\\\\"+System.getProperty("line.separator")+
   4.240 +                "_d\\nd=\\"+System.getProperty("line.separator")+"    d\\nnew\\"+System.getProperty("line.separator")+
   4.241 +                    "    line\\nd\\"+System.getProperty("line.separator")+
   4.242 +                    "    \\n\\"+System.getProperty("line.separator")+
   4.243 +                    "    end"+System.getProperty("line.separator")+
   4.244 +                "_umlaut=\\" +System.getProperty("line.separator")+"    \\u00fc\\"+System.getProperty("line.separator")+
   4.245 +                    "    \\u00fc"+System.getProperty("line.separator");
   4.246 +        assertEquals(expected, output);
   4.247 +        assertEquals("a space a", ep.getProperty("a a"));
   4.248 +        assertEquals("val"+(char)0x1234, ep.getProperty("b"+(char)0x4567));
   4.249 +        assertEquals("!@#$%^&*(){}\\", ep.getProperty("@!#$%^\\"));
   4.250 +        assertEquals("d\nnewline\nd", ep.getProperty("d\nd"));
   4.251 +        assertEquals(umlaut, ep.getProperty("umlaut"));
   4.252 +        assertEquals("a space a", ep.getProperty("_a a"));
   4.253 +        assertEquals("val"+(char)0x1234, ep.getProperty("_b"+(char)0x4567));
   4.254 +        assertEquals("!@#$%^&*\\(){}\\", ep.getProperty("_@!#$%^\\"));
   4.255 +        assertEquals("d\nnewline\nd\nend", ep.getProperty("_d\nd"));
   4.256 +        assertEquals(umlaut+umlaut, ep.getProperty("_umlaut"));
   4.257 +    }
   4.258 +    
   4.259 +    // test that iterator implementation is OK
   4.260 +    public void testIterator() throws Exception {
   4.261 +        EditableProperties ep = loadTestProperties();
   4.262 +        Iterator<Map.Entry<String,String>> it1 = ep.entrySet().iterator();
   4.263 +        while (it1.hasNext()) {
   4.264 +            it1.next();
   4.265 +        }
   4.266 +        Iterator<String> it2 = ep.keySet().iterator();
   4.267 +        while (it2.hasNext()) {
   4.268 +            it2.next();
   4.269 +        }
   4.270 +        it2 = ep.keySet().iterator();
   4.271 +        while (it2.hasNext()) {
   4.272 +            it2.next();
   4.273 +            it2.remove();
   4.274 +        }
   4.275 +        ep.put("a", "aval");
   4.276 +        ep.remove("a");
   4.277 +        ep = loadTestProperties();
   4.278 +        it1 = ep.entrySet().iterator();
   4.279 +        while (it1.hasNext()) {
   4.280 +            Map.Entry<String,String> entry = it1.next();
   4.281 +            assertNotNull("Property key cannot be null", entry.getKey());
   4.282 +            assertNotNull("Property value cannot be null", entry.getValue());
   4.283 +            entry.setValue(entry.getValue()+"-something-new");
   4.284 +        }
   4.285 +        it1 = ep.entrySet().iterator();
   4.286 +        while (it1.hasNext()) {
   4.287 +            it1.next();
   4.288 +            it1.remove();
   4.289 +        }
   4.290 +    }
   4.291 +    
   4.292 +    // test that syntax errors are survived
   4.293 +    public void testInvalidPropertiesFile() throws Exception {
   4.294 +        String invalidProperty = "key=value without correct end\\";
   4.295 +        ByteArrayInputStream is = new ByteArrayInputStream(invalidProperty.getBytes());
   4.296 +        EditableProperties ep = new EditableProperties(false);
   4.297 +        ep.load(is);
   4.298 +        assertEquals("Syntax error should be resolved", 1, ep.keySet().size());
   4.299 +        assertEquals("value without correct end", ep.getProperty("key"));
   4.300 +    }
   4.301 +    
   4.302 +    public void testNonLatinComments() throws Exception {
   4.303 +        // #60249.
   4.304 +        String lsep = System.getProperty("line.separator");
   4.305 +        EditableProperties p = new EditableProperties(false);
   4.306 +        p.setProperty("k", "v");
   4.307 +        p.setComment("k", new String[] {"# \u0158ekni koment teda!"}, false);
   4.308 +        String expected = "# \\u0158ekni koment teda!" + lsep + "k=v" + lsep;
   4.309 +        assertEquals("Storing non-Latin chars in comments works", expected, getAsString(p));
   4.310 +        p = new EditableProperties(false);
   4.311 +        p.load(new ByteArrayInputStream(expected.getBytes("ISO-8859-1")));
   4.312 +        assertEquals("Reading non-Latin chars in comments works", Collections.singletonList("# \u0158ekni koment teda!"), Arrays.asList(p.getComment("k")));
   4.313 +        p.setProperty("k", "v2");
   4.314 +        expected = "# \\u0158ekni koment teda!" + lsep + "k=v2" + lsep;
   4.315 +        assertEquals("Reading and re-writing non-Latin chars in comments works", expected, getAsString(p));
   4.316 +    }
   4.317 +
   4.318 +    
   4.319 +    // helper methods:
   4.320 +    
   4.321 +    
   4.322 +    private String filenameOfTestProperties() {
   4.323 +        // #50987: never use URL.path for this purpose...
   4.324 +        return new File(URI.create(EditablePropertiesTest.class.getResource("data/test.properties").toExternalForm())).getAbsolutePath();
   4.325 +    }
   4.326 +    
   4.327 +    private EditableProperties loadTestProperties() throws IOException {
   4.328 +        URL u = EditablePropertiesTest.class.getResource("data/test.properties");
   4.329 +        EditableProperties ep = new EditableProperties(false);
   4.330 +        InputStream is = u.openStream();
   4.331 +        try {
   4.332 +            ep.load(is);
   4.333 +        } finally {
   4.334 +            is.close();
   4.335 +        }
   4.336 +        return ep;
   4.337 +    }
   4.338 +    
   4.339 +    /*
   4.340 +    private Properties loadTestJavaUtilProperties() throws IOException {
   4.341 +        URL u = EditablePropertiesTest.class.getResource("data/test.properties");
   4.342 +        Properties p = new Properties();
   4.343 +        InputStream is = u.openStream();
   4.344 +        try {
   4.345 +            p.load(is);
   4.346 +        } finally {
   4.347 +            is.close();
   4.348 +        }
   4.349 +        return p;
   4.350 +    }
   4.351 +     */
   4.352 +    
   4.353 +    private void saveProperties(EditableProperties ep, String path) throws Exception {
   4.354 +        OutputStream os = new FileOutputStream(path);
   4.355 +        try {
   4.356 +            ep.store(os);
   4.357 +        } finally {
   4.358 +            os.close();
   4.359 +        }
   4.360 +    }
   4.361 +
   4.362 +    private String getAsString(EditableProperties ep) throws Exception {
   4.363 +        ByteArrayOutputStream os = new ByteArrayOutputStream();
   4.364 +        ep.store(os);
   4.365 +        os.close();
   4.366 +        return os.toString("ISO-8859-1");
   4.367 +    }
   4.368 +    
   4.369 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/openide.util/test/unit/src/org/openide/util/data/test.properties	Wed Jul 29 11:49:18 2009 -0400
     5.3 @@ -0,0 +1,134 @@
     5.4 +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.5 +#
     5.6 +# Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
     5.7 +#
     5.8 +# The contents of this file are subject to the terms of either the GNU
     5.9 +# General Public License Version 2 only ("GPL") or the Common
    5.10 +# Development and Distribution License("CDDL") (collectively, the
    5.11 +# "License"). You may not use this file except in compliance with the
    5.12 +# License. You can obtain a copy of the License at
    5.13 +# http://www.netbeans.org/cddl-gplv2.html
    5.14 +# or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.15 +# specific language governing permissions and limitations under the
    5.16 +# License.  When distributing the software, include this License Header
    5.17 +# Notice in each file and include the License file at
    5.18 +# nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    5.19 +# particular file as subject to the "Classpath" exception as provided
    5.20 +# by Sun in the GPL Version 2 section of the License file that
    5.21 +# accompanied this code. If applicable, add the following below the
    5.22 +# License Header, with the fields enclosed by brackets [] replaced by
    5.23 +# your own identifying information:
    5.24 +# "Portions Copyrighted [year] [name of copyright owner]"
    5.25 +#
    5.26 +# Contributor(s):
    5.27 +#
    5.28 +# The Original Software is NetBeans. The Initial Developer of the Original
    5.29 +# Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
    5.30 +# Microsystems, Inc. All Rights Reserved.
    5.31 +#
    5.32 +# If you wish your version of this file to be governed by only the CDDL
    5.33 +# or only the GPL Version 2, indicate your decision by adding
    5.34 +# "[Contributor] elects to include this software in this distribution
    5.35 +# under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.36 +# single choice of license, a recipient has the option to distribute
    5.37 +# your version of this file under either the CDDL, the GPL Version 2 or
    5.38 +# to extend the choice of license to its licensees as provided above.
    5.39 +# However, if you add GPL Version 2 code and therefore, elected the GPL
    5.40 +# Version 2 license, then the option applies only if the new code is
    5.41 +# made subject to such option by the copyright holder.
    5.42 +
    5.43 +key1=value1
    5.44 +key2 = value2
    5.45 +key3 =value3
    5.46 +key4= value4
    5.47 +
    5.48 +key5:value5
    5.49 +key6 : value6
    5.50 +key7 :value7
    5.51 +key8: value8
    5.52 +
    5.53 +key9 value9
    5.54 +key10   value10
    5.55 +key11  value11
    5.56 +key12  value12
    5.57 +
    5.58 +#comment can end with slash but it does not mean anything\
    5.59 +
    5.60 +!different type of comment
    5.61 +
    5.62 +    #
    5.63 +    #indented comment
    5.64 +    #
    5.65 +
    5.66 +    !
    5.67 +    ! indented comment
    5.68 +    !
    5.69 +
    5.70 +# on the lines below there are some whitespaces which must be ignored
    5.71 +
    5.72 +
    5.73 +
    5.74 +
    5.75 +
    5.76 +
    5.77 +
    5.78 +   key13      =     value13
    5.79 +
    5.80 +
    5.81 +key\
    5.82 +14 value14
    5.83 +key\
    5.84 +  15 value15
    5.85 +k\
    5.86 +e\
    5.87 +y\
    5.88 +1\
    5.89 +6 value16
    5.90 +key\
    5.91 +\
    5.92 + \
    5.93 +        17 value17
    5.94 +key18\
    5.95 +:value18
    5.96 +key19 \
    5.97 +value19
    5.98 +key20 value\
    5.99 +20
   5.100 +key21 value\
   5.101 +     21
   5.102 +key22 v\
   5.103 +a\
   5.104 +    lue22
   5.105 +key23 value\
   5.106 +\
   5.107 +   \
   5.108 +  23
   5.109 +
   5.110 +#comment for property 24
   5.111 +key24=value24
   5.112 +#comment for property 25
   5.113 +#second line of comment for property 25
   5.114 +key25=\
   5.115 +value25
   5.116 +#comment for property 26
   5.117 +key26=value26
   5.118 +
   5.119 +
   5.120 +@!#$%^keyA valueA!@#$%^&*(){}
   5.121 +\ \=\:keyB=valueB\ \=\:
   5.122 +\u1234\keyC value\C\u9876
   5.123 +
   5.124 +  keyD    =
   5.125 +  keyE
   5.126 +keyF              
   5.127 +
   5.128 +keyG\
   5.129 +    
   5.130 +    
   5.131 +    keyH        = value\
   5.132 +    #this is not comment
   5.133 +
   5.134 +keyI=incorrect end: \u123
   5.135 +
   5.136 +keyJ=malformed Unicode escape: \uabyz
   5.137 +