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 270131
Collapse All | Expand All

(-)a/java.api.common/src/org/netbeans/modules/java/api/common/queries/SourcesImpl.java (-3 / +27 lines)
Lines 73-78 Link Here
73
import org.netbeans.modules.java.api.common.impl.RootsAccessor;
73
import org.netbeans.modules.java.api.common.impl.RootsAccessor;
74
import org.netbeans.modules.java.api.common.project.ProjectProperties;
74
import org.netbeans.modules.java.api.common.project.ProjectProperties;
75
import org.netbeans.spi.project.SourceGroupModifierImplementation;
75
import org.netbeans.spi.project.SourceGroupModifierImplementation;
76
import org.netbeans.spi.project.SourceGroupRelativeModifierImplementation;
76
import org.netbeans.spi.project.support.GenericSources;
77
import org.netbeans.spi.project.support.GenericSources;
77
import org.netbeans.spi.project.support.ant.SourcesHelper;
78
import org.netbeans.spi.project.support.ant.SourcesHelper;
78
import org.netbeans.spi.project.support.ant.AntProjectHelper;
79
import org.netbeans.spi.project.support.ant.AntProjectHelper;
Lines 87-93 Link Here
87
/**
88
/**
88
 * Implementation of {@link Sources} interface.
89
 * Implementation of {@link Sources} interface.
89
 */
90
 */
90
final class SourcesImpl implements Sources, SourceGroupModifierImplementation, PropertyChangeListener, ChangeListener  {
91
final class SourcesImpl implements Sources, SourceGroupModifierImplementation, SourceGroupRelativeModifierImplementation, PropertyChangeListener, ChangeListener  {
91
92
92
    @StaticResource
93
    @StaticResource
93
    private static final String MODULE_ICON = "org/netbeans/modules/java/api/common/project/ui/resources/module.png"; //NOI18N
94
    private static final String MODULE_ICON = "org/netbeans/modules/java/api/common/project/ui/resources/module.png"; //NOI18N
Lines 183-188 Link Here
183
    }
184
    }
184
185
185
    @Override
186
    @Override
187
    public SourceGroupModifierImplementation relativeTo(SourceGroup existingGroup, String... projectPart) {
188
        if (sgmi instanceof SourceGroupRelativeModifierImplementation) {
189
            return ((SourceGroupRelativeModifierImplementation)sgmi).relativeTo(existingGroup, projectPart);
190
        } else {
191
            return this;
192
        }
193
    }
194
195
    @Override
186
    public SourceGroup createSourceGroup(String type, String hint) {
196
    public SourceGroup createSourceGroup(String type, String hint) {
187
        return sgmi.createSourceGroup(type, hint);
197
        return sgmi.createSourceGroup(type, hint);
188
    }
198
    }
Lines 250-269 Link Here
250
        final String[] rootPathPropNames = RootsAccessor.getInstance().getRootPathProperties(roots);
260
        final String[] rootPathPropNames = RootsAccessor.getInstance().getRootPathProperties(roots);
251
        final String[] propNames = roots.getRootProperties();
261
        final String[] propNames = roots.getRootProperties();
252
        final String[] displayNames = roots.getRootDisplayNames();
262
        final String[] displayNames = roots.getRootDisplayNames();
253
263
        
254
        for (int i = 0; i < propNames.length; i++) {
264
        for (int i = 0; i < propNames.length; i++) {
255
            final String prop = propNames[i];
265
            final String prop = propNames[i];
256
            final String pathProp =  rootPathPropNames[i];
266
            final String pathProp =  rootPathPropNames[i];
257
267
258
            final List<String> locations;
268
            final List<String> locations;
259
            final List<String> names;
269
            final List<String> names;
270
            final List<String[]> parts;
260
            
271
            
261
            if (pathProp == null || JavaProjectConstants.SOURCES_TYPE_MODULES.equals(type)) {
272
            if (pathProp == null || JavaProjectConstants.SOURCES_TYPE_MODULES.equals(type)) {
262
                locations = Collections.singletonList("${" + prop + "}"); // NOI18N
273
                locations = Collections.singletonList("${" + prop + "}"); // NOI18N
263
                names = Collections.singletonList(displayNames[i]);
274
                names = Collections.singletonList(displayNames[i]);
275
                parts = Collections.singletonList(null);
264
            } else {
276
            } else {
265
                locations = new ArrayList<>();
277
                locations = new ArrayList<>();
266
                names = new ArrayList<>();
278
                names = new ArrayList<>();
279
                parts = new ArrayList<>();
267
                final String pathToModules = evaluator.getProperty(prop);
280
                final String pathToModules = evaluator.getProperty(prop);
268
                final File file = helper.resolveFile(pathToModules);
281
                final File file = helper.resolveFile(pathToModules);
269
                if (file.isDirectory()) {
282
                if (file.isDirectory()) {
Lines 279-285 Link Here
279
                                        pathToModules,
292
                                        pathToModules,
280
                                        f.getName(),
293
                                        f.getName(),
281
                                        variant);
294
                                        variant);
295
                                String[] v = variant.split("/");
296
                                String[] p = new String[v.length + 2];
297
                                // the most important is the module name
298
                                p[0] = f.getName();
299
                                // 2nd project part is the [resolved] path to modules; usually src.dir and test.dir point to the same location,
300
                                // but if they do not, the 'same location' gets a preference.
301
                                p[1] = pathToModules;
302
                                System.arraycopy(v, 0, p, 2, v.length);
282
                                locations.add(resolvedSrcPath); //Todo: Should be unevaluated
303
                                locations.add(resolvedSrcPath); //Todo: Should be unevaluated
304
                                parts.add(p);
283
                                String dispName = displayNames[i];
305
                                String dispName = displayNames[i];
284
                                if (dispName == null || dispName.isEmpty()) {
306
                                if (dispName == null || dispName.isEmpty()) {
285
                                    names.add(Bundle.FMT_ModularSourceRootNoName(f.getName(), pathToModules));
307
                                    names.add(Bundle.FMT_ModularSourceRootNoName(f.getName(), pathToModules));
Lines 292-298 Link Here
292
                }
314
                }
293
            }
315
            }
294
            assert locations.size() == names.size();
316
            assert locations.size() == names.size();
295
317
            assert locations.size() == parts.size();
318
            Iterator<String[]> partIt = parts.iterator();
296
            for (Iterator<String> locationIt = locations.iterator(), nameIt = names.iterator(); locationIt.hasNext() && nameIt.hasNext();) {
319
            for (Iterator<String> locationIt = locations.iterator(), nameIt = names.iterator(); locationIt.hasNext() && nameIt.hasNext();) {
297
                final SourcesHelper.SourceRootConfig cfg = sourcesHelper.sourceRoot(locationIt.next());
320
                final SourcesHelper.SourceRootConfig cfg = sourcesHelper.sourceRoot(locationIt.next());
298
                cfg.displayName(nameIt.next());
321
                cfg.displayName(nameIt.next());
Lines 305-310 Link Here
305
                if (hint != null) {
328
                if (hint != null) {
306
                    cfg.hint(hint);
329
                    cfg.hint(hint);
307
                }
330
                }
331
                cfg.inParts(partIt.next());
308
                cfg.add();  // principal root
332
                cfg.add();  // principal root
309
                if (type != null) {
333
                if (type != null) {
310
                    cfg.type(type).add();    // typed root
334
                    cfg.type(type).add();    // typed root
(-)a/project.ant/apichanges.xml (+14 lines)
Lines 107-112 Link Here
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
108
108
109
    <changes>
109
    <changes>
110
        <change id="associatedRoots">
111
            <api name="general"/>
112
            <summary>Support for source root grouping</summary>
113
            <version major="1" minor="68"/>
114
            <date day="20" month="3" year="2017"/>
115
            <author login="sdedic"/>
116
            <compatibility addition="yes"/>
117
            <description>
118
                <p>
119
                    A <code>SourceRootConfig</code> can be identifier by a structured "identifier" so when a new SourceGroup should be created as an
120
                    associcate to existing sources, it is created in appropriate "sibling" location. See <code>SourceGroupModifier</code> for more details.
121
                </p>
122
            </description>
123
        </change>
110
        <change id="server.is.free">
124
        <change id="server.is.free">
111
            <api name="general"/>
125
            <api name="general"/>
112
            <summary>Desktop dependent Ant Based Project Support UI extracted</summary>
126
            <summary>Desktop dependent Ant Based Project Support UI extracted</summary>
(-)a/project.ant/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.project.ant/1
2
OpenIDE-Module: org.netbeans.modules.project.ant/1
3
OpenIDE-Module-Specification-Version: 1.67
3
OpenIDE-Module-Specification-Version: 1.68
4
OpenIDE-Module-Layer: org/netbeans/modules/project/ant/resources/mf-layer.xml
4
OpenIDE-Module-Layer: org/netbeans/modules/project/ant/resources/mf-layer.xml
5
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties
5
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties
(-)a/project.ant/nbproject/project.xml (-1 / +1 lines)
Lines 81-87 Link Here
81
                    <compile-dependency/>
81
                    <compile-dependency/>
82
                    <run-dependency>
82
                    <run-dependency>
83
                        <release-version>1</release-version>
83
                        <release-version>1</release-version>
84
                        <specification-version>1.40</specification-version>
84
                        <specification-version>1.68</specification-version>
85
                    </run-dependency>
85
                    </run-dependency>
86
                </dependency>
86
                </dependency>
87
                <dependency>
87
                <dependency>
(-)a/project.ant/src/org/netbeans/spi/project/support/ant/SourcesHelper.java (-22 / +118 lines)
Lines 60-65 Link Here
60
import java.util.List;
60
import java.util.List;
61
import java.util.Map;
61
import java.util.Map;
62
import java.util.Set;
62
import java.util.Set;
63
import java.util.function.Function;
63
import java.util.logging.Level;
64
import java.util.logging.Level;
64
import java.util.logging.Logger;
65
import java.util.logging.Logger;
65
import javax.swing.Icon;
66
import javax.swing.Icon;
Lines 74-84 Link Here
74
import org.netbeans.api.queries.SharabilityQuery;
75
import org.netbeans.api.queries.SharabilityQuery;
75
import org.netbeans.modules.project.ant.AntBasedProjectFactorySingleton;
76
import org.netbeans.modules.project.ant.AntBasedProjectFactorySingleton;
76
import org.netbeans.spi.project.SourceGroupModifierImplementation;
77
import org.netbeans.spi.project.SourceGroupModifierImplementation;
77
import org.netbeans.spi.project.support.ant.AntProjectHelper;
78
import org.netbeans.spi.project.SourceGroupRelativeModifierImplementation;
78
import org.netbeans.spi.project.support.ant.PathMatcher;
79
import org.netbeans.spi.project.support.ant.PathMatcher;
80
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
81
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
82
import org.netbeans.spi.project.ui.support.ProjectConvertors;
79
import org.netbeans.spi.project.ui.support.ProjectConvertors;
83
import org.openide.filesystems.FileAttributeEvent;
80
import org.openide.filesystems.FileAttributeEvent;
84
import org.openide.filesystems.FileChangeListener;
81
import org.openide.filesystems.FileChangeListener;
Lines 142-149 Link Here
142
        private final String excludes;
139
        private final String excludes;
143
        private final String hint;
140
        private final String hint;
144
        private boolean removed;    // just for sanity checking
141
        private boolean removed;    // just for sanity checking
145
142
        private final String[] projectParts;
146
        public SourceRoot(String location, String includes, String excludes, String hint, String displayName, Icon icon, Icon openedIcon) {
143
        
144
        public SourceRoot(String location, String includes, String excludes, String hint, String displayName, Icon icon, Icon openedIcon, String[] parts) {
147
            super(location);
145
            super(location);
148
            this.displayName = displayName;
146
            this.displayName = displayName;
149
            this.icon = icon;
147
            this.icon = icon;
Lines 151-156 Link Here
151
            this.includes = includes;
149
            this.includes = includes;
152
            this.excludes = excludes;
150
            this.excludes = excludes;
153
            this.hint = hint;
151
            this.hint = hint;
152
            this.projectParts = parts;
154
            removed = false;
153
            removed = false;
155
        }
154
        }
156
155
Lines 322-329 Link Here
322
    
321
    
323
    private final class TypedSourceRoot extends SourceRoot {
322
    private final class TypedSourceRoot extends SourceRoot {
324
        private final String type;
323
        private final String type;
325
        public TypedSourceRoot(String type, String hint, String location, String includes, String excludes, String displayName, Icon icon, Icon openedIcon) {
324
        public TypedSourceRoot(String type, String hint, String location, String includes, String excludes, String displayName, Icon icon, Icon openedIcon, String[] parts) {
326
            super(location, includes, excludes, hint, displayName, icon, openedIcon);
325
            super(location, includes, excludes, hint, displayName, icon, openedIcon, parts);
327
            this.type = type;
326
            this.type = type;
328
        }
327
        }
329
        public final String getType() {
328
        public final String getType() {
Lines 404-409 Link Here
404
        private String excludes;
403
        private String excludes;
405
        private String type;
404
        private String type;
406
        private String hint;
405
        private String hint;
406
        private String[] parts;
407
407
408
        private SourceRootConfig(String location) {
408
        private SourceRootConfig(String location) {
409
            this.location = location;
409
            this.location = location;
Lines 505-510 Link Here
505
            openedIcon = value;
505
            openedIcon = value;
506
            return this;
506
            return this;
507
        }
507
        }
508
        
509
        /**
510
         * Declares that the source root resides in some (hierarchical) project part.
511
         * The project can be partitioned on multiple levels, each source root may represent some
512
         * part of the project. Partitioning can be used to identify "sibling" roots
513
         * @param parts abstract location of this root 
514
         * @return {@code this}
515
         * @since 1.68
516
         */
517
        public SourceRootConfig inParts(String... parts) {
518
            this.parts = parts;
519
            return this;
520
        }
508
521
509
        /**
522
        /**
510
         * Adds configured source root to <code>SourcesHelper</code>.
523
         * Adds configured source root to <code>SourcesHelper</code>.
Lines 519-527 Link Here
519
                throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N
532
                throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N
520
            }
533
            }
521
            if (type != null) {
534
            if (type != null) {
522
                typedSourceRoots.add(new TypedSourceRoot(type, hint, location, includes, excludes, displayName, icon, openedIcon));
535
                typedSourceRoots.add(new TypedSourceRoot(type, hint, location, includes, excludes, displayName, icon, openedIcon, parts));
523
            } else {
536
            } else {
524
                principalSourceRoots.add(new SourceRoot(location, includes, excludes, hint, displayName, icon, openedIcon));
537
                principalSourceRoots.add(new SourceRoot(location, includes, excludes, hint, displayName, icon, openedIcon, parts));
525
            }
538
            }
526
            return this;
539
            return this;
527
        }
540
        }
Lines 951-957 Link Here
951
            if (type.equals(Sources.TYPE_GENERIC)) {
964
            if (type.equals(Sources.TYPE_GENERIC)) {
952
                List<SourceRoot> roots = new ArrayList<SourceRoot>(principalSourceRoots);
965
                List<SourceRoot> roots = new ArrayList<SourceRoot>(principalSourceRoots);
953
                // Always include the project directory itself as a default:
966
                // Always include the project directory itself as a default:
954
                roots.add(new SourceRoot("", null, null, null, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null));
967
                roots.add(new SourceRoot("", null, null, null, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null, null));
955
                Map<FileObject,SourceRoot> rootsByDir = new LinkedHashMap<FileObject,SourceRoot>();
968
                Map<FileObject,SourceRoot> rootsByDir = new LinkedHashMap<FileObject,SourceRoot>();
956
                // First collect all non-redundant existing roots.
969
                // First collect all non-redundant existing roots.
957
                for (SourceRoot r : roots) {
970
                for (SourceRoot r : roots) {
Lines 1120-1130 Link Here
1120
    public SourceGroupModifierImplementation createSourceGroupModifierImplementation() {
1133
    public SourceGroupModifierImplementation createSourceGroupModifierImplementation() {
1121
        return new SourceGroupModifierImpl();
1134
        return new SourceGroupModifierImpl();
1122
    }
1135
    }
1136
    
1137
    private static final class Key implements Function<SourceRoot, Integer> {
1138
        private SourceRoot  root;
1139
        private String[]    parts;
1140
        
1141
        public Key(SourceRoot root, String[] parts) {
1142
            this.root = root;
1143
            this.parts = parts;
1144
        }
1145
        
1146
        public Integer apply(SourceRoot r) {
1147
            int result = 0;
1148
            int start = 0;
1149
            if (this.root != null) {
1150
                if (root.projectParts != null && r.projectParts != null) {
1151
                    for (int i = 0; i < root.projectParts.length && i < r.projectParts.length; i++) {
1152
                        if (root.projectParts[i].equals(r.projectParts[i])) {
1153
                            start++;
1154
                        } else {
1155
                            break;
1156
                        }
1157
                    }
1158
                }
1159
            }
1160
            if (parts != null && r.projectParts != null) {
1161
                for (int i = start; i < parts.length && i < r.projectParts.length; i++) {
1162
                    if (parts[i].equals(r.projectParts[i])) {
1163
                        result++;
1164
                    } else {
1165
                        break;
1166
                    }
1167
                }
1168
            }
1169
            return result + start;
1170
        }
1171
    }
1172
    
1173
    private class SourceGroupModifierImpl implements SourceGroupModifierImplementation, SourceGroupRelativeModifierImplementation {
1174
        private final Function<SourceRoot, Integer>  similarity;
1123
1175
1124
    private class SourceGroupModifierImpl implements SourceGroupModifierImplementation {
1176
        public SourceGroupModifierImpl() {
1125
1177
            this(null);
1178
        }
1179
        
1180
        public SourceGroupModifierImpl(Function<SourceRoot, Integer> similarity) {
1181
            this.similarity = similarity;
1182
        }
1183
        
1126
        public SourceGroup createSourceGroup(String type, String hint) {
1184
        public SourceGroup createSourceGroup(String type, String hint) {
1127
            SourceRoot root = findRoot(type, hint);
1185
            SourceRoot root = findRoot(type, hint, similarity);
1128
            if (root == null)
1186
            if (root == null)
1129
                return null;
1187
                return null;
1130
            if (root.isRemoved())
1188
            if (root.isRemoved())
Lines 1151-1175 Link Here
1151
        }
1209
        }
1152
1210
1153
        public boolean canCreateSourceGroup(String type, String hint) {
1211
        public boolean canCreateSourceGroup(String type, String hint) {
1154
            return findRoot(type, hint) != null;
1212
            return findRoot(type, hint, similarity) != null;
1155
        }
1213
        }
1156
        private SourceRoot findRoot(String type, String hint) {
1214
        
1215
        private SourceRoot findRoot(String type, String hint, Function<SourceRoot, Integer> similarity) {
1216
            int maxSimilarity = -1;
1217
            SourceRoot candidate = null;
1218
            
1157
            if (Sources.TYPE_GENERIC.equals(type)) {
1219
            if (Sources.TYPE_GENERIC.equals(type)) {
1158
                for (SourceRoot root : principalSourceRoots) {
1220
                for (SourceRoot root : principalSourceRoots) {
1159
                    if (root.getHint() != null
1221
                    if (root.getHint() != null
1160
                            && root.getHint().equals(hint)
1222
                            && root.getHint().equals(hint)
1161
                            && ! root.isRemoved())
1223
                            && ! root.isRemoved()) {
1162
                        return root;
1224
                        if (similarity == null) {
1225
                            return root;
1226
                        } else {
1227
                            int sim = similarity.apply(root);
1228
                            if (sim > maxSimilarity) {
1229
                                candidate = root;
1230
                                maxSimilarity = sim;
1231
                            }
1232
                        }
1233
                    }
1163
                }
1234
                }
1164
            } else {
1235
            } else {
1165
                for (TypedSourceRoot root : typedSourceRoots) {
1236
                for (TypedSourceRoot root : typedSourceRoots) {
1166
                    if (root.getHint() != null
1237
                    if (root.getHint() != null
1167
                            && root.getType().equals(type)
1238
                            && root.getType().equals(type)
1168
                            && root.getHint().equals(hint))
1239
                            && root.getHint().equals(hint)) {
1169
                        return root;
1240
                        if (similarity == null) {
1241
                            return root;
1242
                        } else {
1243
                            int sim = similarity.apply(root);
1244
                            if (sim > maxSimilarity) {
1245
                                candidate = root;
1246
                                maxSimilarity = sim;
1247
                            }
1248
                        }
1249
                    }
1170
                }
1250
                }
1171
            }
1251
            }
1172
            return null;
1252
            return candidate;
1253
        }
1254
1255
        public SourceGroupModifierImplementation relativeTo(SourceGroup existingGroup, String... projectPart) {
1256
            SourceRoot origin = null;
1257
            if (existingGroup != null) {
1258
                FileObject fo = existingGroup.getRootFolder();
1259
                File f = FileUtil.toFile(fo);
1260
                for (SourceRoot r : principalSourceRoots) {
1261
                    File loc = r.getActualLocation();
1262
                    if (loc != null && loc.equals(f)) {
1263
                        origin = r;
1264
                        break;
1265
                    }
1266
                }
1267
            }
1268
            return new SourceGroupModifierImpl(new Key(origin, projectPart));
1173
        }
1269
        }
1174
    }
1270
    }
1175
1271
(-)a/projectapi/apichanges.xml (+16 lines)
Lines 107-112 Link Here
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
108
108
109
    <changes>
109
    <changes>
110
        <change id="SourceGroupRelativeModifierImplementation">
111
            <api name="general"/>
112
            <summary>Added a <code>SourceGroupRelativeModifierImplementation</code> to improve source root creation</summary>
113
            <version major="1" minor="68"/>
114
            <date day="20" month="3" year="2017"/>
115
            <author login="sdedic"/>
116
            <compatibility addition="yes"/>
117
            <description>
118
                <p>
119
                    In presence of multiple source roots, e.g. several source folders, or test folders, some of them may be more
120
                    related to the 
121
                </p>
122
            </description>
123
            <class package="org.netbeans.spi.project.support" name="LookupProviderSupport"/>
124
            <issue number="253355"/>
125
        </change>
110
        <change id="SharabilityQueryMerger">
126
        <change id="SharabilityQueryMerger">
111
            <api name="general"/>
127
            <api name="general"/>
112
            <summary>Added a <code>LookupMerger</code> for <code>SharabilityQueryImplementation2</code></summary>
128
            <summary>Added a <code>LookupMerger</code> for <code>SharabilityQueryImplementation2</code></summary>
(-)a/projectapi/manifest.mf (-1 / +1 lines)
Lines 1-6 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.projectapi/1
2
OpenIDE-Module: org.netbeans.modules.projectapi/1
3
OpenIDE-Module-Specification-Version: 1.67
3
OpenIDE-Module-Specification-Version: 1.68
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
5
OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml
5
OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml
6
OpenIDE-Module-Recommends: cnb.org.netbeans.modules.projectapi.nb
6
OpenIDE-Module-Recommends: cnb.org.netbeans.modules.projectapi.nb
(-)a/projectapi/nbproject/project.properties (-1 / +1 lines)
Lines 43-49 Link Here
43
is.autoload=true
43
is.autoload=true
44
44
45
javac.compilerargs=-Xlint -Xlint:-serial
45
javac.compilerargs=-Xlint -Xlint:-serial
46
javac.source=1.7
46
javac.source=1.8
47
javadoc.arch=${basedir}/arch.xml
47
javadoc.arch=${basedir}/arch.xml
48
javadoc.apichanges=${basedir}/apichanges.xml
48
javadoc.apichanges=${basedir}/apichanges.xml
49
49
(-)a/projectapi/src/org/netbeans/api/project/SourceGroupModifier.java (+41 lines)
Lines 43-48 Link Here
43
package org.netbeans.api.project;
43
package org.netbeans.api.project;
44
44
45
import org.netbeans.spi.project.SourceGroupModifierImplementation;
45
import org.netbeans.spi.project.SourceGroupModifierImplementation;
46
import org.netbeans.spi.project.SourceGroupRelativeModifierImplementation;
46
47
47
/**
48
/**
48
 * <code>SourceGroupModifier</code> provides ways of create specific folders ({@link org.netbeans.api.project.SourceGroup} root folders)
49
 * <code>SourceGroupModifier</code> provides ways of create specific folders ({@link org.netbeans.api.project.SourceGroup} root folders)
Lines 76-81 Link Here
76
        }
77
        }
77
        return impl.createSourceGroup(type, hint);
78
        return impl.createSourceGroup(type, hint);
78
    }
79
    }
80
    
81
    /**
82
     * Creates a source group associated to an existing one. In a project with multiple locations for sources or tests some of those locations
83
     * can be more appropriate (or completely unrelated) to already existing specific sources. This variant of {@link #createSourceGroup(org.netbeans.api.project.Project, java.lang.String, java.lang.String)}
84
     * allows to select appropriate locations, if the newly created {@code SourceGroup} should work in association with some existing one.
85
     * <p/>
86
     * The source group will be created on location most similar to the provided {@code original} group. If {@code projectParts} are specified, the most matching
87
     * location will be selected.
88
     * <p/>
89
     * This feature is prototypically used in J2SE modular projects, where multiple locations exists for tests and sources, yet they are related by their owning module. Other
90
     * project types may also partition project sources into logical groups, similar to modules.
91
     * <p/>
92
     * Some (java) examples:
93
     * <ul>
94
     * <li>to create a source folder in project module, use <code>relativeTo(modulesGroup, "moduleName").createSourceGroup(..)</code>
95
     * <li>to create a specific module root in project module, use <code>relativeTo(modulesGroup, "moduleName", "path-to-modules").createSourceGroup(...)</code>
96
     * <li>to create a test folder for a specific source location, use <code>relativeTo(sourceLocation).createSourceGroup(...)</code>
97
     * <li>or, if there are more test locations to choose, you can use <code>relativeTo(sourceLocation, "test2").createSourceGroup(...)</code>.
98
     * </ul>
99
     * @param project the project
100
     * @param original the original SourceGroup, which the new one should be related to.
101
     * @param type type of sources
102
     * @param hint additional type hint
103
     * @param projectParts optional; abstract location within the project.
104
     * @return the creaed SourceGroup or {@code null}
105
     * @since 1.68
106
     */
107
    public static final SourceGroup createAssociatedSourceGroup(Project project, SourceGroup original, String type, String hint, String... projectParts) {
108
        SourceGroupRelativeModifierImplementation relMod = project.getLookup().lookup(SourceGroupRelativeModifierImplementation.class);
109
        if (relMod == null) {
110
            return createSourceGroup(project, type, hint);
111
        } else {
112
            SourceGroupModifierImplementation impl =  relMod.relativeTo(original, projectParts);
113
            if (impl == null) {
114
                return createSourceGroup(project, type, hint);
115
            } else {
116
                return impl.createSourceGroup(type, hint);
117
            }
118
        }
119
    }
79
120
80
    /**
121
    /**
81
     * Creates a {@link org.netbeans.api.project.SourceGroupModifier.Future} object
122
     * Creates a {@link org.netbeans.api.project.SourceGroupModifier.Future} object
(-)a/projectapi/src/org/netbeans/spi/project/SourceGroupRelativeModifierImplementation.java (+73 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright (c) 2017 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
package org.netbeans.spi.project;
41
42
import org.netbeans.api.project.SourceGroup;
43
44
/**
45
 * Intermediate level for more structured projects, where the simple
46
 * type-based information are not sufficient to create an appropriate folder
47
 * structure. 
48
 * <p/>
49
 * Prototypically used in J2SE Modular projects, where tests or sources belong
50
 * to different modules, and it is critical to create the folder in the "correct"
51
 * one.
52
 * <p/>
53
 * The project can be partitioned to several (hiearchical) parts. SourceGroups for
54
 * certain types/hints can be created in some of those parts (see {@link SourceGroupModifierImplementation#canCreateSourceGroup}.
55
 * For example, java modular projects contains modules, a module may contain several places where sources are expected - these
56
 * form the part hierarchy. When the original SourceGroup is specific enough, the hierarchy argument may be
57
 * missing or can be even ignored by the modifier implementation - provided that the newly created folders have the correct
58
 * relationship to the original source group.
59
 * <p/>
60
 * Similar structure may be used in other types of projects. {@code projectParts} are abstract uninterpreted identifiers, so 
61
 * the implementation / project may choose any semantics suitable for the project type.
62
 * @author sdedic
63
 * @since 1.68
64
 */
65
public interface SourceGroupRelativeModifierImplementation {
66
    /**
67
     * Returns Modifier, which is bound to a specific location or conceptual part of the project.
68
     * @param existingGroup existing location or concept within the project
69
     * @param projectPart identifies part of the project. The meaning depends on the "existingGroup"
70
     * @return modifier able to create folders, or {@code null}, if the specified project part does not exist
71
     */
72
    public SourceGroupModifierImplementation    relativeTo(SourceGroup existingGroup, String... projectPart);
73
}

Return to bug 270131