| /* |
| * 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(); |
| } |
| |
| } |
| |
| } |