blob: 363becc21f05a9a20628c56e441e70e6ed14595c [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.web.project.ui;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.api.queries.VisibilityQuery;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.java.api.common.project.ui.ProjectUISupport;
import org.netbeans.modules.web.api.webmodule.WebProjectConstants;
import org.netbeans.modules.web.project.WebProject;
import org.netbeans.modules.web.project.ui.customizer.WebProjectProperties;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.ui.support.CommonProjectActions;
import org.netbeans.spi.project.ui.support.NodeFactory;
import org.netbeans.spi.project.ui.support.NodeList;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.actions.FileSystemAction;
import org.openide.actions.FindAction;
import org.openide.actions.PasteAction;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.ChangeableDataFilter;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.PasteType;
/**
*
* @author mkleint
*/
@NodeFactory.Registration(projectType="org-netbeans-modules-web-project",position=100)
public final class DocBaseNodeFactory implements NodeFactory {
/** Creates a new instance of LibrariesNodeFactory */
public DocBaseNodeFactory() {
}
private static RequestProcessor RP = new RequestProcessor();
public NodeList createNodes(Project p) {
WebProject project = p.getLookup().lookup(WebProject.class);
assert project != null;
return new DocBaseNodeList(project);
}
private static class DocBaseNodeList implements NodeList<String>, PropertyChangeListener {
private static final String DOC_BASE = "docBase"; //NOI18N
private static final String WEB_INF = "webInf"; //NOI18N
private final WebProject project;
private final ChangeSupport changeSupport = new ChangeSupport(this);
private final PropertyEvaluator evaluator;
private final UpdateHelper helper;
private SourceGroup webDocRoot;
DocBaseNodeList(WebProject proj) {
project = proj;
evaluator = project.evaluator();
helper = project.getUpdateHelper();
Sources s = project.getLookup().lookup(Sources.class);
assert s != null;
// assert s.getSourceGroups(WebProjectConstants.TYPE_DOC_ROOT).length > 0;
if(s.getSourceGroups(WebProjectConstants.TYPE_DOC_ROOT).length > 0)
{
webDocRoot = s.getSourceGroups(WebProjectConstants.TYPE_DOC_ROOT)[0];
}
else
{
String name = ProjectUtils.getInformation( proj ).getDisplayName();
NotifyDescriptor nd = new NotifyDescriptor.Message(NbBundle.getMessage(DocBaseNodeList.class, "LBL_No_Source_Groups_Found", name), NotifyDescriptor.INFORMATION_MESSAGE);
DialogDisplayer.getDefault().notify(nd);
}
}
public List<String> keys() {
FolderHolder nodeFolders = getNodeFolders();
List<String> result = new ArrayList<String>();
result.add(DOC_BASE + getFolderPath(nodeFolders.getWebDocBaseDir()));
if (!nodeFolders.hasCorrectStructure()) {
result.add(WEB_INF + getFolderPath(nodeFolders.getWebInfDir()));
}
return result;
}
public void addChangeListener(ChangeListener l) {
changeSupport.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
changeSupport.removeChangeListener(l);
}
public Node node(String key) {
FolderHolder nodeFolders = getNodeFolders();
if (key.startsWith(DOC_BASE)) {
FileObject webDocBaseDir = nodeFolders.getWebDocBaseDir();
DataFolder webFolder = getFolder(webDocBaseDir);
if (webFolder != null) {
return new DocBaseNode(webFolder, project, new VisibilityQueryDataFilter(webDocRoot));
}
return null;
} else if (key.startsWith(WEB_INF)) {
if (nodeFolders.hasCorrectStructure()) {
return null;
}
FileObject webInfDir = nodeFolders.getWebInfDir();
DataFolder webInfFolder = getFolder(webInfDir);
if (webInfFolder != null) {
return new WebInfNode(webInfFolder, project, new VisibilityQueryDataFilter(null));
}
return null;
}
assert false: "No node for key: " + key; // NOI18N
return null;
}
public void addNotify() {
evaluator.addPropertyChangeListener(this);
}
public void removeNotify() {
evaluator.removePropertyChangeListener(this);
}
public void propertyChange(PropertyChangeEvent evt) {
// The caller holds ProjectManager.mutex() read lock
// #197171 - changeSupport used to fire change in AWT thread which
// does not work in following scenario: when project properties has
// been changed and are being saved the write lock on ProjectManager
// can be hold for long time. And that prevents getNodeFolders()
// method in this class from being executed (in response to change). Therefore I posted
// below fireChange to background thread rather then AWT thread:
RP.post(new Runnable() {
public void run() {
changeSupport.fireChange();
}
});
}
private DataFolder getFolder(FileObject folder) {
if (folder != null) {
return DataFolder.findFolder(folder);
}
return null;
}
// # 114402
private String getFolderPath(FileObject folder) {
if (folder == null) {
return "";
}
return folder.getPath();
}
private FileObject getFileObject(String propName) {
String foName = evaluator.getProperty(propName);
if (foName == null) {
return null;
}
FileObject fo = helper.getAntProjectHelper().resolveFileObject(foName);
// when the project is deleted externally, the sources change could
// trigger a call to thid method before the project directory is
// notified about the deletion - invalid FileObject-s could be returned
return fo != null && fo.isValid() ? fo : null;
}
private FolderHolder getNodeFolders() {
return ProjectManager.mutex().readAccess(new Mutex.Action<FolderHolder>() {
public FolderHolder run() {
FileObject webDocBaseDir = getFileObject(WebProjectProperties.WEB_DOCBASE_DIR);
FileObject webInf = getFileObject(WebProjectProperties.WEBINF_DIR);
return new FolderHolder(webDocBaseDir, webInf);
}
});
}
private static final class FolderHolder {
private final FileObject webDocBaseDir;
private final FileObject webInfDir;
public FolderHolder(FileObject webDocBaseDir, FileObject webInfDir) {
this.webDocBaseDir = webDocBaseDir;
this.webInfDir = webInfDir;
}
public FileObject getWebDocBaseDir() {
return webDocBaseDir;
}
public FileObject getWebInfDir() {
return webInfDir;
}
/**
* Return <code>true</code> if <tt>WEB-INF<tt> folder
* is located inside <tt>web</tt> folder.
* Return <code>false</code> if any of these folders
* is <code>null</code>.
*/
public boolean hasCorrectStructure() {
if (webDocBaseDir == null
|| webInfDir == null) {
return false;
}
return FileUtil.isParentOf(webDocBaseDir, webInfDir);
}
}
}
private static final class DocBaseNode extends BaseNode {
DocBaseNode (DataFolder folder, WebProject project, VisibilityQueryDataFilter filter) {
super(folder, project, filter);
}
@Override
public String getDisplayName () {
return NbBundle.getMessage(DocBaseNodeFactory.class, "LBL_Node_DocBase"); //NOI18N
}
}
private static final class WebInfNode extends BaseNode {
WebInfNode (DataFolder folder, WebProject project, VisibilityQueryDataFilter filter) {
super (folder, project, filter);
}
@Override
public String getDisplayName() {
return NbBundle.getMessage(DocBaseNodeFactory.class, "LBL_Node_WebInf"); //NOI18N
}
}
private abstract static class BaseNode extends FilterNode {
private static Image WEB_PAGES_BADGE = ImageUtilities.loadImage( "org/netbeans/modules/web/project/ui/resources/webPagesBadge.gif" ); //NOI18N
/**
* The MIME type of Java files.
*/
private static final String JAVA_MIME_TYPE = "text/x-java"; //NO18N
private Action actions[];
protected final WebProject project;
BaseNode(final DataFolder folder, WebProject project, VisibilityQueryDataFilter filter) {
super(folder.getNodeDelegate(), folder.createNodeChildren(filter));
this.project = project;
}
@Override
public Image getIcon(int type) {
return computeIcon(false, type);
}
@Override
public Image getOpenedIcon(int type) {
return computeIcon(true, type);
}
private Node getDataFolderNodeDelegate() {
return getLookup().lookup(DataFolder.class).getNodeDelegate();
}
private Image computeIcon(boolean opened, int type) {
Image image;
image = opened ? getDataFolderNodeDelegate().getOpenedIcon(type) : getDataFolderNodeDelegate().getIcon(type);
image = ImageUtilities.mergeImages(image, WEB_PAGES_BADGE, 7, 7);
return image;
}
@Override
public boolean canRename() {
return false;
}
@Override
public Action[] getActions(boolean context) {
if (actions == null) {
actions = new Action[9];
actions[0] = CommonProjectActions.newFileAction();
actions[1] = null;
actions[2] = SystemAction.get(FindAction.class);
actions[3] = null;
actions[4] = SystemAction.get(PasteAction.class);
actions[5] = null;
actions[6] = SystemAction.get(FileSystemAction.class);
actions[7] = null;
actions[8] = ProjectUISupport.createPreselectPropertiesAction(project, "Sources", null); //NOI18N
}
return actions;
}
@Override
public PasteType getDropType(Transferable t, int action, int index) {
try {
if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)){
Object data = t.getTransferData(DataFlavor.javaFileListFlavor);
if (data != null) {
List list = (List) data;
for (Object each : list) {
File f = FileUtil.normalizeFile((File) each);
FileObject file = FileUtil.toFileObject(f);
if (file != null && JAVA_MIME_TYPE.equals(file.getMIMEType())) { //NO18N
// don't allow java files, see #119968
return null;
}
}
}
}
} catch (UnsupportedFlavorException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return super.getDropType(t, action, index);
}
}
static final class VisibilityQueryDataFilter implements ChangeListener, ChangeableDataFilter, PropertyChangeListener {
private static final long serialVersionUID = 1L;
private final ChangeSupport changeSupport = new ChangeSupport(this);
private final SourceGroup sourceGroup;
public VisibilityQueryDataFilter(SourceGroup sourceGroup) {
this.sourceGroup = sourceGroup;
if (this.sourceGroup != null) {
this.sourceGroup.addPropertyChangeListener(this);
}
VisibilityQuery defaultQuery = VisibilityQuery.getDefault();
defaultQuery.addChangeListener(WeakListeners.change(this, defaultQuery));
}
public boolean acceptDataObject(DataObject obj) {
FileObject fo = obj.getPrimaryFile();
return (sourceGroup == null || sourceGroup.contains(fo)) && VisibilityQuery.getDefault().isVisible(fo);
}
public void stateChanged( ChangeEvent e) {
changeSupport.fireChange();
}
public void addChangeListener( ChangeListener listener ) {
changeSupport.addChangeListener(listener);
}
public void removeChangeListener( ChangeListener listener ) {
changeSupport.removeChangeListener(listener);
}
public void propertyChange(PropertyChangeEvent arg0) {
changeSupport.fireChange();
}
}
}