@@ -, +, @@ They should also share the same ClassPath.COMPILE etc. This means that they are available in Go to Type, code completion, Find Usages, etc. (The Java parser may consider true roots to be in error after a clean, but building should fix it.) --- a/java.api.common/nbproject/project.xml +++ a/java.api.common/nbproject/project.xml @@ -123,7 +123,7 @@ - 7.19 + 7.20 @@ -187,6 +187,11 @@ + org.openide.filesystems + + + + org.openide.util --- a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathProviderImpl.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathProviderImpl.java @@ -66,6 +66,7 @@ public final class ClassPathProviderImpl implements ClassPathProvider { private String buildClassesDir = "build.classes.dir"; // NOI18N + private static final String buildGeneratedDir = "build.generated.dir"; // NOI18N private String distJar = "dist.jar"; // NOI18N private String buildTestClassesDir = "build.test.classes.dir"; // NOI18N private String[] javacClasspath = new String[]{"javac.classpath"}; //NOI18N @@ -147,7 +148,11 @@ private FileObject getBuildClassesDir() { return getDir(buildClassesDir); } - + + private FileObject getBuildGeneratedDir() { + return getDir(buildGeneratedDir); + } + private FileObject getDistJar() { return getDir(distJar); } @@ -196,6 +201,10 @@ if (dir != null && (dir.equals(file) || FileUtil.isParentOf(dir,file))) { return 3; } + dir = getBuildGeneratedDir(); + if (dir != null && FileUtil.isParentOf(dir, file) /* but dir != file */) { // #105645 + return 0; + } return -1; } --- a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/SourcePathImplementation.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/SourcePathImplementation.java @@ -66,6 +66,7 @@ import org.openide.filesystems.FileChangeListener; import org.openide.filesystems.FileEvent; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; import org.openide.filesystems.FileUtil; import org.openide.util.Exceptions; import org.openide.util.RequestProcessor; @@ -78,6 +79,7 @@ // TODO: if needed these parameters can be configurable via constructor parameter: private static final String BUILD_DIR = "build.dir"; // NOI18N + private static final String BUILD_GENERATED_DIR = "build.generated.dir"; // NOI18N private static final String DIR_GEN_BINDINGS = "generated/addons"; // NOI18N private static RequestProcessor REQ_PROCESSOR = new RequestProcessor(); // No I18N @@ -89,6 +91,19 @@ private FileChangeListener fcl = null; private PropertyEvaluator evaluator; private boolean canHaveWebServices; + private File buildGeneratedDir = null; + private final FileChangeListener buildGeneratedDirListener = new FileChangeAdapter() { + public @Override void fileFolderCreated(FileEvent fe) { + // XXX could do this asynch like SourceRootScannerTask, but would need to do synch during unit test + invalidate(); + } + public @Override void fileDeleted(FileEvent fe) { + invalidate(); + } + public @Override void fileRenamed(FileRenameEvent fe) { + invalidate(); + } + }; /** * Construct the implementation. @@ -246,6 +261,25 @@ createListener(buildDir, new String[] {DIR_GEN_BINDINGS}); } + String buildGeneratedDirS = evaluator.getProperty(BUILD_GENERATED_DIR); + if (buildGeneratedDirS != null) { + File _buildGeneratedDir = projectHelper.resolveFile(buildGeneratedDirS); + if (!_buildGeneratedDir.equals(buildGeneratedDir)) { + if (buildGeneratedDir != null) { + FileUtil.removeFileChangeListener(buildGeneratedDirListener, buildGeneratedDir); + } + buildGeneratedDir = _buildGeneratedDir; + FileUtil.addFileChangeListener(buildGeneratedDirListener, buildGeneratedDir); + } + if (buildGeneratedDir.isDirectory()) { // #105645 + for (File root : buildGeneratedDir.listFiles()) { + if (!root.isDirectory()) { + continue; + } + result.add(ClassPathSupport.createResource(root.toURI().toURL())); + } + } + } } catch (MalformedURLException ex) { Exceptions.printStackTrace(ex); } --- a/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/Bundle.properties +++ a/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/Bundle.properties @@ -66,4 +66,5 @@ #JavaSourceNodeFactory LBL_Properties_Action=Properties - +# {0} - folder name +JavaSourceNodeFactory.gensrc=Generated Sources ({0}) --- a/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/JavaSourceNodeFactory.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/JavaSourceNodeFactory.java @@ -42,11 +42,16 @@ package org.netbeans.modules.java.api.common.project.ui; import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; +import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.Icon; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -60,7 +65,12 @@ import org.netbeans.spi.project.ui.CustomizerProvider; import org.netbeans.spi.project.ui.support.NodeFactory; import org.netbeans.spi.project.ui.support.NodeList; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileEvent; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; +import org.openide.filesystems.FileUtil; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.util.ChangeSupport; @@ -83,37 +93,78 @@ private static class SourcesNodeList implements NodeList, ChangeListener { private final Project project; - + private final File genSrcDir; + private final FileChangeListener genSrcDirListener; private final ChangeSupport changeSupport = new ChangeSupport(this); - + public SourcesNodeList(Project proj) { project = proj; + genSrcDirListener = new FileChangeAdapter() { + public @Override void fileFolderCreated(FileEvent fe) { + stateChanged(null); + } + public @Override void fileDeleted(FileEvent fe) { + stateChanged(null); + } + public @Override void fileRenamed(FileRenameEvent fe) { + stateChanged(null); + } + }; + File d = FileUtil.toFile(proj.getProjectDirectory()); + // XXX hardcodes the value of ${build.generated.dir}, since we have no access to evaluator + genSrcDir = d != null ? new File(d, "build/generated-sources") : null; } public List keys() { if (this.project.getProjectDirectory() == null || !this.project.getProjectDirectory().isValid()) { return Collections.emptyList(); } - Sources sources = getSources(); - SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA); - - List result = new ArrayList(groups.length); - for( int i = 0; i < groups.length; i++ ) { - result.add(new SourceGroupKey(groups[i])); + List result = new ArrayList(); + for (SourceGroup group : getSources().getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) { + result.add(new SourceGroupKey(group, true)); + } + FileObject genSrc = FileUtil.toFileObject(genSrcDir); + if (genSrc != null) { + for (final FileObject child : genSrc.getChildren()) { + if (!child.isFolder()) { + continue; + } + result.add(new SourceGroupKey(new SourceGroup() { + public FileObject getRootFolder() { + return child; + } + public String getName() { + return child.getNameExt(); + } + public String getDisplayName() { + return NbBundle.getMessage(JavaSourceNodeFactory.class, "JavaSourceNodeFactory.gensrc", child.getNameExt()); + } + public Icon getIcon(boolean opened) { + return null; + } + public boolean contains(FileObject file) throws IllegalArgumentException { + return true; + } + public void addPropertyChangeListener(PropertyChangeListener listener) {} + public void removePropertyChangeListener(PropertyChangeListener listener) {} + }, false)); + } } return result; } public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); + FileUtil.addFileChangeListener(genSrcDirListener, genSrcDir); } public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); + FileUtil.removeFileChangeListener(genSrcDirListener, genSrcDir); } public Node node(SourceGroupKey key) { - return new PackageViewFilterNode(key.group, project); + return new PackageViewFilterNode(key, project); } public void addNotify() { @@ -144,10 +195,12 @@ public final SourceGroup group; public final FileObject fileObject; + public final boolean trueSource; - SourceGroupKey(SourceGroup group) { + SourceGroupKey(SourceGroup group, boolean trueSource) { this.group = group; this.fileObject = group.getRootFolder(); + this.trueSource = trueSource; } @Override @@ -182,35 +235,38 @@ } - /** Yet another cool filter node just to add properties action + /** + * Adjusts context menu of source group root node. */ private static class PackageViewFilterNode extends FilterNode { private final String nodeName; private final Project project; + private final boolean trueSource; - Action[] actions; - - public PackageViewFilterNode(SourceGroup sourceGroup, Project project) { - super(PackageView.createPackageView(sourceGroup)); + public PackageViewFilterNode(SourceGroupKey sourceGroupKey, Project project) { + super(PackageView.createPackageView(sourceGroupKey.group)); this.project = project; this.nodeName = "Sources"; + trueSource = sourceGroupKey.trueSource; } - - public Action[] getActions(boolean context) { - if (!context) { - if (actions == null) { - Action superActions[] = super.getActions(context); - actions = new Action[superActions.length + 2]; - System.arraycopy(superActions, 0, actions, 0, superActions.length); - actions[superActions.length] = null; - actions[superActions.length + 1] = new PreselectPropertiesAction(project, nodeName); + public @Override Action[] getActions(boolean context) { + List actions = new ArrayList(Arrays.asList(super.getActions(context))); + if (trueSource) { + actions.add(null); + actions.add(new PreselectPropertiesAction(project, nodeName)); + } else { + // Just take out "New File..." as this would be misleading. + Iterator scan = actions.iterator(); + while (scan.hasNext()) { + Action a = scan.next(); + if (a != null && a.getClass().getName().equals("org.netbeans.modules.project.ui.actions.NewFile$WithSubMenu")) { // NOI18N + scan.remove(); + } } - return actions; - } else { - return super.getActions(context); } + return actions.toArray(new Action[actions.size()]); } } --- a/java.api.common/src/org/netbeans/modules/java/api/common/queries/BinaryForSourceQueryImpl.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/queries/BinaryForSourceQueryImpl.java @@ -43,8 +43,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.File; -import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; @@ -57,8 +55,8 @@ import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation; import org.netbeans.spi.project.support.ant.AntProjectHelper; import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.openide.filesystems.FileUtil; import org.openide.util.ChangeSupport; -import org.openide.util.Exceptions; /** * @@ -100,14 +98,23 @@ if (root.equals(sourceRoot)) { result = new R (sourceProps); cache.put (sourceRoot,result); - break; + return result; } } for (URL root : this.test.getRootURLs()) { if (root.equals(sourceRoot)) { result = new R (testProps); cache.put (sourceRoot,result); - break; + return result; + } + } + String buildGeneratedDirS = eval.getProperty("build.generated.dir"); + if (buildGeneratedDirS != null) { // #105645 + String parent = helper.resolveFile(buildGeneratedDirS).toURI().toString(); + if (sourceRoot.toString().startsWith(parent)) { + result = new R(sourceProps); + cache.put(sourceRoot, result); + return result; } } } @@ -130,14 +137,7 @@ for (String propName : propNames) { String val = eval.getProperty(propName); if (val != null) { - File f = helper.resolveFile(val); - if (f != null) { - try { - urls.add(f.toURI().toURL()); - } catch (MalformedURLException e) { - Exceptions.printStackTrace(e); - } - } + urls.add(FileUtil.urlForArchiveOrDir(helper.resolveFile(val))); } } return urls.toArray(new URL[urls.size()]); --- a/java.api.common/src/org/netbeans/modules/java/api/common/queries/CompiledSourceForBinaryQueryImpl.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/queries/CompiledSourceForBinaryQueryImpl.java @@ -50,8 +50,11 @@ import java.net.MalformedURLException; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; import java.util.HashMap; +import java.util.List; import javax.swing.event.ChangeListener; import org.netbeans.api.java.queries.SourceForBinaryQuery; import org.netbeans.spi.project.support.ant.PropertyEvaluator; @@ -116,7 +119,7 @@ if (src == null) { return null; } - res = new Result(src); + res = new Result(src, src == sourceRoots); cache.put(binaryRoot, res); return res; } @@ -146,13 +149,30 @@ private final ChangeSupport changeSupport = new ChangeSupport(this); private SourceRoots sourceRoots; + private final boolean gensrc; - public Result(SourceRoots sourceRoots) { + public Result(SourceRoots sourceRoots, boolean gensrc) { this.sourceRoots = sourceRoots; this.sourceRoots.addPropertyChangeListener(this); + this.gensrc = gensrc; } public FileObject[] getRoots() { + if (gensrc) { // #105645 + String buildGeneratedDirS = evaluator.getProperty("build.generated.dir"); // NOI18N + if (buildGeneratedDirS != null) { + FileObject buildGeneratedDir = helper.resolveFileObject(buildGeneratedDirS); + if (buildGeneratedDir != null) { + List roots = new ArrayList(Arrays.asList(sourceRoots.getRoots())); + for (FileObject root : buildGeneratedDir.getChildren()) { + if (root.isFolder()) { + roots.add(root); + } + } + return roots.toArray(new FileObject[roots.size()]); + } + } + } return this.sourceRoots.getRoots(); // no need to cache it, SourceRoots does } --- a/java.api.common/src/org/netbeans/modules/java/api/common/queries/FileBuiltQueryImpl.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/queries/FileBuiltQueryImpl.java @@ -42,6 +42,9 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.List; import org.netbeans.api.project.ProjectManager; import org.openide.filesystems.FileObject; import org.netbeans.api.queries.FileBuiltQuery; @@ -49,6 +52,11 @@ import org.netbeans.spi.queries.FileBuiltQueryImplementation; import org.netbeans.spi.project.support.ant.AntProjectHelper; import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileRenameEvent; +import org.openide.filesystems.FileUtil; import org.openide.util.Mutex.Action; @@ -63,6 +71,18 @@ private final PropertyEvaluator evaluator; private final SourceRoots sourceRoots; private final SourceRoots testRoots; + private File buildGeneratedDir = null; + private final FileChangeListener buildGeneratedDirListener = new FileChangeAdapter() { + public @Override void fileFolderCreated(FileEvent fe) { + invalidate(); + } + public @Override void fileDeleted(FileEvent fe) { + invalidate(); + } + public @Override void fileRenamed(FileRenameEvent fe) { + invalidate(); + } + }; FileBuiltQueryImpl(AntProjectHelper helper, PropertyEvaluator evaluator, SourceRoots sourceRoots, SourceRoots testRoots) { @@ -96,27 +116,50 @@ private FileBuiltQueryImplementation createDelegate() { - String[] srcRoots = sourceRoots.getRootProperties(); - String[] tstRoots = testRoots.getRootProperties(); - String[] from = new String [srcRoots.length + tstRoots.length]; - String[] to = new String [srcRoots.length + tstRoots.length]; - for (int i = 0; i < srcRoots.length; i++) { - from[i] = "${" + srcRoots[i] + "}/*.java"; // NOI18N - to[i] = "${build.classes.dir}/*.class"; // NOI18N + List from = new ArrayList(); + List to = new ArrayList(); + for (String r : sourceRoots.getRootProperties()) { + from.add("${" + r + "}/*.java"); // NOI18N + to.add("${build.classes.dir}/*.class"); // NOI18N } - for (int i = 0; i < tstRoots.length; i++) { - from[srcRoots.length + i] = "${" + tstRoots[i] + "}/*.java"; // NOI18N - to[srcRoots.length + i] = "${build.test.classes.dir}/*.class"; // NOI18N + for (String r : testRoots.getRootProperties()) { + from.add("${" + r + "}/*.java"); // NOI18N + to.add("${build.test.classes.dir}/*.class"); // NOI18N } - return helper.createGlobFileBuiltQuery(evaluator, from, to); // save to pass APH + String buildGeneratedDirS = evaluator.getProperty("build.generated.dir"); // NOI18N + if (buildGeneratedDirS != null) { // #105645 + File _buildGeneratedDir = helper.resolveFile(buildGeneratedDirS); + if (!_buildGeneratedDir.equals(buildGeneratedDir)) { + if (buildGeneratedDir != null) { + FileUtil.removeFileChangeListener(buildGeneratedDirListener, buildGeneratedDir); + } + buildGeneratedDir = _buildGeneratedDir; + FileUtil.addFileChangeListener(buildGeneratedDirListener, buildGeneratedDir); + } + if (buildGeneratedDir.isDirectory()) { + for (File root : buildGeneratedDir.listFiles()) { + if (!root.isDirectory()) { + continue; + } + from.add(root + "/*.java"); // NOI18N + to.add("${build.classes.dir}/*.class"); // NOI18N + } + } + } + return helper.createGlobFileBuiltQuery(evaluator, + from.toArray(new String[from.size()]), + to.toArray(new String[to.size()])); } public void propertyChange(PropertyChangeEvent evt) { if (SourceRoots.PROP_ROOT_PROPERTIES.equals(evt.getPropertyName())) { - synchronized (this) { - delegate = null; - // XXX: what to do with already returned Statuses - } + invalidate(); } } + + private synchronized void invalidate() { + delegate = null; + // XXX: what to do with already returned Statuses + } + } --- a/java.api.common/test/unit/src/org/netbeans/modules/java/api/common/queries/GeneratedSourceRootTest.java +++ a/java.api.common/test/unit/src/org/netbeans/modules/java/api/common/queries/GeneratedSourceRootTest.java @@ -0,0 +1,364 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.java.api.common.queries; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.project.JavaProjectConstants; +import org.netbeans.api.java.queries.BinaryForSourceQuery; +import org.netbeans.api.java.queries.SourceForBinaryQuery; +import org.netbeans.api.java.queries.SourceLevelQuery; +import org.netbeans.api.java.queries.UnitTestForSourceQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.api.project.SourceGroup; +import org.netbeans.api.project.Sources; +import org.netbeans.api.queries.FileBuiltQuery; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.api.common.SourceRoots; +import org.netbeans.modules.java.api.common.ant.UpdateHelper; +import org.netbeans.modules.java.api.common.ant.UpdateImplementation; +import org.netbeans.modules.java.api.common.classpath.ClassPathProviderImpl; +import org.netbeans.spi.project.AuxiliaryConfiguration; +import org.netbeans.spi.project.support.ant.AntBasedProjectType; +import org.netbeans.spi.project.support.ant.AntProjectHelper; +import org.netbeans.spi.project.support.ant.EditableProperties; +import org.netbeans.spi.project.support.ant.ProjectGenerator; +import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.netbeans.spi.project.support.ant.ReferenceHelper; +import org.netbeans.spi.project.support.ant.SourcesHelper; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.test.TestFileUtils; +import org.openide.util.ChangeSupport; +import org.openide.util.Lookup; +import org.openide.util.Mutex; +import org.openide.util.lookup.Lookups; +import org.openide.util.test.MockLookup; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Test for #105645 functionality: build/generated-sources/NAME/ roots. + */ +public class GeneratedSourceRootTest extends NbTestCase { + + public GeneratedSourceRootTest(String n) { + super(n); + } + + public void testSourceRoots() throws Exception { + Project p = createTestProject(true); + FileObject d = p.getProjectDirectory(); + FileObject src = d.getFileObject("src"); + FileObject stuff = d.getFileObject("build/generated-sources/stuff"); + SourceGroup[] groups = ProjectUtils.getSources(p).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA); + assertEquals(2, groups.length); + assertEquals(src, groups[0].getRootFolder()); + assertEquals(d.getFileObject("test"), groups[1].getRootFolder()); + ClassPath sourcePath = ClassPath.getClassPath(src, ClassPath.SOURCE); + assertEquals(Arrays.asList(src, stuff), Arrays.asList(sourcePath.getRoots())); + FileObject moreStuff = FileUtil.createFolder(d, "build/generated-sources/morestuff"); + assertEquals(Arrays.asList(src, stuff, moreStuff), Arrays.asList(sourcePath.getRoots())); + ClassPath compile = ClassPath.getClassPath(src, ClassPath.COMPILE); + assertEquals(compile, ClassPath.getClassPath(stuff, ClassPath.COMPILE)); + assertEquals(compile, ClassPath.getClassPath(moreStuff, ClassPath.COMPILE)); + assertEquals(ClassPath.getClassPath(src, ClassPath.EXECUTE), ClassPath.getClassPath(stuff, ClassPath.EXECUTE)); + assertEquals(ClassPath.getClassPath(src, ClassPath.BOOT), ClassPath.getClassPath(stuff, ClassPath.BOOT)); + d.getFileObject("build").delete(); + assertEquals(Arrays.asList(src), Arrays.asList(sourcePath.getRoots())); + } + + public void testMiscellaneousQueries() throws Exception { + Project p = createTestProject(true); + FileObject d = p.getProjectDirectory(); + FileObject src = d.getFileObject("src"); + FileObject test = d.getFileObject("test"); + FileObject stuff = d.getFileObject("build/generated-sources/stuff"); + URL classes = new URL(d.getURL(), "build/classes/"); + URL testClasses = new URL(d.getURL(), "build/test/classes/"); + FileObject xgen = stuff.getFileObject("net/nowhere/XGen.java"); + assertEquals(Arrays.asList(src, stuff), Arrays.asList(SourceForBinaryQuery.findSourceRoots(classes).getRoots())); + assertEquals(Arrays.asList(test), Arrays.asList(SourceForBinaryQuery.findSourceRoots(testClasses).getRoots())); + assertEquals(Collections.singletonList(classes), Arrays.asList(BinaryForSourceQuery.findBinaryRoots(src.getURL()).getRoots())); + assertEquals(Collections.singletonList(testClasses), Arrays.asList(BinaryForSourceQuery.findBinaryRoots(test.getURL()).getRoots())); + assertEquals(Collections.singletonList(classes), Arrays.asList(BinaryForSourceQuery.findBinaryRoots(stuff.getURL()).getRoots())); + assertEquals(Collections.singletonList(src.getURL()), Arrays.asList(UnitTestForSourceQuery.findSources(test))); + assertEquals(Collections.singletonList(test.getURL()), Arrays.asList(UnitTestForSourceQuery.findUnitTests(src))); + assertEquals("1.5", SourceLevelQuery.getSourceLevel(stuff)); + FileBuiltQuery.Status status = FileBuiltQuery.getStatus(xgen); + assertNotNull(status); + assertFalse(status.isBuilt()); + FileUtil.createData(d, "build/classes/net/nowhere/XGen.class"); + assertTrue(status.isBuilt()); + assertEquals("ISO-8859-2", FileEncodingQuery.getEncoding(xgen).name()); + // check also dynamic changes in set of gensrc roots: + FileObject moreStuff = FileUtil.createFolder(d, "build/generated-sources/morestuff"); + FileObject ygen = FileUtil.createData(moreStuff, "net/nowhere/YGen.java"); + assertEquals(Arrays.asList(src, stuff, moreStuff), Arrays.asList(SourceForBinaryQuery.findSourceRoots(classes).getRoots())); + assertEquals(Collections.singletonList(classes), Arrays.asList(BinaryForSourceQuery.findBinaryRoots(moreStuff.getURL()).getRoots())); + // XXX should previously created Result objects fire changes? ideally yes, but probably unnecessary + assertEquals("1.5", SourceLevelQuery.getSourceLevel(moreStuff)); + status = FileBuiltQuery.getStatus(ygen); + assertNotNull(status); + assertFalse(status.isBuilt()); + FileUtil.createData(d, "build/classes/net/nowhere/YGen.class"); + assertTrue(status.isBuilt()); + assertEquals("ISO-8859-2", FileEncodingQuery.getEncoding(ygen).name()); + d.getFileObject("build").delete(); + assertEquals(Arrays.asList(src), Arrays.asList(SourceForBinaryQuery.findSourceRoots(classes).getRoots())); + } + + public void testFirstGenSrcAddedDynamically() throws Exception { + Project p = createTestProject(false); + FileObject d = p.getProjectDirectory(); + FileObject src = d.getFileObject("src"); + URL classes = new URL(d.getURL(), "build/classes/"); + ClassPath sourcePath = ClassPath.getClassPath(src, ClassPath.SOURCE); + assertEquals(Arrays.asList(src), Arrays.asList(sourcePath.getRoots())); + assertEquals(Arrays.asList(src), Arrays.asList(SourceForBinaryQuery.findSourceRoots(classes).getRoots())); + // now add the first gensrc root: + FileObject stuff = FileUtil.createFolder(d, "build/generated-sources/stuff"); + FileObject xgen = FileUtil.createData(stuff, "net/nowhere/XGen.java"); + assertEquals(Arrays.asList(src, stuff), Arrays.asList(sourcePath.getRoots())); + assertEquals(ClassPath.getClassPath(src, ClassPath.COMPILE), ClassPath.getClassPath(stuff, ClassPath.COMPILE)); + assertEquals(Arrays.asList(src, stuff), Arrays.asList(SourceForBinaryQuery.findSourceRoots(classes).getRoots())); + assertEquals(Collections.singletonList(classes), Arrays.asList(BinaryForSourceQuery.findBinaryRoots(stuff.getURL()).getRoots())); + FileBuiltQuery.Status status = FileBuiltQuery.getStatus(xgen); + assertNotNull(status); + assertFalse(status.isBuilt()); + FileUtil.createData(d, "build/classes/net/nowhere/XGen.class"); + assertTrue(status.isBuilt()); + } + + protected @Override void setUp() throws Exception { + clearWorkDir(); + MockLookup.setInstances(new TestAntBasedProjectType()); + } + + private Project createTestProject(boolean initGenRoot) throws Exception { + final FileObject dir = FileUtil.toFileObject(getWorkDir()); + TestFileUtils.writeFile(dir, "src/net/nowhere/X.java", + "package net.nowhere; public class X {}"); + TestFileUtils.writeFile(dir, "test/net/nowhere/XTest.java", + "package net.nowhere; public class XTest {}"); + if (initGenRoot) { + TestFileUtils.writeFile(dir, "build/generated-sources/stuff/net/nowhere/XGen.java", + "package net.nowhere; public class XGen {}"); + } + return ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() { + public Project run() throws Exception { + AntProjectHelper h = ProjectGenerator.createProject(dir, "test"); + EditableProperties pp = h.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); + pp.setProperty("src.dir", "src"); + pp.setProperty("test.src.dir", "test"); + pp.setProperty("build.dir", "build"); + pp.setProperty("build.classes.dir", "${build.dir}/classes"); + pp.setProperty("build.test.classes.dir", "${build.dir}/test/classes"); + pp.setProperty("build.generated.dir", "${build.dir}/generated-sources"); + pp.setProperty("javac.classpath", "lib.jar"); + pp.setProperty("javac.test.classpath", "${javac.classpath}:junit.jar"); + pp.setProperty("run.classpath", "${javac.classpath}:${build.classes.dir}:runlib.jar"); + pp.setProperty("run.test.classpath", "${javac.test.classpath}:${build.test.classes.dir}:runlib.jar"); + pp.setProperty("dist.dir", "dist"); + pp.setProperty("dist.jar", "${dist.dir}/x.jar"); + pp.setProperty("javac.source", "1.5"); + pp.setProperty("encoding", "ISO-8859-2"); + h.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, pp); + Element data = h.getPrimaryConfigurationData(true); + Document doc = data.getOwnerDocument(); + ((Element) data.appendChild(doc.createElementNS(NS, "source-roots")). + appendChild(doc.createElementNS(NS, "root"))). + setAttribute("id", "src.dir"); + ((Element) data.appendChild(doc.createElementNS(NS, "test-roots")). + appendChild(doc.createElementNS(NS, "root"))). + setAttribute("id", "test.src.dir"); + h.putPrimaryConfigurationData(data, true); + Project p = ProjectManager.getDefault().findProject(dir); + assertEquals(TestProject.class, p.getClass()); + ProjectManager.getDefault().saveProject(p); + return p; + } + }); + } + + private static final String NS = "urn:test"; + private static class TestAntBasedProjectType implements AntBasedProjectType { + public String getType() { + return "test"; + } + public Project createProject(AntProjectHelper helper) throws IOException { + return new TestProject(helper); + } + public String getPrimaryConfigurationDataElementName(boolean shared) { + return "data"; + } + public String getPrimaryConfigurationDataElementNamespace(boolean shared) { + return NS; + } + } + private static class TestProject implements Project { + private final AntProjectHelper helper; + private Lookup lookup; + TestProject(AntProjectHelper helper) { + this.helper = helper; + } + public FileObject getProjectDirectory() { + return helper.getProjectDirectory(); + } + public synchronized Lookup getLookup() { + if (lookup == null) { + PropertyEvaluator evaluator = helper.getStandardPropertyEvaluator(); + AuxiliaryConfiguration aux = helper.createAuxiliaryConfiguration(); + ReferenceHelper refHelper = new ReferenceHelper(helper, aux, evaluator); + UpdateHelper upHelper = new UpdateHelper(new UpdateImplementation() { + public boolean isCurrent() {return true;} + public boolean canUpdate() {throw new AssertionError();} + public void saveUpdate(EditableProperties props) throws IOException {throw new AssertionError();} + public Element getUpdatedSharedConfigurationData() {throw new AssertionError();} + public EditableProperties getUpdatedProjectProperties() {throw new AssertionError();} + }, helper); + SourceRoots src = SourceRoots.create(upHelper, evaluator, refHelper, NS, + "source-roots", false, "src.{0}{1}.dir"); + SourceRoots test = SourceRoots.create(upHelper, evaluator, refHelper, NS, + "test-roots", false, "test.{0}{1}.dir"); + lookup = Lookups.fixed( + aux, + new TestSources(helper, evaluator, src, test), + new ClassPathProviderImpl(this.helper, evaluator, src, test), + QuerySupport.createCompiledSourceForBinaryQuery(helper, evaluator, src, test), + QuerySupport.createBinaryForSourceQueryImplementation(src, test, helper, evaluator), + QuerySupport.createUnitTestForSourceQuery(src, test), + QuerySupport.createSourceLevelQuery(evaluator), + QuerySupport.createFileBuiltQuery(helper, evaluator, src, test), + QuerySupport.createFileEncodingQuery(evaluator, "encoding") + ); + } + return lookup; + } + } + /** Simplified copy of J2SESources. */ + private static class TestSources implements Sources, PropertyChangeListener, ChangeListener { + private final AntProjectHelper helper; + private final PropertyEvaluator evaluator; + private final SourceRoots sourceRoots; + private final SourceRoots testRoots; + private SourcesHelper sourcesHelper; + private Sources delegate; + private final ChangeSupport changeSupport = new ChangeSupport(this); + TestSources(AntProjectHelper helper, PropertyEvaluator evaluator, SourceRoots sourceRoots, SourceRoots testRoots) { + this.helper = helper; + this.evaluator = evaluator; + this.evaluator.addPropertyChangeListener(this); + this.sourceRoots = sourceRoots; + this.sourceRoots.addPropertyChangeListener(this); + this.testRoots = testRoots; + this.testRoots.addPropertyChangeListener(this); + initSources(); + } + public SourceGroup[] getSourceGroups(final String type) { + return ProjectManager.mutex().readAccess(new Mutex.Action() { + public SourceGroup[] run() { + Sources _delegate; + synchronized (TestSources.this) { + if (delegate == null) { + delegate = initSources(); + delegate.addChangeListener(TestSources.this); + } + _delegate = delegate; + } + return _delegate.getSourceGroups(type); + } + }); + } + private Sources initSources() { + sourcesHelper = new SourcesHelper(helper, evaluator); + register(sourceRoots); + register(testRoots); + sourcesHelper.addNonSourceRoot("${build.dir}"); + return sourcesHelper.createSources(); + } + private void register(SourceRoots roots) { + String[] propNames = roots.getRootProperties(); + String[] rootNames = roots.getRootNames(); + for (int i = 0; i < propNames.length; i++) { + String prop = propNames[i]; + String displayName = roots.getRootDisplayName(rootNames[i], prop); + String loc = "${" + prop + "}"; + sourcesHelper.addPrincipalSourceRoot(loc, displayName, null, null); + sourcesHelper.addTypedSourceRoot(loc, JavaProjectConstants.SOURCES_TYPE_JAVA, displayName, null, null); + } + } + public void addChangeListener(ChangeListener changeListener) { + changeSupport.addChangeListener(changeListener); + } + public void removeChangeListener(ChangeListener changeListener) { + changeSupport.removeChangeListener(changeListener); + } + private void fireChange() { + synchronized (this) { + if (delegate != null) { + delegate.removeChangeListener(this); + delegate = null; + } + } + changeSupport.fireChange(); + } + public void propertyChange(PropertyChangeEvent evt) { + String propName = evt.getPropertyName(); + if (SourceRoots.PROP_ROOT_PROPERTIES.equals(propName) || "build.dir".equals(propName)) { + this.fireChange(); + } + } + public void stateChanged(ChangeEvent event) { + this.fireChange(); + } + } + +} --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java @@ -584,6 +584,9 @@ if (!ep.containsKey(ProjectProperties.EXCLUDES)) { ep.setProperty(ProjectProperties.EXCLUDES, ""); // NOI18N } + if (!ep.containsKey("build.generated.dir")) { // NOI18N + ep.setProperty("build.generated.dir", "${build.dir}/generated-sources"); // NOI18N + } helper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, ep); try { ProjectManager.getDefault().saveProject(J2SEProject.this); --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProjectGenerator.java +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProjectGenerator.java @@ -297,6 +297,7 @@ ep.setProperty("build.dir", "build"); // NOI18N ep.setComment("build.dir", new String[] {"# " + NbBundle.getMessage(J2SEProjectGenerator.class, "COMMENT_build.dir")}, false); // NOI18N ep.setProperty("build.classes.dir", "${build.dir}/classes"); // NOI18N + ep.setProperty("build.generated.dir", "${build.dir}/generated-sources"); // NOI18N ep.setProperty("build.test.classes.dir", "${build.dir}/test/classes"); // NOI18N ep.setProperty("build.test.results.dir", "${build.dir}/test/results"); // NOI18N ep.setProperty("build.classes.excludes", "**/*.java,**/*.form"); // NOI18N --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl @@ -337,6 +337,10 @@ sourcepath /does/not/exist + + gensrcdir + /does/not/exist + customize true @@ -361,6 +365,11 @@ ${java.io.tmpdir} false + + + + + @@ -775,12 +784,24 @@ - + + + + + + + + + + + :${build.generated.subdirs} + + init,deps-jar,-pre-pre-compile,-pre-compile,web-service-client-compile,-compile-depend have.sources - + @@ -817,6 +838,7 @@ + ${build.generated.dir} @@ -1089,6 +1111,10 @@ **/*.java + + ${build.generated.dir} + +