blob: 5f4a8e3497afd376850f88b981625c70b31ac11a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.apisupport.project;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.Sources;
import static org.netbeans.modules.apisupport.project.Bundle.*;
import org.netbeans.modules.apisupport.project.api.ManifestManager;
import org.netbeans.modules.apisupport.project.api.UIUtil;
import org.netbeans.modules.apisupport.project.api.Util;
import org.netbeans.modules.apisupport.project.queries.AccessibilityQueryImpl;
import org.netbeans.modules.apisupport.project.queries.AnnotationProcessingQueryImpl;
import org.netbeans.modules.apisupport.project.queries.AntArtifactProviderImpl;
import org.netbeans.modules.apisupport.project.queries.BinaryForSourceImpl;
import org.netbeans.modules.apisupport.project.queries.ClassPathProviderImpl;
import org.netbeans.modules.apisupport.project.queries.FileEncodingQueryImpl;
import org.netbeans.modules.apisupport.project.queries.JavadocForBinaryImpl;
import org.netbeans.modules.apisupport.project.queries.ModuleProjectClassPathExtender;
import org.netbeans.modules.apisupport.project.queries.ProjectWhiteListQueryImplementation;
import org.netbeans.modules.apisupport.project.queries.SourceForBinaryImpl;
import org.netbeans.modules.apisupport.project.queries.SourceLevelQueryImpl;
import org.netbeans.modules.apisupport.project.queries.SubprojectProviderImpl;
import org.netbeans.modules.apisupport.project.queries.TemplateAttributesProvider;
import org.netbeans.modules.apisupport.project.queries.UnitTestForSourceQueryImpl;
import org.netbeans.modules.apisupport.project.spi.PlatformJarProvider;
import org.netbeans.modules.apisupport.project.ui.ModuleActions;
import org.netbeans.modules.apisupport.project.ui.ModuleLogicalView;
import org.netbeans.modules.apisupport.project.ui.ModuleOperations;
import org.netbeans.modules.apisupport.project.ui.customizer.CustomizerProviderImpl;
import org.netbeans.modules.apisupport.project.ui.customizer.NbModulePackageModifierImplementation;
import org.netbeans.modules.apisupport.project.ui.customizer.SingleModuleProperties;
import org.netbeans.modules.apisupport.project.ui.customizer.SuiteProperties;
import org.netbeans.modules.apisupport.project.universe.HarnessVersion;
import org.netbeans.modules.apisupport.project.universe.LocalizedBundleInfo;
import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
import org.netbeans.modules.apisupport.project.universe.ModuleList;
import org.netbeans.modules.apisupport.project.universe.NbPlatform;
import org.netbeans.spi.java.project.support.ExtraSourceJavadocSupport;
import org.netbeans.spi.java.project.support.LookupMergerSupport;
import org.netbeans.spi.java.queries.JavadocForBinaryQueryImplementation;
import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.spi.project.support.LookupProviderSupport;
import org.netbeans.spi.project.support.ant.AntBasedProjectRegistration;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.GeneratedFilesHelper;
import org.netbeans.spi.project.support.ant.ProjectXmlSavedHook;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.netbeans.spi.project.support.ant.SourcesHelper;
import org.netbeans.spi.project.ui.PrivilegedTemplates;
import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.netbeans.spi.project.ui.RecommendedTemplates;
import org.netbeans.spi.project.ui.support.UILookupMergerSupport;
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
import org.openide.ErrorManager;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.NbBundle.Messages;
import org.openide.util.Utilities;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Element;
/**
* A NetBeans module project.
* @author Jesse Glick
*/
@AntBasedProjectRegistration(
type=NbModuleProject.TYPE,
iconResource=NbModuleProject.NB_PROJECT_ICON_PATH,
sharedName=NbModuleProject.NAME_SHARED,
sharedNamespace= NbModuleProject.NAMESPACE_SHARED,
privateName="data", // NOI18N
privateNamespace= "http://www.netbeans.org/ns/nb-module-project-private/1" // NOI18N
)
public final class NbModuleProject implements Project {
static final String TYPE = "org.netbeans.modules.apisupport.project"; // NOI18N
static final String NAME_SHARED = "data"; // NOI18N
public static final String NAMESPACE_SHARED = "http://www.netbeans.org/ns/nb-module-project/3"; // NOI18N
public static final String NAMESPACE_SHARED_2 = "http://www.netbeans.org/ns/nb-module-project/2"; // NOI18N
public static final String NB_PROJECT_ICON_PATH =
"org/netbeans/modules/apisupport/project/resources/module.png"; // NOI18N
public static final String NB_PROJECT_OSGI_ICON_PATH =
"org/netbeans/modules/apisupport/project/resources/bundle.png"; // NOI18N
private static final Icon NB_PROJECT_ICON = ImageUtilities.loadImageIcon(NB_PROJECT_ICON_PATH, false);
public static final String SOURCES_TYPE_JAVAHELP = "javahelp"; // NOI18N
static final String[] COMMON_TEST_TYPES = {"unit", "qa-functional"}; // NOI18N
public static final String OPENIDE_MODULE_NAME = "OpenIDE-Module-Name"; // NOI18N
private final AntProjectHelper helper;
private final Evaluator eval;
private final Lookup lookup;
private final InstanceContent ic;
private Map<FileObject,Element> extraCompilationUnits;
private final GeneratedFilesHelper genFilesHelper;
@Messages({
"# {0} - project directory", "NbModuleProject.too_new=This version of the IDE is too old to read metadata in {0}.",
"LBL_source_packages=Source Packages",
"LBL_qa-functional_test_packages=Functional Test Packages",
"LBL_unit_test_packages=Unit Test Packages",
"LBL_javahelp_packages=JavaHelp"
})
public NbModuleProject(AntProjectHelper helper) throws IOException {
AuxiliaryConfiguration aux = helper.createAuxiliaryConfiguration();
for (int v = 4; v < 10; v++) {
if (aux.getConfigurationFragment("data", "http://www.netbeans.org/ns/nb-module-project/" + v, true) != null) { // NOI18N
throw Exceptions.attachLocalizedMessage(new IOException("too new"), // NOI18N
NbModuleProject_too_new(FileUtil.getFileDisplayName(helper.getProjectDirectory())));
}
}
this.helper = helper;
genFilesHelper = new GeneratedFilesHelper(helper);
Util.err.log("Loading project in " + getProjectDirectory());
if (getCodeNameBase() == null) {
throw new IOException("Misconfigured project in " + FileUtil.getFileDisplayName(getProjectDirectory()) + " has no defined <code-name-base>"); // NOI18N
}
if (getModuleType() == NbModuleType.NETBEANS_ORG && ModuleList.findNetBeansOrg(getProjectDirectoryFile()) == null) {
// #69097: preferable to throwing an assertion error later...
throw new IOException("netbeans.org-type module requires at least nbbuild: " + FileUtil.getFileDisplayName(helper.getProjectDirectory())); // NOI18N
}
eval = new Evaluator(this);
// XXX could add globs for other package roots too
List<String> from = new ArrayList<String>();
List<String> to = new ArrayList<String>();
from.add("${src.dir}/*.java"); // NOI18N
to.add("${build.classes.dir}/*.class"); // NOI18N
for (String type : supportedTestTypes(false)) {
from.add("${test." + type + ".src.dir}/*.java"); // NOI18N
to.add("${build.test." + type + ".classes.dir}/*.class"); // NOI18N
}
FileBuiltQueryImplementation fileBuilt = helper.createGlobFileBuiltQuery(
eval,from.toArray(new String[0]), to.toArray(new String[0]));
SourcesHelper sourcesHelper = new SourcesHelper(this, helper, eval);
// Temp build dir is always internal; NBM build products go elsewhere, but
// difficult to predict statically exactly what they are!
// XXX would be good to mark at least the module JAR as owned by this project
// (currently FOQ/SH do not support that)
sourcesHelper.sourceRoot("${src.dir}")// NOI18N
.hint(JavaProjectConstants.SOURCES_HINT_MAIN)
.displayName(LBL_source_packages()).add() // as principal root
.type(JavaProjectConstants.SOURCES_TYPE_JAVA).add(); // as typed root
for (String type : supportedTestTypes(false)) {
sourcesHelper.sourceRoot("${test." + type + ".src.dir}")// NOI18N
.hint(JavaProjectConstants.SOURCES_HINT_TEST)
.displayName(type.equals("qa-functional") ? LBL_qa_functional_test_packages() : LBL_unit_test_packages()).add() // as principal root
.type(JavaProjectConstants.SOURCES_TYPE_JAVA).add(); // as typed root
}
// XXX other principal source roots, as needed...
if (helper.resolveFileObject("javahelp/manifest.mf") == null) { // NOI18N
// Special hack for core - ignore core/javahelp
sourcesHelper.sourceRoot("javahelp").type(SOURCES_TYPE_JAVAHELP)
.displayName(LBL_javahelp_packages()).add();
}
for (Map.Entry<FileObject,Element> entry : getExtraCompilationUnits().entrySet()) {
Element pkgrootEl = XMLUtil.findElement(entry.getValue(), "package-root", NbModuleProject.NAMESPACE_SHARED); // NOI18N
String pkgrootS = XMLUtil.findText(pkgrootEl);
FileObject root = entry.getKey();
// #192773: try to make a unique display name; a schema addition might be better
String displayName = FileUtil.getRelativePath(getProjectDirectory(), root);
if (displayName == null) {
displayName = root.getNameExt();
}
sourcesHelper.sourceRoot(pkgrootS).type(JavaProjectConstants.SOURCES_TYPE_JAVA).displayName(displayName).add();
}
// #56457: support external source roots too.
sourcesHelper.registerExternalRoots(FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT);
ic = new InstanceContent();
lookup = createLookup(new Info(), aux, helper, fileBuilt, sourcesHelper);
}
public @Override String toString() {
return "NbModuleProject[" + getProjectDirectory() + "]"; // NOI18N
}
@Override public Lookup getLookup() {
return lookup;
}
public void refreshLookup() {
if (getModuleType() == NbModuleType.SUITE_COMPONENT) {
if (lookup.lookup(SuiteProvider.class) == null) {
ic.add(new SuiteProviderImpl());
}
} else {
SuiteProvider sp = lookup.lookup(SuiteProvider.class);
if (sp != null) {
ic.remove(sp);
}
}
}
private Lookup createLookup(ProjectInformation info, AuxiliaryConfiguration aux, AntProjectHelper helper, FileBuiltQueryImplementation fileBuilt, final SourcesHelper sourcesHelper) {
Sources srcs = sourcesHelper.createSources();
srcs.addChangeListener(new ChangeListener() {
@Override public void stateChanged(ChangeEvent e) {
// added source root, probably via SourceGroupModifiedImplementation
getLookup().lookup(ModuleActions.class).refresh();
}
});
ExtraSJQEvaluator eJSQEval = new ExtraSJQEvaluator();
ic.add(this);
ic.add(info);
ic.add(aux);
ic.add(helper.createCacheDirectoryProvider());
ic.add(helper.createAuxiliaryProperties());
ic.add(new SavedHook());
ic.add(UILookupMergerSupport.createProjectOpenHookMerger(new OpenedHook()));
ic.add(new ModuleActions(this));
ic.add(new ClassPathProviderImpl(this));
ic.add(new SourceForBinaryImpl(this));
ic.add(new BinaryForSourceImpl(this));
ic.add(LookupMergerSupport.createSFBLookupMerger());
ic.add(createESQI(eJSQEval));
ic.add(new JavadocForBinaryImpl(this));
ic.add(LookupMergerSupport.createJFBLookupMerger());
ic.add(createEJQI(eJSQEval));
ic.add(new UnitTestForSourceQueryImpl(this));
ic.add(new ModuleLogicalView(this));
ic.add(new SubprojectProviderImpl(this));
ic.add(fileBuilt);
ic.add(new AccessibilityQueryImpl(this));
ic.add(new SourceLevelQueryImpl(this));
//ic.add(new ProjectWhiteListQueryImplementation(this));
ic.add(new ProjectWhiteListQueryImplementation(this));
ic.add(new NbModulePackageModifierImplementation(this));
ic.add(helper.createSharabilityQuery2(evaluator(), new String[0], new String[]{
"${build.dir}", // NOI18N
}));
ic.add(srcs);
ic.add(sourcesHelper.createSourceGroupModifierImplementation()); // XXX only for unit tests, will need custom impl for qa-functional
ic.add(new AntArtifactProviderImpl(this, helper, evaluator()));
ic.add(new CustomizerProviderImpl(this, getHelper(), evaluator()));
ic.add(new NbModuleProviderImpl(this));
ic.add(new NbRefactoringProviderImpl(this));
ic.add(new NbProjectProviderImpl(this));
ic.add(new PrivilegedTemplatesImpl());
ic.add(new ModuleProjectClassPathExtender(this));
ic.add(new LocalizedBundleInfoProvider());
ic.add(new ModuleOperations(this));
ic.add(LookupProviderSupport.createSourcesMerger());
ic.add(UILookupMergerSupport.createPrivilegedTemplatesMerger());
ic.add(UILookupMergerSupport.createRecommendedTemplatesMerger());
ic.add(new TemplateAttributesProvider(this, getHelper(), getModuleType() == NbModuleType.NETBEANS_ORG));
ic.add(new FileEncodingQueryImpl());
ic.add(new AnnotationProcessingQueryImpl(this));
ic.add(new PlatformJarProviderImpl());
if (getModuleType() == NbModuleType.SUITE_COMPONENT) {
ic.add(new SuiteProviderImpl());
}
Lookup baseLookup = new AbstractLookup(ic);
return LookupProviderSupport.createCompositeLookup(baseLookup, "Projects/org-netbeans-modules-apisupport-project/Lookup"); //NOI18N
}
@Override public FileObject getProjectDirectory() {
return helper.getProjectDirectory();
}
public File getProjectDirectoryFile() {
return FileUtil.toFile(getProjectDirectory());
}
/**
* Get the minimum harness version required to work with this module.
*/
public HarnessVersion getMinimumHarnessVersion() {
if (helper.createAuxiliaryConfiguration().getConfigurationFragment(NbModuleProject.NAME_SHARED, NbModuleProject.NAMESPACE_SHARED_2, true) != null) {
return HarnessVersion.V50;
} else {
return HarnessVersion.V55u1;
}
}
/**
* Replacement for {@link AntProjectHelper#getPrimaryConfigurationData}
* taking into account the /2 -> /3 upgrade.
*/
public Element getPrimaryConfigurationData() {
return ProjectManager.mutex().readAccess(new Mutex.Action<Element>() {
@Override public Element run() {
AuxiliaryConfiguration ac = helper.createAuxiliaryConfiguration();
Element data = ac.getConfigurationFragment(NbModuleProject.NAME_SHARED, NbModuleProject.NAMESPACE_SHARED_2, true);
if (data != null) {
return XMLUtil.translateXML(data, NbModuleProject.NAMESPACE_SHARED);
} else {
return helper.getPrimaryConfigurationData(true);
}
}
});
}
/**
* Replacement for {@link AntProjectHelper#putPrimaryConfigurationData}
* taking into account the /2 -> /3 upgrade.
*/
public void putPrimaryConfigurationData(final Element data) {
ProjectManager.mutex().writeAccess(new Mutex.Action<Void>() {
@Override public Void run() {
AuxiliaryConfiguration ac = helper.createAuxiliaryConfiguration();
if (ac.getConfigurationFragment(NbModuleProject.NAME_SHARED, NbModuleProject.NAMESPACE_SHARED_2, true) != null) {
ac.putConfigurationFragment(XMLUtil.translateXML(data, NbModuleProject.NAMESPACE_SHARED_2), true);
} else {
helper.putPrimaryConfigurationData(data, true);
}
return null;
}
});
}
/** Returns a relative path to a project's source directory. */
public String getSourceDirectoryPath() {
return evaluator().getProperty("src.dir"); // NOI18N
}
public NbModuleType getModuleType() {
Element data = getPrimaryConfigurationData();
if (XMLUtil.findElement(data, "suite-component", NbModuleProject.NAMESPACE_SHARED) != null) { // NOI18N
return NbModuleType.SUITE_COMPONENT;
} else if (XMLUtil.findElement(data, "standalone", NbModuleProject.NAMESPACE_SHARED) != null) { // NOI18N
return NbModuleType.STANDALONE;
} else {
return NbModuleType.NETBEANS_ORG;
}
}
public @CheckForNull FileObject getManifestFile() {
String v = evaluator().getProperty("manifest.mf");
return v != null ? helper.resolveFileObject(v) : null;
}
public @CheckForNull Manifest getManifest() {
return Util.getManifest(getManifestFile());
}
public AntProjectHelper getHelper() {
return helper;
}
public PropertyEvaluator evaluator() {
return eval;
}
private FileObject getDir(String prop) {
String v = evaluator().getProperty(prop);
if (v == null) {
Logger.getLogger(NbModuleProject.class.getName()).log(Level.WARNING,
"#150612: property {0} was undefined in {1}", new Object[] {prop, this});
return null;
}
return helper.resolveFileObject(v);
}
public FileObject getSourceDirectory() {
return getDir("src.dir"); // NOI18N
}
public FileObject getTestSourceDirectory(String type) {
return getDir("test." + type + ".src.dir"); // NOI18N
}
private File getTestSourceDirectoryFile(String type) {
String dir = evaluator().getProperty("test." + type + ".src.dir"); // NOI18N
return dir != null ? helper.resolveFile(dir) : null;
}
public @NonNull File getClassesDirectory() {
String classesDir = evaluator().getProperty("build.classes.dir"); // NOI18N
return helper.resolveFile(classesDir != null ? classesDir : "unknown");
}
public @NonNull File getTestClassesDirectory(String type) {
String testClassesDir = evaluator().getProperty("build.test." + type + ".classes.dir"); // NOI18N
return helper.resolveFile(testClassesDir != null ? testClassesDir : "unknown");
}
public File getGeneratedClassesDirectory() {
return gensrc(getClassesDirectory());
}
public File getTestGeneratedClassesDirectory(String type) {
return gensrc(getTestClassesDirectory(type));
}
private File gensrc(File clazz) {
return new File(clazz.getParentFile(), clazz.getName() + "-generated"); // NOI18N
}
public FileObject getJavaHelpDirectory() {
if (helper.resolveFileObject("javahelp/manifest.mf") != null) { // NOI18N
// Special hack for core.
return null;
}
return helper.resolveFileObject("javahelp"); // NOI18N
}
public @NonNull File getModuleJarLocation() {
String v = evaluator().evaluate("${cluster}/${module.jar}");
// XXX could use ModuleList here instead
return helper.resolveFile(v != null ? v : "unknown");
}
public @NonNull File getTestUserDirLockFile() {
String v = evaluator().evaluate("${test.user.dir}/lock");
return getHelper().resolveFile(v != null ? v : "unknown");
}
public String getCodeNameBase() {
Element config = getPrimaryConfigurationData();
Element cnb = XMLUtil.findElement(config, "code-name-base", NbModuleProject.NAMESPACE_SHARED); // NOI18N
if (cnb != null) {
return XMLUtil.findText(cnb);
} else {
return null;
}
}
private String getModuleName() {
Manifest m = getManifest();
if (m != null) {
String moduleName = m.getMainAttributes().getValue(OPENIDE_MODULE_NAME);
if (moduleName != null) {
return moduleName;
}
}
return null;
}
public @CheckForNull String getSpecVersion() {
//TODO shall we check for illegal cases like "none-defined" or "both-defined" here?
Manifest m = getManifest();
if (m != null) {
String manVersion = ManifestManager.getInstance(m, false).getSpecificationVersion();
if (manVersion != null) {
return manVersion;
}
}
String svb = evaluator().getProperty(SingleModuleProperties.SPEC_VERSION_BASE);
if (svb != null) {
return svb/* #72826 */.replaceAll("(\\.[0-9]+)\\.0$", "$1"); // NOI18N
}
return null;
}
/**
* Slash-separated path inside netbeans.org sources, or null for external modules.
*/
public String getPathWithinNetBeansOrg() {
FileObject nbroot = getNbrootFileObject(null);
if (nbroot != null) {
return FileUtil.getRelativePath(nbroot, getProjectDirectory());
} else {
return null;
}
}
private File getNbroot() {
File dir = getProjectDirectoryFile();
File nbroot = ModuleList.findNetBeansOrg(dir);
if (nbroot != null) {
return nbroot;
} else {
// OK, not it.
NbPlatform platform = getPlatform();
if (platform != null) {
URL[] roots = platform.getSourceRoots();
for (int i = 0; i < roots.length; i++) {
if (roots[i].getProtocol().equals("file")) { // NOI18N
File f = Utilities.toFile(URI.create(roots[i].toExternalForm()));
if (ModuleList.isNetBeansOrg(f)) {
return f;
}
}
}
}
// Did not find it.
return null;
}
}
public File getNbrootFile(String path) {
File nbroot = getNbroot();
if (nbroot != null) {
return new File(nbroot, path.replace('/', File.separatorChar));
} else {
return null;
}
}
public FileObject getNbrootFileObject(String path) {
File f = path != null ? getNbrootFile(path) : getNbroot();
if (f != null) {
return FileUtil.toFileObject(f);
} else {
return null;
}
}
public ModuleList getModuleList() throws IOException {
NbPlatform p = getPlatform(false);
if (p == null || ! p.isValid()) {
// #67148: have to use something... (and getEntry(codeNameBase) will certainly fail!)
if (getModuleType() == NbModuleType.NETBEANS_ORG) {
// #174689: NB.org modules scan sources, not binary platform
return ModuleList.getModuleList(getProjectDirectoryFile(), null);
}
// TODO dealing with nonexistent platforms probably not complete / 100% correct yet,
// see #61227; but project with unresolved platform may also load as result
// of suite-chaining; perhaps resolve already in loadProject
Util.err.log(ErrorManager.WARNING, "Project in " + FileUtil.getFileDisplayName(getProjectDirectory()) // NOI18N
+ " is missing its platform '" + evaluator().getProperty("nbplatform.active") + "', switching to default platform"); // NOI18N
NbPlatform p2 = NbPlatform.getDefaultPlatform();
return ModuleList.getModuleList(getProjectDirectoryFile(), p2 != null ? p2.getDestDir() : null);
}
ModuleList ml;
try {
ml = ModuleList.getModuleList(getProjectDirectoryFile(), p.getDestDir());
} catch (IOException x) {
// #69029: maybe invalidated platform? Try the default platform instead.
Logger.getLogger(NbModuleProject.class.getName()).log(Level.FINE, null, x);
NbPlatform p2 = NbPlatform.getDefaultPlatform();
return ModuleList.getModuleList(getProjectDirectoryFile(), p2 != null ? p2.getDestDir() : null);
}
if (ml.getEntry(getCodeNameBase()) == null) {
ModuleList.refresh();
ml = ModuleList.getModuleList(getProjectDirectoryFile());
if (ml.getEntry(getCodeNameBase()) == null) {
// XXX try to give better diagnostics - as examples are discovered
Util.err.log(ErrorManager.WARNING, "Project in " + FileUtil.getFileDisplayName(getProjectDirectory()) + " does not appear to be listed in its own module list; some sort of misconfiguration (e.g. not listed in its own suite)"); // NOI18N
}
}
return ml;
}
/**
* Get the platform which this project is currently associated with.
* @param fallback if true, fall back to the default platform if necessary
* @return the current platform; or null if fallback is false and there is no
* platform specified, or an invalid platform is specified, or even if
* fallback is true but even the default platform is not available
*/
public NbPlatform getPlatform(boolean fallback) {
NbPlatform p = getPlatform();
if (fallback && (p == null || !p.isValid())) {
p = NbPlatform.getDefaultPlatform();
}
return p;
}
private NbPlatform getPlatform() {
File file = getPlatformFile();
if (file == null) {
return null;
}
String harnessDir = evaluator().getProperty("harness.dir");
return NbPlatform.getPlatformByDestDir(file, harnessDir != null ? getHelper().resolveFile(harnessDir) : null);
}
File getPlatformFile() {
String prop = evaluator().getProperty(ModuleList.NETBEANS_DEST_DIR);
if (prop == null) {
return null;
}
return getHelper().resolveFile(prop);
}
/**
* Check whether Javadoc generation is possible.
*/
public boolean supportsJavadoc() {
if (evaluator().getProperty("module.javadoc.packages") != null) {
return true;
}
Element config = getPrimaryConfigurationData();
Element pubPkgs = XMLUtil.findElement(config, "public-packages", NbModuleProject.NAMESPACE_SHARED); // NOI18N
if (pubPkgs == null) {
// Try <friend-packages> too.
pubPkgs = XMLUtil.findElement(config, "friend-packages", NbModuleProject.NAMESPACE_SHARED); // NOI18N
}
return pubPkgs != null && !XMLUtil.findSubElements(pubPkgs).isEmpty();
}
public List<String> supportedTestTypes() {
return supportedTestTypes(true);
}
public List<String> supportedTestTypes(boolean mustExist) {
List<String> types = new ArrayList<String>();
for (String type : COMMON_TEST_TYPES) {
if (((mustExist && getTestSourceDirectory(type) != null)
|| (! mustExist && getTestSourceDirectoryFile(type) != null))
&& !Boolean.parseBoolean(evaluator().getProperty("disable." + type + ".tests"))) {
types.add(type);
}
}
// XXX could look for others in project.xml, in which case fix Evaluator to use that
return types;
}
/**
* Find marked extra compilation units.
* Gives a map from the package root to the defining XML element.
*/
public Map<FileObject,Element> getExtraCompilationUnits() {
if (extraCompilationUnits == null) {
extraCompilationUnits = new HashMap<FileObject,Element>();
for (Element ecu : XMLUtil.findSubElements(getPrimaryConfigurationData())) {
if (ecu.getLocalName().equals("extra-compilation-unit")) { // NOI18N
Element pkgrootEl = XMLUtil.findElement(ecu, "package-root", NbModuleProject.NAMESPACE_SHARED); // NOI18N
String pkgrootS = XMLUtil.findText(pkgrootEl);
String pkgrootEval = evaluator().evaluate(pkgrootS);
FileObject pkgroot = pkgrootEval != null ? getHelper().resolveFileObject(pkgrootEval) : null;
if (pkgroot == null) {
Util.err.log(ErrorManager.WARNING, "Could not find package-root " + pkgrootEval + " for " + getCodeNameBase());
continue;
}
extraCompilationUnits.put(pkgroot, ecu);
}
}
}
return extraCompilationUnits;
}
/** Get the Java source level used for this module. Default is 1.4. */
public String getJavacSource() {
String javacSource = evaluator().getProperty(SingleModuleProperties.JAVAC_SOURCE);
assert javacSource != null;
return javacSource;
}
private ClassPath[] boot, source, compile;
private final class OpenedHook extends ProjectOpenedHook {
OpenedHook() {}
@Override protected void projectOpened() {
open();
}
@Override protected void projectClosed() {
try {
ProjectManager.getDefault().saveProject(NbModuleProject.this);
} catch (IOException e) {
Util.err.notify(e);
}
// XXX could discard caches, etc.
// unregister project's classpaths to GlobalClassPathRegistry
if (boot != null) {
assert source != null && compile != null : "#46802 / #201230";
GlobalPathRegistry.getDefault().unregister(ClassPath.BOOT, boot);
GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, source);
GlobalPathRegistry.getDefault().unregister(ClassPath.COMPILE, compile);
boot = null;
source = null;
compile = null;
}
}
}
/**
* Run the open hook.
* For use from unit tests.
*/
public void open() {
// write user.properties.file=$userdir/build.properties to platform-private.properties
if (getModuleType() == NbModuleType.STANDALONE) {
// XXX skip this in case nbplatform.active is not defined
final Project p = this; // XXX workaround for NB editor bug
ProjectManager.mutex().writeAccess(new Mutex.Action<Void>() {
@Override public Void run() {
String path = "nbproject/private/platform-private.properties"; // NOI18N
EditableProperties ep = getHelper().getProperties(path);
File buildProperties = new File(System.getProperty("netbeans.user"), "build.properties"); // NOI18N
ep.setProperty("user.properties.file", buildProperties.getAbsolutePath()); //NOI18N
getHelper().putProperties(path, ep);
try {
ProjectManager.getDefault().saveProject(p);
} catch (IOException e) {
ErrorManager.getDefault().notify(e);
}
return null;
}
});
}
// register project's classpaths to GlobalClassPathRegistry
ClassPathProviderImpl cpProvider = lookup.lookup(ClassPathProviderImpl.class);
ClassPath[] _boot = cpProvider.getProjectClassPaths(ClassPath.BOOT);
assert _boot != null : "No BOOT path";
ClassPath[] _source = cpProvider.getProjectClassPaths(ClassPath.SOURCE);
assert _source != null : "No SOURCE path";
ClassPath[] _compile = cpProvider.getProjectClassPaths(ClassPath.COMPILE);
assert _compile != null : "No COMPILE path";
// Possible cause of #68414: do not change instance vars until after the dangerous stuff has been computed.
GlobalPathRegistry.getDefault().register(ClassPath.BOOT, _boot);
GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, _source);
GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, _compile);
boot = _boot;
source = _source;
compile = _compile;
// refresh build.xml and build-impl.xml for external modules
if (getModuleType() != NbModuleType.NETBEANS_ORG) {
try {
refreshBuildScripts(true);
} catch (IOException e) {
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
}
}
}
/**
* <strong>For use from unit tests only.</strong> Returns {@link
* LocalizedBundleInfo} for this project.
*/
public @CheckForNull LocalizedBundleInfo getBundleInfo() {
return getLookup().lookup(LocalizedBundleInfo.Provider.class).getLocalizedBundleInfo();
}
private int atomicActionCounter = 0;
/** See issue #69440 for more details. */
public synchronized void setRunInAtomicAction(boolean runInAtomicAction) {
if (atomicActionCounter == 0 && ! runInAtomicAction) {
throw new IllegalArgumentException("Not in atomic action");
} // NOI18N
atomicActionCounter += runInAtomicAction ? 1 : -1;
if (runInAtomicAction && atomicActionCounter == 1) {
eval.setRunInAtomicAction(true);
} else if (! runInAtomicAction && atomicActionCounter == 0) {
eval.setRunInAtomicAction(false);
}
}
/** See issue #69440 for more details. */
public synchronized boolean isRunInAtomicAction() {
return eval.isRunInAtomicAction();
}
private final class Info implements ProjectInformation, PropertyChangeListener {
private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
private final String name;
private final String openideModuleName;
private String displayName;
Info() {
String cnb = getCodeNameBase();
name = cnb != null ? cnb : /* #70490 */getProjectDirectory().toString();
String omn = getModuleName();
openideModuleName = omn;
}
@Override public String getName() {
return name;
}
private String getOpenideModuleName() {
return openideModuleName;
}
@Override public String getDisplayName() {
if (displayName == null) {
LocalizedBundleInfo bundleInfo = getBundleInfo();
if (bundleInfo != null) {
displayName = bundleInfo.getDisplayName();
}
}
if (displayName == null) {
displayName = getOpenideModuleName();
}
if (/* #70490 */displayName == null) {
displayName = getName();
}
assert displayName != null : NbModuleProject.this;
return displayName;
}
private void setDisplayName(String newDisplayName) {
String oldDisplayName = getDisplayName();
displayName = newDisplayName == null ? getName() : newDisplayName;
firePropertyChange(ProjectInformation.PROP_DISPLAY_NAME, oldDisplayName, displayName);
}
@Override public Icon getIcon() {
return NB_PROJECT_ICON;
}
@Override public Project getProject() {
return NbModuleProject.this;
}
@Override public void addPropertyChangeListener(PropertyChangeListener pchl) {
changeSupport.addPropertyChangeListener(pchl);
}
@Override public void removePropertyChangeListener(PropertyChangeListener pchl) {
changeSupport.removePropertyChangeListener(pchl);
}
private void firePropertyChange(String propName, Object oldValue, Object newValue) {
changeSupport.firePropertyChange(propName, oldValue, newValue);
}
@Override public void propertyChange(PropertyChangeEvent evt) {
if (ProjectInformation.PROP_DISPLAY_NAME.equals(evt.getPropertyName())) {
setDisplayName((String) evt.getNewValue());
}
}
}
public void notifyDeleting() {
eval.removeListeners();
}
private final class SavedHook extends ProjectXmlSavedHook {
SavedHook() {}
@Override protected void projectXmlSaved() throws IOException {
// refresh build.xml and build-impl.xml for external modules
if (getModuleType() != NbModuleType.NETBEANS_ORG) {
refreshBuildScripts(false);
}
}
}
public void refreshBuildScripts(boolean checkForProjectXmlModified) throws IOException {
refreshBuildScripts(checkForProjectXmlModified, getPlatform(true));
}
public void refreshBuildScripts(boolean checkForProjectXmlModified, NbPlatform customPlatform) throws IOException {
if (customPlatform == null) { // #181798
return;
}
String buildImplPath =
customPlatform.getHarnessVersion().compareTo(HarnessVersion.V65) <= 0
|| eval.getProperty(SuiteProperties.CLUSTER_PATH_PROPERTY) == null
? "build-impl-65.xsl" : "build-impl.xsl"; // NOI18N
genFilesHelper.refreshBuildScript(
GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
NbModuleProject.class.getResource("resources/" + buildImplPath), // NOI18N
checkForProjectXmlModified);
genFilesHelper.refreshBuildScript(
GeneratedFilesHelper.BUILD_XML_PATH,
NbModuleProject.class.getResource("resources/build.xsl"), // NOI18N
checkForProjectXmlModified);
}
private final class SuiteProviderImpl implements SuiteProvider {
@Override public File getSuiteDirectory() {
String suiteDir = evaluator().getProperty("suite.dir"); // NOI18N
return suiteDir == null ? null : helper.resolveFile(suiteDir);
}
@Override public File getClusterDirectory() {
return getModuleJarLocation().getParentFile().getParentFile().getAbsoluteFile();
}
}
private static final class PrivilegedTemplatesImpl implements PrivilegedTemplates, RecommendedTemplates {
private static final String[] PRIVILEGED_NAMES = {
"Templates/Classes/Class.java", // NOI18N
"Templates/Classes/Package", // NOI18N
"Templates/Classes/Interface.java", // NOI18N
//"Templates/GUIForms/JPanel.java", // NOI18N
"Templates/JUnit/SimpleJUnitTest.java", // NOI18N
"Templates/" + UIUtil.TEMPLATE_FOLDER + "/" + UIUtil.TEMPLATE_ACTION_ID,
"Templates/" + UIUtil.TEMPLATE_FOLDER + "/newHTML",
"Templates/" + UIUtil.TEMPLATE_FOLDER + "/" + UIUtil.TEMPLATE_WINDOW_ID,
//"Templates/Other/properties.properties", // NOI18N
};
static {
assert PRIVILEGED_NAMES.length <= 10 : "Too many privileged templates to fit! extras will be ignored: " +
Arrays.asList(PRIVILEGED_NAMES).subList(10, PRIVILEGED_NAMES.length);
}
private static final String[] RECOMMENDED_TYPES = {
"java-classes", // NOI18N
"java-main-class", // NOI18N
"java-forms", // NOI18N
"java-beans", // NOI18N
"oasis-XML-catalogs", // NOI18N
"REST-clients", // NOI18N
"XML", // NOI18N
"ant-script", // NOI18N
"ant-task", // NOI18N
"junit", // NOI18N
"simple-files", // NOI18N
UIUtil.TEMPLATE_CATEGORY,
};
@Override public String[] getPrivilegedTemplates() {
return PRIVILEGED_NAMES;
}
@Override public String[] getRecommendedTypes() {
return RECOMMENDED_TYPES;
}
}
private final class LocalizedBundleInfoProvider extends FileChangeAdapter implements LocalizedBundleInfo.Provider {
private LocalizedBundleInfo bundleInfo;
private FileObject manifestFO;
private final FileChangeListener listener = FileUtil.weakFileChangeListener(this, null);
@Override public LocalizedBundleInfo getLocalizedBundleInfo() {
if (bundleInfo == null) {
Manifest mf = getManifest();
FileObject srcFO = getSourceDirectory();
if (mf != null && srcFO != null) {
bundleInfo = ApisupportAntUtils.findLocalizedBundleInfo(srcFO, mf);
}
if (bundleInfo != null) {
bundleInfo.addPropertyChangeListener(getLookup().lookup(Info.class));
}
if (mf != null) {
manifestFO = getManifestFile();
if (manifestFO != null) {
manifestFO.addFileChangeListener(listener);
}
}
}
return bundleInfo;
}
@Override public void fileChanged(FileEvent fe) {
// cannot reload manifest-dependent things immediately (see 67961 for more details)
bundleInfo = null;
}
@Override public void fileDeleted(FileEvent fe) {
manifestFO = null;
}
}
private SourceForBinaryQueryImplementation createESQI(ExtraSJQEvaluator eJSQEval) {
SourceForBinaryQueryImplementation sfbqi = ExtraSourceJavadocSupport.createExtraSourceQueryImplementation(this, getHelper(), eJSQEval);
registerListener(sfbqi, eJSQEval);
return sfbqi;
}
private JavadocForBinaryQueryImplementation createEJQI(ExtraSJQEvaluator eJSQEval) {
JavadocForBinaryQueryImplementation jfbqi = ExtraSourceJavadocSupport.createExtraJavadocQueryImplementation(this, getHelper(), eJSQEval);
registerListener(jfbqi, eJSQEval);
return jfbqi;
}
private void registerListener(Object qimpl, ExtraSJQEvaluator eJSQEval) {
try {
// XXX #66275: ugly hack until proper API change;
// we need ESJQI to work when project is loaded - not opened - to resolve deps on lib wrapper modules correctly
Field listenerF = qimpl.getClass().getDeclaredField("listener"); // NOI18N
listenerF.setAccessible(true);
eJSQEval.addPropertyChangeListener((PropertyChangeListener) listenerF.get(qimpl));
} catch (Exception ex) {
Logger.getLogger(NbModuleProject.class.getName()).log(Level.FINE, "Turning off source query support for loaded project '" + ProjectUtils.getInformation(this).getDisplayName() + "'", ex);
}
}
/** Matches ExtraProjectSourceForBinaryQueryImpl*/
public static final String SOURCE_START = "source.reference."; //NOI18N
/**
* <tt>&lt;class-path-extension&gt;</tt> to <tt>file/source/javadoc.reference....</tt> properties adapter
* for {@link ExtraSourceJavadocSupport} query implementations.
*/
private final class ExtraSJQEvaluator implements PropertyEvaluator {
private static final String REF_START = "file.reference."; //NOI18N
private static final String JAVADOC_START = "javadoc.reference."; //NOI18N
ExtraSJQEvaluator() {
evaluator().addPropertyChangeListener(new PropertyChangeListener() {
@Override public void propertyChange(PropertyChangeEvent evt) {
final String prop = evt.getPropertyName();
if (prop == null || prop.startsWith(SOURCE_START) || prop.startsWith(JAVADOC_START)) {
cache.set(null);
pcs.firePropertyChange(evt);
}
}
});
}
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private final AtomicReference<Map<String, String>> cache = new AtomicReference<Map<String, String>>();
@Override public String getProperty(String prop) {
return getProperties().get(prop);
}
@Override public String evaluate(String text) {
throw new UnsupportedOperationException(); // currently not needed
}
@Override @NonNull public Map<String, String> getProperties() {
Map<String,String> ce = cache.get();
if (ce == null) {
ce = new HashMap<String, String>();
ProjectXMLManager pxm = new ProjectXMLManager(NbModuleProject.this);
String[] cpExts = pxm.getBinaryOrigins();
for (String cpe : cpExts) {
addFileRef(ce, cpe);
}
Map<String, String> prjProps = evaluator().getProperties();
if (prjProps != null) {
for (Map.Entry<String, String> entry : prjProps.entrySet()) {
if (entry.getKey().startsWith(SOURCE_START) || entry.getKey().startsWith(JAVADOC_START)) {
ce.put(entry.getKey(), entry.getValue());
}
}
}
if (!cache.compareAndSet(null, ce)) {
Map<String,String> tmp = cache.get();
if (tmp != null) {
ce = tmp;
}
}
}
return ce;
}
private void addFileRef(Map<String, String> props, String path) {
// #66275:
// XXX parts of code copied from o.n.spi.project.ant.ReferenceHelper;
// will do proper API change later with issue #70894, will also simplify impl of isssue #66188
final File normalizedFile = FileUtil.normalizeFile(PropertyUtils.resolveFile(getProjectDirectoryFile(), path));
String fileID = normalizedFile.getName();
// if the file is folder then add to ID string also parent folder name,
// i.e. if external source folder name is "src" the ID will
// be a bit more selfdescribing, e.g. project-src in case
// of ID for ant/project/src directory.
if (normalizedFile.isDirectory() && normalizedFile.getParentFile() != null) {
fileID = normalizedFile.getParentFile().getName()+"-"+normalizedFile.getName();
}
fileID = PropertyUtils.getUsablePropertyName(fileID);
// we don't need to resolve duplicate file names here, all <c-p-e>-s reside in release/modules/ext
props.put(REF_START + fileID, path);
}
@Override public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
@Override public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
}
private class PlatformJarProviderImpl implements PlatformJarProvider {
@Override public Set<File> getPlatformJars() throws IOException {
NbPlatform platform = getPlatform(true);
if (platform == null) {
return Collections.emptySet();
}
Set<ModuleEntry> entries = platform.getModules();
Set<File> jars = new HashSet<File>(entries.size());
for (ModuleEntry entry : entries) {
jars.add(entry.getJarLocation());
}
return jars;
}
}
}