This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 242226
Collapse All | Expand All

(-)a/openide.loaders/apichanges.xml (+19 lines)
Lines 109-114 Link Here
109
<!-- ACTUAL CHANGES BEGIN HERE: -->
109
<!-- ACTUAL CHANGES BEGIN HERE: -->
110
110
111
  <changes>
111
  <changes>
112
      <change id="org.openide.loaders.DataFolder.SortMode.NATURAL">
113
          <api name="loaders"/>
114
          <summary>Introduces SortMode for natural sorting.</summary>
115
          <version major="7" minor="65"/>
116
          <date day="21" month="10" year="2015"/>
117
          <author login="jhavlin"/>
118
          <compatibility addition="yes" binary="compatible" source="compatible"
119
                         semantic="compatible" deprecation="no" deletion="no"
120
                         modification="no"/>
121
          <description>
122
              <p>
123
                  Added support for natural sorting of DataObjects. This means
124
                  that the sort is case insensitive and number sequences are
125
                  sorted by value rather than lexicographically.
126
              </p>
127
          </description>
128
          <class package="org.openide.loaders" name="DataFolder"/>
129
          <issue number="242226"/>
130
      </change>
112
      <change id="templates.separation">
131
      <change id="templates.separation">
113
          <api name="loaders"/>
132
          <api name="loaders"/>
114
          <summary>Separate template handling</summary>
133
          <summary>Separate template handling</summary>
(-)a/openide.loaders/manifest.mf (-1 / +1 lines)
Lines 1-6 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.openide.loaders
2
OpenIDE-Module: org.openide.loaders
3
OpenIDE-Module-Specification-Version: 7.64
3
OpenIDE-Module-Specification-Version: 7.65
4
OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties
5
OpenIDE-Module-Provides: org.netbeans.modules.templates.v1_0
5
OpenIDE-Module-Provides: org.netbeans.modules.templates.v1_0
6
OpenIDE-Module-Layer: org/netbeans/modules/openide/loaders/layer.xml
6
OpenIDE-Module-Layer: org/netbeans/modules/openide/loaders/layer.xml
(-)a/openide.loaders/src/org/openide/loaders/Bundle.properties (+1 lines)
Lines 110-115 Link Here
110
VALUE_sort_last_modified=By Modification Time
110
VALUE_sort_last_modified=By Modification Time
111
VALUE_sort_size=By File Size
111
VALUE_sort_size=By File Size
112
VALUE_sort_extensions=By Extension
112
VALUE_sort_extensions=By Extension
113
VALUE_sort_natural=Naturally By Name
113
114
114
115
115
#
116
#
(-)a/openide.loaders/src/org/openide/loaders/DataFolder.java (+15 lines)
Lines 1108-1113 Link Here
1108
         */
1108
         */
1109
        public static final SortMode EXTENSIONS = new FolderComparator(FolderComparator.EXTENSIONS);
1109
        public static final SortMode EXTENSIONS = new FolderComparator(FolderComparator.EXTENSIONS);
1110
1110
1111
        /**
1112
         * Folder go first (sorted naturally by name) followed by files sorted
1113
         * by natural name and extension. Natural means that number sequences
1114
         * are evaluated and compared by value rather than lexicographically.
1115
         *
1116
         * @since org.openide.loaders 7.65
1117
         */
1118
        public static final SortMode NATURAL = new FolderComparator(FolderComparator.NATURAL);
1119
1111
        /** Method to write the sort mode to a folder's attributes.
1120
        /** Method to write the sort mode to a folder's attributes.
1112
        * @param folder folder write this mode to
1121
        * @param folder folder write this mode to
1113
        */
1122
        */
Lines 1126-1131 Link Here
1126
                x = "M"; // NOI18N
1135
                x = "M"; // NOI18N
1127
            } else if (this == SIZE) {
1136
            } else if (this == SIZE) {
1128
                x = "S"; // NOI18N
1137
                x = "S"; // NOI18N
1138
            } else if (this == EXTENSIONS) {
1139
                x = "X"; // NOI18N
1140
            } else if (this == NATURAL) {
1141
                x = "L"; // NOI18N
1129
            } else {
1142
            } else {
1130
                x = "O"; // NOI18N
1143
                x = "O"; // NOI18N
1131
            }
1144
            }
Lines 1148-1153 Link Here
1148
            case 'O': return NONE;
1161
            case 'O': return NONE;
1149
            case 'M': return LAST_MODIFIED;
1162
            case 'M': return LAST_MODIFIED;
1150
            case 'S': return SIZE;
1163
            case 'S': return SIZE;
1164
            case 'X': return EXTENSIONS;
1165
            case 'L': return NATURAL;
1151
            case 'F':
1166
            case 'F':
1152
            default:
1167
            default:
1153
                return FOLDER_NAMES;
1168
                return FOLDER_NAMES;
(-)a/openide.loaders/src/org/openide/loaders/FolderComparator.java (+124 lines)
Lines 69-74 Link Here
69
    public static final int SIZE = 5;
69
    public static final int SIZE = 5;
70
    /** by extension, then name */
70
    /** by extension, then name */
71
    public static final int EXTENSIONS = 6;
71
    public static final int EXTENSIONS = 6;
72
    /** by natural name (f10.txt > f9.txt) */
73
    public static final int NATURAL = 7;
72
74
73
75
74
    /** mode to use */
76
    /** mode to use */
Lines 110-115 Link Here
110
            return compareSize(obj1, obj2);
112
            return compareSize(obj1, obj2);
111
        case EXTENSIONS:
113
        case EXTENSIONS:
112
            return compareExtensions(obj1, obj2);
114
            return compareExtensions(obj1, obj2);
115
        case NATURAL:
116
            return compareNatural(obj1, obj2);
113
        default:
117
        default:
114
            assert false : mode;
118
            assert false : mode;
115
            return 0;
119
            return 0;
Lines 268-271 Link Here
268
            return fo1.getNameExt().compareTo(fo2.getNameExt());
272
            return fo1.getNameExt().compareTo(fo2.getNameExt());
269
        }
273
        }
270
    }
274
    }
275
276
    private static int compareNatural(Object o1, Object o2) {
277
278
        FileObject fo1 = findFileObject(o1);
279
        FileObject fo2 = findFileObject(o2);
280
281
        // Folders first.
282
        boolean f1 = fo1.isFolder();
283
        boolean f2 = fo2.isFolder();
284
        if (f1 != f2) {
285
            return f1 ? -1 : 1;
286
        }
287
288
        int res = compareFileNameNatural(fo1.getNameExt(), fo2.getNameExt());
289
        return res;
290
    }
291
292
    private static int compareFileNameNatural(String name1, String name2) {
293
294
        String n1 = name1.toLowerCase();
295
        String n2 = name2.toLowerCase();
296
297
        int p1;  // pointer to first string
298
        int p2;  // pointer to second string
299
300
        for (p1 = 0, p2 = 0; p1 < n1.length() && p2 < n2.length(); ) {
301
            char c1 = n1.charAt(p1);
302
            char c2 = n2.charAt(p2);
303
304
            ReadNumericValueResult nv1 = readNumericValue(n1, p1);
305
            ReadNumericValueResult nv2 = readNumericValue(n2, p2);
306
307
            if (nv1 != null && nv2 != null) {
308
                if (nv1.getValue() == nv2.getValue()) {
309
                    p1 = nv1.getEndPos();
310
                    p2 = nv2.getEndPos();
311
                } else {
312
                    return nv1.getValue() - nv2.getValue();
313
                }
314
            } else {
315
                if (c1 != c2) {
316
                    return c1 - c2;
317
                } else {
318
                    p1 ++;
319
                    p2 ++;
320
                }
321
            }
322
        }
323
        boolean unfinished1 = p1 < n1.length();
324
        boolean unfinished2 = p2 < n2.length();
325
        if (!unfinished1 && !unfinished2) {
326
            return name1.compareTo(name2);
327
        } else if (unfinished1) {
328
            return 1; // first string is longer (prefix of second string)
329
        } else if (unfinished2) {
330
            return -1; // second string is longer (prefix of first string)
331
        } else {
332
            assert false : "Invalid state in natural comparator";       //NOI18N
333
            return n1.compareTo(n2);
334
        }
335
    }
336
337
    /**
338
     * Read numeric value token starting at position {@code pos}. It can be
339
     * delimited by whitespace (so it supports values in strings like "a1b", "a
340
     * 1b", "a1 b", "a 1 b").
341
     *
342
     * @param s Input string.
343
     * @param pos Position in the input string.
344
     *
345
     * @return The numeric value starting at that position and end position in
346
     * the string, or null if there is no numeric value (e.g. there is a
347
     * white-space only.).
348
     */
349
    private static ReadNumericValueResult readNumericValue(String s, int pos) {
350
        int val = 0;
351
        boolean num = false; // some number value was read
352
        boolean afterNum = false; // we are reading trailing whitespace
353
        int len = s.length();
354
        for (int i = pos; i < len; i++) {
355
            char c = s.charAt(i);
356
            if (c >= '0' && c <= '9') {
357
                if (afterNum) { // new, separated number encountered
358
                    return new ReadNumericValueResult(val, i);
359
                }
360
                val = val * 10 + (c - '0');
361
                num = true;
362
            } else if (Character.isWhitespace(c)) { // leading or trailing space
363
                if (num) {
364
                    afterNum = true; // in trailing whitespace after number
365
                }
366
            } else {
367
                return num ? new ReadNumericValueResult(val, i) : null;
368
            }
369
        }
370
        return num ? new ReadNumericValueResult(val, len) : null;
371
    }
372
373
    /**
374
     * Class for representing result returned from
375
     * {@link #readNumericValue(String, int)}.
376
     */
377
    private static class ReadNumericValueResult {
378
379
        private final int value;
380
        private final int endPos;
381
382
        public ReadNumericValueResult(int value, int endPos) {
383
            this.value = value;
384
            this.endPos = endPos;
385
        }
386
387
        public int getValue() {
388
            return value;
389
        }
390
391
        public int getEndPos() {
392
            return endPos;
393
        }
394
    }
271
}
395
}
(-)a/openide.loaders/src/org/openide/loaders/FolderOrder.java (-1 / +18 lines)
Lines 169-175 Link Here
169
            }
169
            }
170
170
171
            // compare by the provided comparator
171
            // compare by the provided comparator
172
            return ((FolderComparator)(getSortMode())).doCompare(obj1, obj2);
172
            SortMode comparator = getSortMode();
173
            if (comparator instanceof FolderComparator) {
174
                return ((FolderComparator) comparator).doCompare(obj1, obj2);
175
            } else if ((obj1 instanceof DataObject) // Also support custom
176
                    && (obj2 instanceof DataObject)) { // comparators, #242226.
177
                return comparator.compare(
178
                        (DataObject) obj1, (DataObject) obj2);
179
            } else {
180
                FileObject fo1 = FolderComparator.findFileObject(obj1);
181
                FileObject fo2 = FolderComparator.findFileObject(obj2);
182
                try {
183
                    return comparator.compare(
184
                            DataObject.find(fo1), DataObject.find(fo2));
185
                } catch (DataObjectNotFoundException ex) {
186
                    throw new IllegalArgumentException("Expected "      //NOI18N
187
                            + "DataObjects or Nodes.");                 //NOI18N
188
                }
189
            }
173
        } else {
190
        } else {
174
            if (i2 == null) {
191
            if (i2 == null) {
175
                return -1;
192
                return -1;
(-)a/openide.loaders/src/org/openide/loaders/SortModeEditor.java (-2 / +4 lines)
Lines 58-64 Link Here
58
        DataFolder.SortMode.FOLDER_NAMES,
58
        DataFolder.SortMode.FOLDER_NAMES,
59
        DataFolder.SortMode.LAST_MODIFIED,
59
        DataFolder.SortMode.LAST_MODIFIED,
60
        DataFolder.SortMode.SIZE,
60
        DataFolder.SortMode.SIZE,
61
        DataFolder.SortMode.EXTENSIONS
61
        DataFolder.SortMode.EXTENSIONS,
62
        DataFolder.SortMode.NATURAL
62
    };
63
    };
63
64
64
    /** Names for modes. First is for displaying files */
65
    /** Names for modes. First is for displaying files */
Lines 69-75 Link Here
69
        DataObject.getString ("VALUE_sort_folder_names"),
70
        DataObject.getString ("VALUE_sort_folder_names"),
70
        DataObject.getString ("VALUE_sort_last_modified"),
71
        DataObject.getString ("VALUE_sort_last_modified"),
71
        DataObject.getString ("VALUE_sort_size"),
72
        DataObject.getString ("VALUE_sort_size"),
72
        DataObject.getString ("VALUE_sort_extensions")
73
        DataObject.getString ("VALUE_sort_extensions"),
74
        DataObject.getString ("VALUE_sort_natural")
73
    };
75
    };
74
76
75
    /** @return names of the two possible modes */
77
    /** @return names of the two possible modes */
(-)a3114c94d4ff (+167 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.openide.loaders;
43
44
import java.awt.EventQueue;
45
import java.io.IOException;
46
import java.lang.reflect.InvocationTargetException;
47
import java.util.ArrayList;
48
import java.util.Collections;
49
import java.util.List;
50
import static junit.framework.TestCase.assertEquals;
51
import static junit.framework.TestCase.assertNotNull;
52
import org.junit.Test;
53
import org.openide.filesystems.FileObject;
54
import org.openide.filesystems.FileSystem;
55
import org.openide.filesystems.FileUtil;
56
57
/**
58
 *
59
 * @author jhavlin
60
 */
61
public class FolderComparatorTest {
62
63
    public FolderComparatorTest() {
64
    }
65
66
    @Test
67
    public void testNaturalComparatorBasic() throws IOException {
68
        testNaturalComparator(new String[]{
69
            "b 10.txt",
70
            "b 9.txt",
71
            "a2.txt",
72
            "a 4 9.txt",
73
            "a10.txt",
74
            "b0070.txt",
75
            "a 3.txt",
76
            "b08.txt"
77
        }, new String[]{
78
            "a2.txt",
79
            "a 3.txt",
80
            "a 4 9.txt",
81
            "a10.txt",
82
            "b08.txt",
83
            "b 9.txt",
84
            "b 10.txt",
85
            "b0070.txt"
86
        });
87
    }
88
89
    @Test
90
    public void testNaturalComparatorWithSuffixes() throws IOException {
91
        testNaturalComparator(new String[]{
92
            "a01b",
93
            "a2x",
94
            "a02",
95
            "a1"
96
        }, new String[]{
97
            "a1",
98
            "a01b",
99
            "a02",
100
            "a2x"
101
        });
102
    }
103
104
    @Test
105
    public void testUseCustomComparator() throws IOException,
106
            InterruptedException, InvocationTargetException {
107
108
        FileSystem fs = FileUtil.createMemoryFileSystem();
109
110
        fs.getRoot().createData("aaaa.txt");
111
        fs.getRoot().createData("bbb.txt");
112
        fs.getRoot().createData("cc.txt");
113
        fs.getRoot().createData("d.txt");
114
        fs.getRoot().refresh();
115
116
        DataFolder.SortMode custom = new DataFolder.SortMode() {
117
            @Override
118
            public int compare(DataObject o1, DataObject o2) {
119
                return o1.getName().length() - o2.getName().length();
120
            }
121
        };
122
123
        DataFolder df = DataFolder.findFolder(fs.getRoot());
124
        df.setSortMode(custom);
125
        EventQueue.invokeAndWait(new Runnable() {
126
            @Override
127
            public void run() {
128
            }
129
        });
130
        DataObject[] children = df.getChildren();
131
        assertEquals("d.txt", children[0].getName());
132
        assertEquals("cc.txt", children[1].getName());
133
        assertEquals("bbb.txt", children[2].getName());
134
        assertEquals("aaaa.txt", children[3].getName());
135
    }
136
137
    @Test
138
    public void testNaturalComparatorFallback() throws IOException {
139
        testNaturalComparator(new String[]{
140
            "a01.txt",
141
            "a001.txt",
142
            "A1.txt"
143
        }, new String[]{
144
            "A1.txt",
145
            "a001.txt",
146
            "a01.txt"
147
        });
148
    }
149
150
    private void testNaturalComparator(String[] fileNames,
151
            String[] expectedOrder) throws IOException {
152
        FolderComparator c = new FolderComparator(FolderComparator.NATURAL);
153
        FileSystem fs = FileUtil.createMemoryFileSystem();
154
        FileObject root = fs.getRoot();
155
        List<DataObject> list = new ArrayList<DataObject>();
156
        for (String n : fileNames) {
157
            FileObject fo = root.createData(n);
158
            assertNotNull(fo);
159
            list.add(DataObject.find(fo));
160
        }
161
162
        Collections.sort(list, c);
163
        for (int i = 0; i < expectedOrder.length; i++) {
164
            assertEquals(expectedOrder[i], list.get(i).getName());
165
        }
166
    }
167
}

Return to bug 242226