/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.ruby.railsprojects.ui;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.modules.ruby.railsprojects.Generator;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.modules.ruby.rubyproject.ui.LibrariesNode;
import org.netbeans.modules.ruby.railsprojects.RailsProject;
import org.netbeans.modules.ruby.railsprojects.SourceRoots;
import org.netbeans.modules.ruby.railsprojects.ui.customizer.CustomizerProviderImpl;
import org.netbeans.modules.ruby.rubyproject.rake.RakeSupport;
import org.netbeans.spi.project.ui.support.NodeFactory;
import org.netbeans.spi.project.ui.support.NodeList;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
/**
* Factory for the nodes in the Rails Project logical view.
*/
@NodeFactory.Registration(projectType="org-netbeans-modules-ruby-railsprojects")
public final class ProjectRootNodeFactory implements NodeFactory {
public NodeList createNodes(Project p) {
RailsProject project = p.getLookup().lookup(RailsProject.class);
assert project != null;
return new RootChildren(project);
}
private static class RootChildren implements NodeList<RootChildNode>, ChangeListener {
private final RailsProject project;
private final List<ChangeListener> listeners;
public RootChildren(final RailsProject project) {
this.project = project;
this.listeners = new ArrayList<ChangeListener>();
}
public List<RootChildNode> keys() {
if (this.project.getProjectDirectory() == null || !this.project.getProjectDirectory().isValid()) {
return Collections.emptyList();
}
// source roots
Sources sources = getSources();
SourceGroup[] groups = sources.getSourceGroups(RailsProject.SOURCES_TYPE_RUBY);
// Here we're adding sources, tests
List<RootChildNode> result = new ArrayList<RootChildNode>(groups.length);
for( int i = 0; i < groups.length; i++ ) {
result.add(RootChildNode.group(groups[i], getGenerator(groups[i].getName())));
}
// libraries node
result.add(RootChildNode.libraries());
// files
SourceRoots roots = project.getSourceRoots();
if (roots != null) {
FileObject[] extra = roots.getExtraFiles();
if (extra != null && extra.length > 0) {
for (FileObject f : extra) {
result.add(RootChildNode.fileObject(f));
}
}
}
return result;
}
private Generator getGenerator(String subdir) {
if (subdir.equals("app/controllers")) { // NOI18N
return Generator.CONTROLLER;
}
if (subdir.equals("app/views")) { // NOI18N
return Generator.CONTROLLER;
}
if (subdir.equals("app/models")) { // NOI18N
return Generator.MODEL;
}
if (subdir.equals("app/metal")) { // NOI18N
return Generator.METAL;
}
if (subdir.equals("db")) { // NOI18N
return Generator.MIGRATION;
}
return Generator.NONE;
}
public synchronized void addChangeListener(ChangeListener l) {
listeners.add(l);
}
public synchronized void removeChangeListener(ChangeListener l) {
listeners.remove(l);
}
private void fireChange() {
ArrayList<ChangeListener> list = new ArrayList<ChangeListener>();
synchronized (this) {
list.addAll(listeners);
}
Iterator<ChangeListener> it = list.iterator();
while (it.hasNext()) {
ChangeListener elem = it.next();
elem.stateChanged(new ChangeEvent( this ));
}
}
public Node node(final RootChildNode key) {
if (key.libraryNode) {
return new LibrariesNode(project);
}
if (key.group != null) {
return new FolderViewFilterNode(key.group, key.generator, project);
} else if (key.fileObject != null) {
try {
if (RakeSupport.isRakeFile(key.fileObject)) {
return new RakeSupport.RakeNode(key.fileObject);
} else {
DataObject dobj = DataObject.find(key.fileObject);
return new FilterNode(dobj.getNodeDelegate());
}
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
return null;
}
} else {
throw new AssertionError("Unknown/Invalid key: " + key);
}
}
public void addNotify() {
getSources().addChangeListener(this);
}
public void removeNotify() {
getSources().removeChangeListener(this);
}
public void stateChanged(ChangeEvent e) {
// setKeys(getKeys());
// The caller holds ProjectManager.mutex() read lock
SwingUtilities.invokeLater(new Runnable() {
public void run() {
fireChange();
}
});
}
private Sources getSources() {
return ProjectUtils.getSources(project);
}
}
private static class RootChildNode {
private final SourceGroup group;
private final FileObject fileObject;
private final Generator generator;
private final boolean libraryNode;
private RootChildNode(SourceGroup group, FileObject fileObject, Generator generator, boolean libraryNode) {
this.group = group;
this.fileObject = fileObject;
this.generator = generator;
this.libraryNode = libraryNode;
}
private RootChildNode(SourceGroup group, FileObject fileObject, Generator generator) {
this(group, fileObject, generator, false);
}
static RootChildNode group(final SourceGroup group, final Generator generator) {
return new RootChildNode(group, group.getRootFolder(), generator);
}
static RootChildNode fileObject(final FileObject fileObject) {
return new RootChildNode(null, fileObject, Generator.NONE);
}
static RootChildNode libraries() {
return new RootChildNode(null, null, Generator.NONE, true);
}
public @Override int hashCode() {
if (libraryNode) {
return 0;
}
return fileObject.hashCode();
}
public @Override boolean equals(Object obj) {
if (!(obj instanceof RootChildNode)) {
return false;
} else {
RootChildNode otherKey = (RootChildNode) obj;
if (libraryNode || otherKey.libraryNode) {
return libraryNode && otherKey.libraryNode;
}
String thisDisplayName = group == null ? null : group.getDisplayName();
String otherDisplayName = otherKey.group == null ? null : otherKey.group.getDisplayName();
// XXX what is the operator binding order supposed to be here??
return fileObject.equals(otherKey.fileObject) &&
(thisDisplayName == null ? otherDisplayName == null : thisDisplayName.equals(otherDisplayName));
}
}
public @Override String toString() {
return "ProjectRootNodeFactory[fileObject: " + fileObject + // NOI18N
", group: " + group + // NOI18N
", generator: " + generator + // NOI18N
", libraryNode: " + libraryNode + ']'; // NOI18N
}
}
// Copied from inner class in Java Projects' PackageView class:
/**
* FilterNode which listens on the PackageViewSettings and changes the view to
* the package view or tree view
*
*/
private static final class RootNode extends FilterNode { // implements PropertyChangeListener {
private SourceGroup sourceGroup;
private RootNode (SourceGroup group, Generator generator) {
// XXX?
super(getOriginalNode(group, generator));
this.sourceGroup = group;
//JavaProjectSettings.addPropertyChangeListener(WeakListeners.propertyChange(this, JavaProjectSettings.class));
}
// public void propertyChange (PropertyChangeEvent event) {
// if (JavaProjectSettings.PROP_PACKAGE_VIEW_TYPE.equals(event.getPropertyName())) {
// changeOriginal(getOriginalNode(sourceGroup), true);
// }
// }
private static Node getOriginalNode(SourceGroup group, Generator generator) {
FileObject root = group.getRootFolder();
//Guard condition, if the project is (closed) and deleted but not yet gced
// and the view is switched, the source group is not valid.
if ( root == null || !root.isValid()) {
return new AbstractNode (Children.LEAF, Lookups.singleton(Generator.NONE));
}
// switch (JavaProjectSettings.getPackageViewType()) {
// case JavaProjectSettings.TYPE_PACKAGE_VIEW:
// return new PackageRootNode(group);
// case JavaProjectSettings.TYPE_TREE:
return new TreeRootNode(group, generator);
// default:
// assert false : "Unknown PackageView Type"; //NOI18N
// return new PackageRootNode(group);
// }
}
}
/** Yet another cool filter node just to add properties action
*/
private static class FolderViewFilterNode extends FilterNode {
private String nodeName;
private Project project;
Action[] actions;
public FolderViewFilterNode(SourceGroup sourceGroup, Generator generator, Project project) {
//super(PackageView.createPackageView(sourceGroup));
super(new RootNode(sourceGroup, generator));
this.project = project;
this.nodeName = "Sources"; // NOI18N
}
public @Override Action[] getActions(boolean context) {
if (!context) {
if (actions == null) {
Action superActions[] = super.getActions(context);
actions = new Action[superActions.length + 2];
System.arraycopy(superActions, 0, actions, 0, superActions.length);
actions[superActions.length] = null;
actions[superActions.length + 1] = new PreselectPropertiesAction(project, nodeName);
}
return actions;
} else {
return super.getActions(context);
}
}
}
/** The special properties action
*/
static class PreselectPropertiesAction extends AbstractAction {
private final Project project;
private final String nodeName;
private final String panelName;
public PreselectPropertiesAction(Project project, String nodeName) {
this(project, nodeName, null);
}
public PreselectPropertiesAction(Project project, String nodeName, String panelName) {
super(NbBundle.getMessage(ProjectRootNodeFactory.class, "LBL_Properties_Action"));
this.project = project;
this.nodeName = nodeName;
this.panelName = panelName;
}
public void actionPerformed(ActionEvent e) {
// RubyCustomizerProvider cp = (RubyCustomizerProvider) project.getLookup().lookup(RubyCustomizerProvider.class);
CustomizerProviderImpl cp = project.getLookup().lookup(CustomizerProviderImpl.class);
if (cp != null) {
cp.showCustomizer(nodeName, panelName);
}
}
}
}