blob: 9043788e131f1b99573614d7b5ab827f16ded784 [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.php.project;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
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.SourceGroup;
import org.netbeans.api.queries.VisibilityQuery;
import org.netbeans.api.search.SearchRoot;
import org.netbeans.api.search.SearchScopeOptions;
import org.netbeans.api.search.provider.SearchInfo;
import org.netbeans.api.search.provider.SearchInfoUtils;
import org.netbeans.api.search.provider.SearchListener;
import org.netbeans.modules.php.api.documentation.PhpDocumentations;
import org.netbeans.modules.php.api.phpmodule.PhpModule;
import org.netbeans.modules.php.api.util.FileUtils;
import org.netbeans.modules.php.project.annotations.ProjectUserAnnotationsProvider;
import org.netbeans.modules.php.project.api.PhpSeleniumProvider;
import org.netbeans.modules.php.project.api.PhpSourcePath;
import org.netbeans.modules.php.project.classpath.BasePathSupport;
import org.netbeans.modules.php.project.classpath.ClassPathProviderImpl;
import org.netbeans.modules.php.project.classpath.IncludePathClassPathProvider;
import org.netbeans.modules.php.project.copysupport.CopySupport;
import org.netbeans.modules.php.project.internalserver.InternalWebServer;
import org.netbeans.modules.php.project.problems.ProjectPropertiesProblemProvider;
import org.netbeans.modules.php.project.ui.actions.support.CommandUtils;
import org.netbeans.modules.php.project.ui.actions.support.ConfigAction;
import org.netbeans.modules.php.project.ui.actions.support.DebugStarterFactory;
import org.netbeans.modules.php.project.ui.codecoverage.PhpCoverageProvider;
import org.netbeans.modules.php.project.ui.customizer.CompositePanelProviderImpl;
import org.netbeans.modules.php.project.ui.customizer.CustomizerProviderImpl;
import org.netbeans.modules.php.project.ui.customizer.IgnorePathSupport;
import org.netbeans.modules.php.project.ui.customizer.PhpProjectProperties;
import org.netbeans.modules.php.project.ui.logicalview.PhpLogicalViewProvider;
import org.netbeans.modules.php.project.ui.options.PhpOptions;
import org.netbeans.modules.php.project.util.PhpProjectUtils;
import org.netbeans.modules.php.project.util.UsageLogging;
import org.netbeans.modules.php.spi.executable.DebugStarter;
import org.netbeans.modules.php.spi.framework.PhpFrameworkProvider;
import org.netbeans.modules.php.spi.framework.PhpModuleIgnoredFilesExtender;
import org.netbeans.modules.php.spi.testing.PhpTestingProvider;
import org.netbeans.modules.php.spi.testing.PhpTestingProviders;
import org.netbeans.modules.web.browser.api.BrowserSupport;
import org.netbeans.modules.web.browser.api.WebBrowser;
import org.netbeans.modules.web.browser.api.BrowserUISupport;
import org.netbeans.modules.web.browser.spi.PageInspectorCustomizer;
import org.netbeans.modules.web.clientproject.api.jstesting.JsTestingProvider;
import org.netbeans.modules.web.clientproject.api.jstesting.JsTestingProviders;
import org.netbeans.modules.web.common.api.CssPreprocessor;
import org.netbeans.modules.web.common.api.CssPreprocessors;
import org.netbeans.modules.web.common.api.CssPreprocessorsListener;
import org.netbeans.modules.web.common.api.UsageLogger;
import org.netbeans.modules.web.common.api.WebUtils;
import org.netbeans.modules.web.common.spi.ProjectWebRootProvider;
import org.netbeans.modules.web.common.spi.ServerURLMappingImplementation;
import org.netbeans.modules.web.common.ui.api.CssPreprocessorsUI;
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.AntProjectEvent;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.AntProjectListener;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.FilterPropertyProvider;
import org.netbeans.spi.project.support.ant.ProjectXmlSavedHook;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.support.ant.PropertyProvider;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.netbeans.spi.project.support.ant.ReferenceHelper;
import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.netbeans.spi.project.ui.support.UILookupMergerSupport;
import org.netbeans.spi.search.SearchFilterDefinition;
import org.netbeans.spi.search.SearchInfoDefinition;
import org.netbeans.spi.search.SearchInfoDefinitionFactory;
import org.netbeans.spi.search.SubTreeSearchOptions;
import org.openide.awt.HtmlBrowser;
import org.openide.awt.NotificationDisplayer;
import org.openide.filesystems.FileAttributeEvent;
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.ChangeSupport;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.WeakSet;
import org.openide.util.lookup.Lookups;
import org.openide.windows.WindowManager;
import org.openide.windows.WindowSystemEvent;
import org.openide.windows.WindowSystemListener;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* @author ads, Tomas Mysik
*/
@AntBasedProjectRegistration(
type=PhpProjectType.TYPE,
iconResource=PhpProject.PROJECT_ICON,
sharedNamespace=PhpProjectType.PROJECT_CONFIGURATION_NAMESPACE,
privateNamespace=PhpProjectType.PRIVATE_CONFIGURATION_NAMESPACE
)
public final class PhpProject implements Project {
static final Logger LOGGER = Logger.getLogger(PhpProject.class.getName());
@StaticResource
public static final String PROJECT_ICON = "org/netbeans/modules/php/project/ui/resources/phpProject.png"; // NOI18N
final AntProjectHelper helper;
final UpdateHelper updateHelper;
private final ReferenceHelper refHelper;
private final PropertyEvaluator eval;
private final Lookup lookup;
private final SourceRoots sourceRoots;
private final SourceRoots testRoots;
private final SourceRoots seleniumRoots;
private final SearchFilterDefinition searchFilterDef = new PhpSearchFilterDef();
// ok to read it more times
volatile FileObject webRootDirectory;
volatile String name;
private final AntProjectListener phpAntProjectListener = new PhpAntProjectListener();
private final PropertyChangeListener projectPropertiesListener = new ProjectPropertiesListener();
// @GuardedBy("ProjectManager.mutex() & ignoredFoldersLock") #211924
Set<BasePathSupport.Item> ignoredFolders;
final Object ignoredFoldersLock = new Object();
// changes in ignored files - special case because of PhpVisibilityQuery
final ChangeSupport ignoredFoldersChangeSupport = new ChangeSupport(this);
final Frameworks frameworks;
final TestingProviders testingProviders;
private final ChangeListener frameworksListener;
// FS changes
private final SourceDirectoryFileChangeListener sourceDirectoryFileChangeListener = new SourceDirectoryFileChangeListener();
// project's property changes
public static final String PROP_FRAMEWORKS = "frameworks"; // NOI18N
public static final String PROP_WEB_ROOT = "webRoot"; // NOI18N
final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private final Set<PropertyChangeListener> propertyChangeListeners = new WeakSet<>();
// css preprocessors
final CssPreprocessorsListener cssPreprocessorsListener = new CssPreprocessorsListener() {
@Override
public void preprocessorsChanged() {
// noop?
}
@Override
public void optionsChanged(CssPreprocessor cssPreprocessor) {
recompileSources(cssPreprocessor);
}
@Override
public void customizerChanged(Project project, CssPreprocessor cssPreprocessor) {
if (project.equals(PhpProject.this)) {
recompileSources(cssPreprocessor);
}
}
@Override
public void processingErrorOccured(Project project, CssPreprocessor cssPreprocessor, String error) {
// noop
}
};
// #233052
private final WindowSystemListener windowSystemListener = new WindowSystemListener() {
@Override
public void beforeLoad(WindowSystemEvent event) {
}
@Override
public void afterLoad(WindowSystemEvent event) {
}
@Override
public void beforeSave(WindowSystemEvent event) {
// browser
getLookup().lookup(ClientSideDevelopmentSupport.class).close();
}
@Override
public void afterSave(WindowSystemEvent event) {
}
};
@NbBundle.Messages({
"PhpProject.sourceRoots.sources=Source Files",
"PhpProject.sourceRoots.tests=Test Files",
"PhpProject.sourceRoots.selenium=Selenium Test Files",
})
public PhpProject(AntProjectHelper helper) {
assert helper != null;
this.helper = helper;
updateHelper = new UpdateHelper(UpdateImplementation.NULL, helper);
AuxiliaryConfiguration configuration = helper.createAuxiliaryConfiguration();
eval = createEvaluator();
refHelper = new ReferenceHelper(helper, configuration, getEvaluator());
sourceRoots = SourceRoots.Builder.create(updateHelper, eval, Bundle.PhpProject_sourceRoots_sources())
.setProperties(PhpProjectProperties.SRC_DIR)
.build();
testRoots = SourceRoots.Builder.create(updateHelper, eval, Bundle.PhpProject_sourceRoots_tests())
.setPropertyNumericPrefix(PhpProjectProperties.TEST_SRC_DIR)
.setTests(true)
.build();
seleniumRoots = SourceRoots.Builder.create(updateHelper, eval, Bundle.PhpProject_sourceRoots_selenium())
.setPropertyNumericPrefix(PhpProjectProperties.SELENIUM_SRC_DIR)
.setTests(true)
.build();
PhpModuleImpl phpModule = new PhpModuleImpl(this);
frameworks = new Frameworks(phpModule);
testingProviders = TestingProviders.create(this);
// lookup
lookup = createLookup(configuration, phpModule);
// listeners
addWeakPropertyEvaluatorListener(projectPropertiesListener);
helper.addAntProjectListener(WeakListeners.create(AntProjectListener.class, phpAntProjectListener, helper));
sourceRoots.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
removeSourceDirListener();
addSourceDirListener();
resetFrameworks();
}
});
frameworksListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
fireFrameworksChange();
}
};
frameworks.addChangeListener(WeakListeners.change(frameworksListener, frameworks));
WindowManager windowManager = WindowManager.getDefault();
windowManager.addWindowSystemListener(WeakListeners.create(WindowSystemListener.class, windowSystemListener, windowManager));
}
@Override
public Lookup getLookup() {
return lookup;
}
PropertyEvaluator getEvaluator() {
return eval;
}
void addWeakPropertyEvaluatorListener(PropertyChangeListener listener) {
eval.addPropertyChangeListener(WeakListeners.propertyChange(listener, eval));
}
void addWeakIgnoredFilesListener(ChangeListener listener) {
ignoredFoldersChangeSupport.addChangeListener(WeakListeners.change(listener, ignoredFoldersChangeSupport));
VisibilityQuery visibilityQuery = VisibilityQuery.getDefault();
visibilityQuery.addChangeListener(WeakListeners.change(listener, visibilityQuery));
}
// add as a weak listener, only once
boolean addWeakPropertyChangeListener(PropertyChangeListener listener) {
if (!propertyChangeListeners.add(listener)) {
// already added
return false;
}
addPropertyChangeListener(WeakListeners.propertyChange(listener, propertyChangeSupport));
return true;
}
void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
public SearchFilterDefinition getSearchFilterDefinition() {
return searchFilterDef;
}
private PropertyEvaluator createEvaluator() {
// It is currently safe to not use the UpdateHelper for PropertyEvaluator; UH.getProperties() delegates to APH
// Adapted from APH.getStandardPropertyEvaluator (delegates to ProjectProperties):
PropertyEvaluator baseEval1 = PropertyUtils.sequentialPropertyEvaluator(
helper.getStockPropertyPreprovider(),
helper.getPropertyProvider(PhpConfigurationProvider.CONFIG_PROPS_PATH));
PropertyEvaluator baseEval2 = PropertyUtils.sequentialPropertyEvaluator(
helper.getStockPropertyPreprovider(),
helper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH));
return PropertyUtils.sequentialPropertyEvaluator(
helper.getStockPropertyPreprovider(),
helper.getPropertyProvider(PhpConfigurationProvider.CONFIG_PROPS_PATH),
new ConfigPropertyProvider(baseEval1, "nbproject/private/configs", helper), // NOI18N
helper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH),
helper.getProjectLibrariesPropertyProvider(),
PropertyUtils.userPropertiesProvider(baseEval2,
"user.properties.file", FileUtil.toFile(getProjectDirectory())), // NOI18N
new ConfigPropertyProvider(baseEval1, "nbproject/configs", helper), // NOI18N
helper.getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH));
}
@Override
public FileObject getProjectDirectory() {
return getHelper().getProjectDirectory();
}
public SourceRoots getSourceRoots() {
return sourceRoots;
}
public SourceRoots getTestRoots() {
return testRoots;
}
public SourceRoots getSeleniumRoots() {
return seleniumRoots;
}
@CheckForNull
FileObject getSourcesDirectory() {
for (FileObject root : sourceRoots.getRoots()) {
// return the first one
return root;
}
return null;
}
/**
* @return tests directory or <code>null</code>
*/
FileObject[] getTestsDirectories() {
return testRoots.getRoots();
}
/**
* @return selenium tests directory or <code>null</code>
*/
FileObject[] getSeleniumDirectories() {
return seleniumRoots.getRoots();
}
/**
* @return selenium tests directory or <code>null</code>
*/
FileObject getSeleniumDirectory() {
for (FileObject root : seleniumRoots.getRoots()) {
// return the first one
return root;
}
return null;
}
/**
* @return web root directory or sources directory if not set
*/
FileObject getWebRootDirectory() {
if (webRootDirectory == null) {
webRootDirectory = resolveWebRootDirectory();
}
return webRootDirectory;
}
private FileObject resolveWebRootDirectory() {
if (PhpProjectValidator.isFatallyBroken(this)) {
// corrupted project
return null;
}
FileObject sources = getSourcesDirectory();
assert sources != null;
String webRootProperty = eval.getProperty(PhpProjectProperties.WEB_ROOT);
if (webRootProperty == null) {
// web root directory not set, return sources
return sources;
}
FileObject webRootDir = sources.getFileObject(webRootProperty);
if (webRootDir != null) {
return webRootDir;
}
LOGGER.log(Level.INFO, "Web root directory {0} not found for project {1}", new Object[] {webRootProperty, getName()});
// web root directory not found, return sources
return sources;
}
void addSourceDirListener() {
FileObject sourcesDirectory = getSourcesDirectory();
if (sourcesDirectory == null) {
return;
}
if (sourcesDirectory.equals(sourceDirectoryFileChangeListener.getSourceDir())) {
// already listening to this source dir
// this usually happens for new project - property change is fired _before_ project open
return;
}
synchronized (sourceDirectoryFileChangeListener) {
sourceDirectoryFileChangeListener.setSourceDir(sourcesDirectory);
FileUtil.addRecursiveListener(sourceDirectoryFileChangeListener, FileUtil.toFile(sourcesDirectory), new FileFilter() {
@Override
public boolean accept(File pathname) {
return isVisible(pathname);
}
}, null);
}
}
void removeSourceDirListener() {
FileObject sourceDir = sourceDirectoryFileChangeListener.getSourceDir();
if (sourceDir == null) {
// not listening
return;
}
synchronized (sourceDirectoryFileChangeListener) {
try {
FileUtil.removeRecursiveListener(sourceDirectoryFileChangeListener, FileUtil.toFile(sourceDir));
} catch (IllegalArgumentException ex) {
LOGGER.log(Level.INFO, null, ex);
} finally {
sourceDirectoryFileChangeListener.setSourceDir(null);
}
}
}
void recompileSources(CssPreprocessor cssPreprocessor) {
assert cssPreprocessor != null;
FileObject sourcesDirectory = getSourcesDirectory();
if (sourcesDirectory == null) {
return;
}
// force recompiling
CssPreprocessors.getDefault().process(cssPreprocessor, this, sourcesDirectory);
}
public PhpModule getPhpModule() {
PhpModule phpModule = getLookup().lookup(PhpModuleImpl.class);
assert phpModule != null;
return phpModule;
}
boolean isVisible(File file) {
if (getIgnoredFiles().contains(file)) {
return false;
}
return VisibilityQuery.getDefault().isVisible(file);
}
boolean isVisible(FileObject fileObject) {
File file = FileUtil.toFile(fileObject);
if (file == null) {
if (getIgnoredFileObjects().contains(fileObject)) {
return false;
}
return VisibilityQuery.getDefault().isVisible(fileObject);
}
return isVisible(file);
}
public Set<File> getIgnoredFiles() {
Set<File> ignored = new HashSet<>();
addIgnoredProjectFiles(ignored);
addIgnoredFrameworkFiles(ignored);
return ignored;
}
// #172139 caused NPE in GlobalVisibilityQueryImpl
public Set<FileObject> getIgnoredFileObjects() {
Set<FileObject> ignoredFileObjects = new HashSet<>();
for (File file : getIgnoredFiles()) {
FileObject fo = FileUtil.toFileObject(file);
if (fo != null) {
ignoredFileObjects.add(fo);
}
}
return ignoredFileObjects;
}
private void addIgnoredProjectFiles(final Set<File> ignored) {
ProjectManager.mutex().readAccess(new Mutex.Action<Void>() {
@Override
public Void run() {
synchronized (ignoredFoldersLock) {
if (ignoredFolders == null) {
ignoredFolders = resolveIgnoredFolders(PhpProjectProperties.IGNORE_PATH);
}
assert ignoredFolders != null : "Ignored folders cannot be null";
for (BasePathSupport.Item item : ignoredFolders) {
if (item.isBroken()) {
continue;
}
ignored.add(new File(item.getAbsoluteFilePath(helper.getProjectDirectory())));
}
}
return null;
}
});
}
private void resetIgnoredFolders() {
ProjectManager.mutex().readAccess(new Mutex.Action<Void>() {
@Override
public Void run() {
synchronized (ignoredFoldersLock) {
ignoredFolders = null;
}
return null;
}
});
}
private void addIgnoredFrameworkFiles(Set<File> ignored) {
PhpModule phpModule = getPhpModule();
for (PhpFrameworkProvider provider : getFrameworks()) {
PhpModuleIgnoredFilesExtender ignoredFilesExtender = provider.getIgnoredFilesExtender(phpModule);
if (ignoredFilesExtender == null) {
continue;
}
for (File file : ignoredFilesExtender.getIgnoredFiles()) {
assert file != null : "Ignored file = null found in " + provider.getIdentifier();
assert file.isAbsolute() : "Not absolute file found in " + provider.getIdentifier();
ignored.add(file);
}
}
}
// no need to cache it, it is called only for code analysis
public Set<FileObject> getCodeAnalysisExcludeFileObjects() {
Set<FileObject> excludedFileObjects = new HashSet<>();
Set<BasePathSupport.Item> excluded = resolveIgnoredFolders(PhpProjectProperties.CODE_ANALYSIS_EXCLUDES);
assert excluded != null : "Ignored folders cannot be null";
for (BasePathSupport.Item item : excluded) {
if (item.isBroken()) {
continue;
}
File file = new File(item.getAbsoluteFilePath(helper.getProjectDirectory()));
FileObject fo = FileUtil.toFileObject(file);
if (fo != null) {
excludedFileObjects.add(fo);
}
}
return excludedFileObjects;
}
private Set<BasePathSupport.Item> resolveIgnoredFolders(String propertyName) {
IgnorePathSupport ignorePathSupport = new IgnorePathSupport(eval, refHelper, helper);
Set<BasePathSupport.Item> ignored = new HashSet<>();
EditableProperties properties = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
Iterator<BasePathSupport.Item> itemsIterator = ignorePathSupport.itemsIterator(properties.getProperty(propertyName));
while (itemsIterator.hasNext()) {
ignored.add(itemsIterator.next());
}
return ignored;
}
public List<PhpFrameworkProvider> getFrameworks() {
return frameworks.getFrameworks();
}
public void resetFrameworks() {
List<PhpFrameworkProvider> oldFrameworkProviders = getFrameworks();
frameworks.resetFrameworks();
List<PhpFrameworkProvider> newFrameworkProviders = getFrameworks();
if (!oldFrameworkProviders.equals(newFrameworkProviders)) {
fireFrameworksChange();
}
}
void fireFrameworksChange() {
propertyChangeSupport.firePropertyChange(PROP_FRAMEWORKS, null, null);
// #209206 - also, likely some files are newly hidden/visible
fireIgnoredFilesChange();
}
public List<PhpTestingProvider> getTestingProviders() {
return testingProviders.getTestingProviders();
}
public String getName() {
if (name == null) {
ProjectManager.mutex().readAccess(new Mutex.Action<Void>() {
@Override
public Void run() {
Element data = getHelper().getPrimaryConfigurationData(true);
NodeList nl = data.getElementsByTagNameNS(PhpProjectType.PROJECT_CONFIGURATION_NAMESPACE, "name"); // NOI18N
if (nl.getLength() == 1) {
nl = nl.item(0).getChildNodes();
if (nl.getLength() == 1
&& nl.item(0).getNodeType() == Node.TEXT_NODE) {
name = ((Text) nl.item(0)).getNodeValue();
}
}
if (name == null) {
name = "???"; // NOI18N
}
return null;
}
});
}
assert name != null;
return name;
}
public void setName(final String name) {
ProjectManager.mutex().writeAccess(new Runnable() {
@Override
public void run() {
Element data = getHelper().getPrimaryConfigurationData(true);
NodeList nl = data.getElementsByTagNameNS(PhpProjectType.PROJECT_CONFIGURATION_NAMESPACE, "name"); // NOI18N
Element nameEl;
if (nl.getLength() == 1) {
nameEl = (Element) nl.item(0);
NodeList deadKids = nameEl.getChildNodes();
while (deadKids.getLength() > 0) {
nameEl.removeChild(deadKids.item(0));
}
} else {
nameEl = data.getOwnerDocument().createElementNS(
PhpProjectType.PROJECT_CONFIGURATION_NAMESPACE, "name"); // NOI18N
data.insertBefore(nameEl, /* OK if null */data.getChildNodes().item(0));
}
nameEl.appendChild(data.getOwnerDocument().createTextNode(name));
getHelper().putPrimaryConfigurationData(data, true);
}
});
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder(200);
buffer.append(getClass().getName());
buffer.append(" [ project directory: ");
buffer.append(getProjectDirectory());
buffer.append(" ]");
return buffer.toString();
}
public AntProjectHelper getHelper() {
return helper;
}
public CopySupport getCopySupport() {
return getLookup().lookup(CopySupport.class);
}
private Lookup createLookup(AuxiliaryConfiguration configuration, PhpModule phpModule) {
PhpProjectEncodingQueryImpl phpProjectEncodingQueryImpl = new PhpProjectEncodingQueryImpl(getEvaluator());
Lookup base = Lookups.fixed(new Object[] {
this,
CopySupport.getInstance(this),
new SeleniumProvider(),
new PhpCoverageProvider(this),
new Info(),
configuration,
new PhpOpenedHook(),
new PhpProjectXmlSavedHook(),
new PhpActionProvider(this),
new PhpConfigurationProvider(this),
phpModule,
PhpLanguagePropertiesAccessor.getDefault().createForProject(this),
new PhpEditorExtender(this),
helper.createCacheDirectoryProvider(),
helper.createAuxiliaryProperties(),
new ClassPathProviderImpl(this, getSourceRoots(), getTestRoots(), getSeleniumRoots()),
new PhpLogicalViewProvider(this),
new CustomizerProviderImpl(this),
PhpSharabilityQuery.create(helper, getEvaluator(), getSourceRoots(), getTestRoots(), getSeleniumRoots()),
LookupProviderSupport.createSharabilityQueryMerger(),
new PhpProjectOperations(this) ,
phpProjectEncodingQueryImpl,
new CreateFromTemplateAttributesImpl(getHelper(), phpProjectEncodingQueryImpl),
new PhpTemplates(),
new PhpSources(this, getHelper(), getEvaluator(), getSourceRoots(), getTestRoots(), getSeleniumRoots()),
getHelper(),
getEvaluator(),
PhpSearchInfo.create(this),
new PhpSubTreeSearchOptions(),
new PhpTestingProvidersImpl(testingProviders),
InternalWebServer.createForProject(this),
CssPreprocessorsUI.getDefault().createProjectProblemsProvider(this),
ProjectPropertiesProblemProvider.createForProject(this),
UILookupMergerSupport.createProjectProblemsProviderMerger(),
new ProjectWebRootProviderImpl(),
ClientSideDevelopmentSupport.create(this),
ProjectBrowserProviderImpl.create(this),
new PhpVisibilityQuery.PhpVisibilityQueryImpl(this),
new UsageLogging(),
new ImportantFilesImpl(this),
new ProjectUserAnnotationsProvider(this),
// ?? getRefHelper()
});
return LookupProviderSupport.createCompositeLookup(base, "Projects/org-netbeans-modules-php-project/Lookup"); // NOI18N
}
public ReferenceHelper getRefHelper() {
return refHelper;
}
public void fireIgnoredFilesChange() {
resetIgnoredFolders();
ignoredFoldersChangeSupport.fireChange();
}
private final class Info implements ProjectInformation {
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
@Override
public String getDisplayName() {
return PhpProject.this.getName();
}
@Override
public Icon getIcon() {
return ImageUtilities.image2Icon(ImageUtilities.loadImage(PROJECT_ICON));
}
@Override
public String getName() {
return PropertyUtils.getUsablePropertyName(getDisplayName());
}
@Override
public Project getProject() {
return PhpProject.this;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
void firePropertyChange(String prop) {
propertyChangeSupport.firePropertyChange(prop , null, null);
}
}
private final class PhpOpenedHook extends ProjectOpenedHook {
@Override
protected void projectOpened() {
new ProjectUpgrader(PhpProject.this).upgrade();
addSourceDirListener();
CssPreprocessors.getDefault().addCssPreprocessorsListener(cssPreprocessorsListener);
frameworks.projectOpened();
// avoid slowness
readFolders();
getTestingProviders();
PhpDocumentations.getDocumentations();
getName();
PhpOptions.getInstance().ensurePhpGlobalIncludePath();
ClassPathProviderImpl cpProvider = lookup.lookup(ClassPathProviderImpl.class);
ClassPath[] bootClassPaths = cpProvider.getProjectClassPaths(PhpSourcePath.BOOT_CP);
GlobalPathRegistry.getDefault().register(PhpSourcePath.BOOT_CP, bootClassPaths);
GlobalPathRegistry.getDefault().register(PhpSourcePath.PROJECT_BOOT_CP, cpProvider.getProjectClassPaths(PhpSourcePath.PROJECT_BOOT_CP));
GlobalPathRegistry.getDefault().register(PhpSourcePath.SOURCE_CP, cpProvider.getProjectClassPaths(PhpSourcePath.SOURCE_CP));
for (ClassPath classPath : bootClassPaths) {
IncludePathClassPathProvider.addProjectIncludePath(classPath);
}
// ensure that code coverage is initialized in case it's enabled...
PhpCoverageProvider coverageProvider = getLookup().lookup(PhpCoverageProvider.class);
if (coverageProvider.isEnabled()) {
PhpCoverageProvider.notifyProjectOpened(PhpProject.this);
}
JsTestingProvider jsTestingProvider = JsTestingProviders.getDefault().getJsTestingProvider(PhpProject.this, false);
if (jsTestingProvider != null) {
jsTestingProvider.projectOpened(PhpProject.this);
}
// autoconfigured?
checkAutoconfigured();
// #187060 - exception in projectOpened => project IS NOT opened (so move it at the end of the hook)
getCopySupport().projectOpened();
// log usage
PhpProjectUtils.logUsage(PhpProject.class, "USG_PROJECT_OPEN_PHP", Arrays.asList(PhpProjectUtils.getFrameworksForUsage(frameworks.getFrameworks()))); // NOI18N
// #192386
LOGGER.finest("PROJECT_OPENED_FINISHED");
}
@Override
protected void projectClosed() {
try {
removeSourceDirListener();
CssPreprocessors.getDefault().removeCssPreprocessorsListener(cssPreprocessorsListener);
frameworks.projectClosed();
ClassPathProviderImpl cpProvider = lookup.lookup(ClassPathProviderImpl.class);
ClassPath[] bootClassPaths = cpProvider.getProjectClassPaths(PhpSourcePath.BOOT_CP);
GlobalPathRegistry.getDefault().unregister(PhpSourcePath.BOOT_CP, bootClassPaths);
GlobalPathRegistry.getDefault().unregister(PhpSourcePath.PROJECT_BOOT_CP, cpProvider.getProjectClassPaths(PhpSourcePath.PROJECT_BOOT_CP));
GlobalPathRegistry.getDefault().unregister(PhpSourcePath.SOURCE_CP, cpProvider.getProjectClassPaths(PhpSourcePath.SOURCE_CP));
for (ClassPath classPath : bootClassPaths) {
IncludePathClassPathProvider.removeProjectIncludePath(classPath);
}
// internal web server
lookup.lookup(InternalWebServer.class).stop();
// browser
lookup.lookup(ClientSideDevelopmentSupport.class).close();
JsTestingProvider jsTestingProvider = JsTestingProviders.getDefault().getJsTestingProvider(PhpProject.this, false);
if (jsTestingProvider != null) {
jsTestingProvider.projectClosed(PhpProject.this);
}
} finally {
// #187060 - exception in projectClosed => project IS closed (so do it in finally block)
getCopySupport().projectClosed();
// #192386
LOGGER.finest("PROJECT_CLOSED_FINISHED");
}
}
private void readFolders() {
// #165494 - moved from projectClosed() to projectOpened()
// clear references to ensure that all the dirs are read again
webRootDirectory = null;
resetIgnoredFolders();
// read dirs
getIgnoredFiles();
getSourceRoots().getRoots();
getTestRoots().getRoots();
getSeleniumRoots().getRoots();
}
@NbBundle.Messages({
"# {0} - project name",
"PhpOpenedHook.notification.autoconfigured.title=Project {0} automatically configured",
"PhpOpenedHook.notification.autoconfigured.details=Review and correct important project settings detected by the IDE.",
})
private void checkAutoconfigured() {
PhpProjectProperties projectProperties = new PhpProjectProperties(PhpProject.this);
if (projectProperties.isAutoconfigured()) {
NotificationDisplayer.getDefault().notify(
Bundle.PhpOpenedHook_notification_autoconfigured_title(ProjectUtils.getInformation(PhpProject.this).getDisplayName()),
NotificationDisplayer.Priority.LOW.getIcon(),
Bundle.PhpOpenedHook_notification_autoconfigured_details(),
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PhpProjectUtils.openCustomizer(PhpProject.this, CompositePanelProviderImpl.SOURCES);
}
},
NotificationDisplayer.Priority.LOW);
projectProperties.setAutoconfigured(false);
projectProperties.save();
}
}
}
private static final class ConfigPropertyProvider extends FilterPropertyProvider implements PropertyChangeListener {
private final PropertyEvaluator baseEval;
private final String prefix;
private final AntProjectHelper helper;
public ConfigPropertyProvider(PropertyEvaluator baseEval, String prefix, AntProjectHelper helper) {
super(computeDelegate(baseEval, prefix, helper));
this.baseEval = baseEval;
this.prefix = prefix;
this.helper = helper;
baseEval.addPropertyChangeListener(this);
}
@Override
public void propertyChange(PropertyChangeEvent ev) {
if (PhpConfigurationProvider.PROP_CONFIG.equals(ev.getPropertyName())) {
setDelegate(computeDelegate(baseEval, prefix, helper));
}
}
private static PropertyProvider computeDelegate(PropertyEvaluator baseEval, String prefix, AntProjectHelper helper) {
String config = baseEval.getProperty(PhpConfigurationProvider.PROP_CONFIG);
if (config != null) {
return helper.getPropertyProvider(prefix + "/" + config + ".properties"); // NOI18N
}
return PropertyUtils.fixedPropertyProvider(Collections.<String, String>emptyMap());
}
}
public final class PhpProjectXmlSavedHook extends ProjectXmlSavedHook {
@Override
protected void projectXmlSaved() throws IOException {
Info info = getLookup().lookup(Info.class);
assert info != null;
info.firePropertyChange(ProjectInformation.PROP_NAME);
info.firePropertyChange(ProjectInformation.PROP_DISPLAY_NAME);
}
}
private final class SeleniumProvider implements PhpSeleniumProvider {
@Override
public FileObject getTestDirectory(boolean showCustomizer) {
return ProjectPropertiesSupport.getSeleniumDirectory(PhpProject.this, showCustomizer);
}
@Override
public void runAllTests() {
ConfigAction.get(ConfigAction.Type.SELENIUM, PhpProject.this).runProject();
}
@Override
public boolean isSupportEnabled(FileObject[] activatedFOs) {
if (activatedFOs.length == 0) {
return false;
}
PhpProject onlyOneProjectAllowed = null;
for (FileObject fileObj : activatedFOs) {
if (fileObj == null) {
return false;
}
// only php files or folders allowed
if (fileObj.isData() && !FileUtils.isPhpFile(fileObj)) {
return false;
}
PhpProject phpProject = PhpProjectUtils.getPhpProject(fileObj);
if (phpProject == null) {
return false;
}
if (PhpProjectValidator.isFatallyBroken(phpProject)) {
return false;
}
if (onlyOneProjectAllowed == null) {
onlyOneProjectAllowed = phpProject;
} else {
if (!onlyOneProjectAllowed.equals(phpProject)) {
// tests can be generated only for one project at one time
return false;
}
}
if (fileObj == phpProject.getProjectDirectory()) { // "Run Selenium Tests" action should be active for the project node
return true;
}
FileObject sources = ProjectPropertiesSupport.getSourcesDirectory(phpProject);
if (sources == null || sources.equals(fileObj)) {
return false;
}
if (!CommandUtils.isUnderSources(phpProject, fileObj)
|| CommandUtils.isUnderTests(phpProject, fileObj, false)
|| CommandUtils.isUnderSelenium(phpProject, fileObj, false)) {
return false;
}
}
return true;
}
@Override
public List<Object> getTestSourceRoots(Collection<SourceGroup> createdSourceRoots, FileObject refFileObject) {
ArrayList<Object> folders = new ArrayList<>();
Project p = FileOwnerQuery.getOwner(refFileObject);
if (p != null && (p instanceof PhpProject)) {
List<FileObject> seleniumDirectories = ProjectPropertiesSupport.getSeleniumDirectories((PhpProject) p, true);
SourceGroup[] sourceGroups = PhpProjectUtils.getSourceGroups((PhpProject) p);
for (SourceGroup sg : sourceGroups) {
if (!sg.contains(refFileObject)) {
if (seleniumDirectories.contains(sg.getRootFolder())) {
folders.add(sg);
}
}
}
}
return folders;
}
}
private final class ProjectPropertiesListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (PhpProjectProperties.IGNORE_PATH.equals(propertyName)) {
fireIgnoredFilesChange();
} else if (PhpProjectProperties.WEB_ROOT.equals(propertyName)
|| PhpProjectProperties.SRC_DIR.equals(propertyName)) {
FileObject oldWebRoot = webRootDirectory;
webRootDirectory = null;
// useful since it fires changes with fileobjects -> client can better use it than "htdocs/web/" values
propertyChangeSupport.firePropertyChange(PROP_WEB_ROOT, oldWebRoot, getWebRootDirectory());
}
}
}
private final class SourceDirectoryFileChangeListener implements FileChangeListener {
private volatile FileObject sourceDir;
@CheckForNull
public FileObject getSourceDir() {
return sourceDir;
}
public void setSourceDir(FileObject sourceDir) {
this.sourceDir = sourceDir;
}
@Override
public void fileFolderCreated(FileEvent fe) {
FileObject file = fe.getFile();
if (!isVisible(file)) {
return;
}
frameworksReset(file);
processChange(file);
}
@Override
public void fileDataCreated(FileEvent fe) {
FileObject file = fe.getFile();
if (!isVisible(file)) {
return;
}
frameworksReset(file);
browserReload(file);
processChange(file);
}
@Override
public void fileChanged(FileEvent fe) {
FileObject file = fe.getFile();
if (!isVisible(file)) {
return;
}
browserReload(file);
processChange(file);
}
@Override
public void fileDeleted(FileEvent fe) {
FileObject file = fe.getFile();
if (!isVisible(file)) {
return;
}
frameworksReset(file);
browserReload(file);
processChange(file);
}
@Override
public void fileRenamed(FileRenameEvent fe) {
FileObject file = fe.getFile();
if (!isVisible(file)) {
return;
}
frameworksReset(file);
processChange(file, fe.getName(), fe.getExt());
}
@Override
public void fileAttributeChanged(FileAttributeEvent fe) {
// noop
}
private boolean isVisible(FileObject file) {
return PhpProjectUtils.isVisible(PhpVisibilityQuery.forProject(PhpProject.this), file);
}
// if any direct child of source folder changes, reset frameworks (new framework can be found in project)
private void frameworksReset(FileObject file) {
FileObject sourcesDirectory = getSourcesDirectory();
if (sourcesDirectory == null) {
// corrupted project
return;
}
if (file.getParent().equals(sourcesDirectory)) {
LOGGER.fine("file change, frameworks back to null");
resetFrameworks();
}
}
// possible browser reload, if nb integration is present
private void browserReload(FileObject file) {
ClientSideDevelopmentSupport easelSupport = PhpProject.this.getLookup().lookup(ClientSideDevelopmentSupport.class);
assert easelSupport != null;
easelSupport.reload(file);
}
private void processChange(FileObject fileObject) {
CssPreprocessors.getDefault().process(PhpProject.this, fileObject);
}
private void processChange(FileObject fileObject, String originalName, String originalExtension) {
CssPreprocessors.getDefault().process(PhpProject.this, fileObject, originalName, originalExtension);
}
}
private final class PhpAntProjectListener implements AntProjectListener {
@Override
public void configurationXmlChanged(AntProjectEvent ev) {
name = null;
}
@Override
public void propertiesChanged(AntProjectEvent ev) {
}
}
private final class ProjectWebRootProviderImpl implements ProjectWebRootProvider {
@Override
public FileObject getWebRoot(FileObject file) {
FileObject webRoot = ProjectPropertiesSupport.getWebRootDirectory(PhpProject.this);
if (webRoot == null) {
return null;
}
if (webRoot.equals(file)
|| FileUtil.isParentOf(webRoot, file)) {
return webRoot;
}
return null;
}
@Override
public Collection<FileObject> getWebRoots() {
FileObject webRoot = ProjectPropertiesSupport.getWebRootDirectory(PhpProject.this);
if (webRoot == null) {
return Collections.emptyList();
}
return Collections.singleton(webRoot);
}
}
private static final class PhpSearchInfo extends SearchInfoDefinition implements PropertyChangeListener {
private static final Logger LOGGER = Logger.getLogger(PhpSearchInfo.class.getName());
private final PhpProject project;
// @GuardedBy(this)
private SearchInfo delegate = null;
private PhpSearchInfo(PhpProject project) {
this.project = project;
}
public static SearchInfoDefinition create(PhpProject project) {
PhpSearchInfo phpSearchInfo = new PhpSearchInfo(project);
project.getSourceRoots().addPropertyChangeListener(phpSearchInfo);
project.getTestRoots().addPropertyChangeListener(phpSearchInfo);
project.getSeleniumRoots().addPropertyChangeListener(phpSearchInfo);
return phpSearchInfo;
}
private SearchInfo createDelegate() {
SearchInfo searchInfo = SearchInfoUtils.createSearchInfoForRoots(
getRoots(), false, project.getSearchFilterDefinition(),
SearchInfoDefinitionFactory.SHARABILITY_FILTER);
return searchInfo;
}
@Override
public boolean canSearch() {
return true;
}
@Override
public Iterator<FileObject> filesToSearch(
SearchScopeOptions searchScopeOptions,
SearchListener listener,
AtomicBoolean terminated) {
return getDelegate().getFilesToSearch(searchScopeOptions,
listener, terminated).iterator();
}
@Override
public List<SearchRoot> getSearchRoots() {
return getDelegate().getSearchRoots();
}
private FileObject[] getRoots() {
List<FileObject> roots = new LinkedList<>();
addRoots(roots, project.getSourceRoots());
addRoots(roots, project.getTestRoots());
addRoots(roots, project.getSeleniumRoots());
addIncludePath(roots, PhpSourcePath.getIncludePath(project.getSourcesDirectory()));
return roots.toArray(new FileObject[roots.size()]);
}
// #197968
private void addRoots(List<FileObject> roots, SourceRoots sourceRoots) {
for (FileObject root : sourceRoots.getRoots()) {
if (!root.isFolder()) {
LOGGER.log(Level.WARNING, "Not folder {0} for source roots {1}", new Object[] {root, Arrays.toString(sourceRoots.getRootNames())});
} else {
roots.add(root);
}
}
}
private void addIncludePath(List<FileObject> roots, List<FileObject> includePath) {
for (FileObject folder : includePath) {
if (!folder.isFolder()) {
LOGGER.log(Level.WARNING, "Not folder {0} for Include path {1}", new Object[] {folder, includePath});
} else {
roots.add(folder);
}
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (SourceRoots.PROP_ROOTS.equals(evt.getPropertyName())) {
synchronized (this) {
delegate = createDelegate();
}
}
}
/**
* @return the delegate
*/
private synchronized SearchInfo getDelegate() {
if (delegate == null) {
delegate = createDelegate();
}
return delegate;
}
}
private final class PhpSearchFilterDef extends SearchFilterDefinition {
@Override
public boolean searchFile(FileObject file) {
if (!file.isData()) {
throw new IllegalArgumentException("File expected");
}
return PhpVisibilityQuery.forProject(PhpProject.this).isVisible(file);
}
@Override
public FolderResult traverseFolder(FileObject folder) {
if (!folder.isFolder()) {
throw new IllegalArgumentException("Folder expected");
}
if (PhpVisibilityQuery.forProject(PhpProject.this).isVisible(folder)) {
return FolderResult.TRAVERSE;
}
return FolderResult.DO_NOT_TRAVERSE;
}
}
private final class PhpSubTreeSearchOptions extends SubTreeSearchOptions {
private List<SearchFilterDefinition> filterList;
public PhpSubTreeSearchOptions() {
this.filterList = this.createList();
}
@Override
public List<SearchFilterDefinition> getFilters() {
return filterList;
}
private List<SearchFilterDefinition> createList() {
List<SearchFilterDefinition> list = new ArrayList<>(2);
list.add(getSearchFilterDefinition());
list.add(SearchInfoDefinitionFactory.SHARABILITY_FILTER);
return Collections.unmodifiableList(list);
}
}
public static final class ClientSideDevelopmentSupport implements ServerURLMappingImplementation, PageInspectorCustomizer, PropertyChangeListener {
private static final RequestProcessor RP = new RequestProcessor(ClientSideDevelopmentSupport.class);
private final UsageLogger browserUsageLogger = UsageLogger.projectBrowserUsageLogger(PhpProjectUtils.USAGE_LOGGER_NAME);
final PhpProject project;
private final RequestProcessor.Task reloadTask;
private volatile String projectRootUrl;
private volatile String browserId;
private volatile Boolean browserReloadOnSave = null;
// @GuardedBy("this")
private BrowserSupport browserSupport = null;
// @GuardedBy("this")
private boolean browserSupportInitialized = false;
volatile JButton customizerButton = null;
private ClientSideDevelopmentSupport(PhpProject project) {
assert project != null;
this.project = project;
reloadTask = RP.create(new Runnable() {
@Override
public void run() {
reload();
}
});
}
public static ClientSideDevelopmentSupport create(PhpProject project) {
ClientSideDevelopmentSupport serverMapping = new ClientSideDevelopmentSupport(project);
ProjectPropertiesSupport.addWeakPropertyEvaluatorListener(project, serverMapping);
return serverMapping;
}
@Override
public URL toServer(int projectContext, FileObject projectFile) {
initProjectUrl();
if (projectRootUrl == null) {
return null;
}
FileObject webRoot = project.getWebRootDirectory();
if (webRoot == null) {
return null;
}
String relPath = FileUtil.getRelativePath(webRoot, projectFile);
if (relPath == null) {
return null;
}
try {
URL u = new URL(projectRootUrl + relPath);
WebBrowser browser = getWebBrowser();
if (browser != null) {
u = browser.toBrowserURL(project, projectFile, u);
}
return u;
} catch (MalformedURLException ex) {
return null;
}
}
@Override
public FileObject fromServer(int projectContext, URL serverURL) {
// #219339 - strip down query and/or fragment:
serverURL = WebUtils.stringToUrl(WebUtils.urlToString(serverURL, true));
if (serverURL == null) {
return null;
}
initProjectUrl();
if (projectRootUrl == null) {
return null;
}
FileObject webRoot = project.getWebRootDirectory();
if (webRoot == null) {
return null;
}
WebBrowser browser = getWebBrowser();
if (browser != null) {
serverURL = browser.fromBrowserURL(project, serverURL);
}
String url = CommandUtils.urlToString(serverURL, true);
if (url.startsWith(projectRootUrl)) {
return webRoot.getFileObject(url.substring(projectRootUrl.length()));
}
return null;
}
@Override
public boolean isHighlightSelectionEnabled() {
return true;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener l) {
// noop
}
@Override
public void removePropertyChangeListener(PropertyChangeListener l) {
// noop
}
public void showFileUrl(URL url, FileObject file) {
// let browser update URL if necessary:
WebBrowser browser = getWebBrowser();
if (browser != null) {
url = browser.toBrowserURL(project, file, url);
}
BrowserSupport support = getBrowserSupport();
if (support != null) {
support.load(url, file);
} else {
HtmlBrowser.URLDisplayer.getDefault().showURL(url);
}
// usage logging
if (browser == null) {
// default ide browser
browser = BrowserUISupport.getDefaultBrowserChoice(true);
}
browserUsageLogger.log(PhpProjectType.TYPE, browser.getId(), browser.getBrowserFamily().name());
}
private void initProjectUrl() {
if (projectRootUrl == null) {
projectRootUrl = getProjectRootUrl();
}
}
private String getProjectRootUrl() {
try {
String url = CommandUtils.urlToString(CommandUtils.getBaseURL(project, true), true);
if (!url.endsWith("/")) { // NOI18N
url += "/"; // NOI18N
}
return url;
} catch (MalformedURLException ex) {
return null;
}
}
public boolean canReload(FileObject fo) {
initBrowser();
// #226389
DebugStarter debugStarter = DebugStarterFactory.getInstance();
if (debugStarter != null
&& debugStarter.isAlreadyRunning()) {
return false;
}
BrowserSupport support = getBrowserSupport();
if (support == null || support.ignoreChange(fo)) {
return false;
}
// #226256
return browserReloadOnSave;
}
public void reload(FileObject file) {
if (canReload(file)) {
reloadTask.schedule(200);
}
}
@NbBundle.Messages("ClientSideDevelopmentSupport.reload.copySupportRunning=Copy Support is still running - do you really want to reload the page?")
void reload() {
assert RP.isRequestProcessorThread();
BrowserSupport support = getBrowserSupport();
if (support == null) {
return;
}
if (!support.isWebBrowserPaneOpen()) {
return;
}
// #226884, 227281 - wait till copysupport finishes
if (!project.getCopySupport().waitFinished(Bundle.ClientSideDevelopmentSupport_reload_copySupportRunning(), 5000, getCustomizerButton())) {
return;
}
support.reload();
}
public void close() {
BrowserSupport support = getBrowserSupport();
if (support != null) {
support.close(true);
}
}
@NbBundle.Messages("ClientSideDevelopmentSupport.reload.customize=Customize...")
private JButton getCustomizerButton() {
if (customizerButton != null) {
return customizerButton;
}
customizerButton = Mutex.EVENT.readAccess(new Mutex.Action<JButton>() {
@Override
public JButton run() {
assert EventQueue.isDispatchThread();
JButton button = new JButton(Bundle.ClientSideDevelopmentSupport_reload_customize());
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PhpProjectUtils.openCustomizer(project, CompositePanelProviderImpl.BROWSER);
}
});
return button;
}
});
return customizerButton;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (PhpProjectProperties.URL.equals(propertyName)) {
projectRootUrl = null;
} else if (PhpProjectProperties.BROWSER_ID.equals(propertyName)) {
browserUsageLogger.reset();
resetBrowser();
resetBrowserSupport();
} else if (PhpProjectProperties.BROWSER_RELOAD_ON_SAVE.equals(propertyName)) {
resetBrowserReloadOnSave();
}
}
private void initBrowser() {
if (browserId == null) {
browserId = project.getEvaluator().getProperty(PhpProjectProperties.BROWSER_ID);
}
if (browserReloadOnSave == null) {
if (browserId == null) {
// default ide browser
browserReloadOnSave = false;
} else {
WebBrowser browser = BrowserUISupport.getBrowser(browserId);
if (browser != null
&& browser.hasNetBeansIntegration()) {
browserReloadOnSave = ProjectPropertiesSupport.getBrowserReloadOnSave(project);
} else {
browserReloadOnSave = false;
}
}
}
assert browserReloadOnSave != null;
}
private void resetBrowser() {
browserId = null;
resetBrowserReloadOnSave();
}
private void resetBrowserReloadOnSave() {
browserReloadOnSave = null;
}
private synchronized void resetBrowserSupport() {
if (browserSupport != null) {
browserSupport.close(false);
}
browserSupport = null;
browserSupportInitialized = false;
}
private synchronized BrowserSupport getBrowserSupport() {
if (browserSupportInitialized) {
return browserSupport;
}
browserSupportInitialized = true;
WebBrowser browser = getWebBrowser();
if (browser == null) {
browserSupport = null;
return null;
}
browserSupport = BrowserSupport.create(browser);
return browserSupport;
}
@CheckForNull
private WebBrowser getWebBrowser() {
initBrowser();
if (browserId == null) {
return null;
}
return BrowserUISupport.getBrowser(browserId);
}
}
private static final class PhpTestingProvidersImpl implements PhpTestingProviders {
private final TestingProviders testingProviders;
public PhpTestingProvidersImpl(TestingProviders testingProviders) {
assert testingProviders != null;
this.testingProviders = testingProviders;
}
@Override
public List<PhpTestingProvider> getEnabledTestingProviders() {
return testingProviders.getTestingProviders();
}
}
}