001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.ant.freeform.ui;
043:
044: import java.awt.Image;
045: import java.awt.event.ActionEvent;
046: import java.beans.PropertyChangeEvent;
047: import java.beans.PropertyChangeListener;
048: import java.io.IOException;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.Collections;
052: import java.util.List;
053: import java.util.logging.Level;
054: import java.util.logging.Logger;
055: import javax.swing.AbstractAction;
056: import javax.swing.Action;
057: import javax.swing.event.ChangeEvent;
058: import javax.swing.event.ChangeListener;
059: import org.netbeans.api.project.Project;
060: import org.netbeans.api.project.ProjectInformation;
061: import org.netbeans.api.project.ProjectManager;
062: import org.netbeans.api.project.ProjectUtils;
063: import org.netbeans.api.project.ui.OpenProjects;
064: import org.netbeans.api.project.ui.OpenProjects;
065: import org.netbeans.api.queries.VisibilityQuery;
066: import org.netbeans.modules.ant.freeform.FreeformProject;
067: import org.netbeans.modules.ant.freeform.FreeformProjectType;
068: import org.netbeans.modules.ant.freeform.spi.support.Util;
069: import org.netbeans.modules.ant.freeform.spi.ProjectNature;
070: import org.netbeans.spi.project.support.ant.AntProjectEvent;
071: import org.netbeans.spi.project.support.ant.AntProjectListener;
072: import org.netbeans.spi.project.support.ant.PathMatcher;
073: import org.netbeans.spi.project.ui.support.NodeFactory;
074: import org.netbeans.spi.project.ui.support.NodeList;
075: import org.openide.actions.OpenAction;
076: import org.openide.filesystems.FileObject;
077: import org.openide.filesystems.FileUtil;
078: import org.openide.loaders.ChangeableDataFilter;
079: import org.openide.loaders.DataFolder;
080: import org.openide.loaders.DataObject;
081: import org.openide.loaders.DataObjectNotFoundException;
082: import org.openide.nodes.AbstractNode;
083: import org.openide.nodes.Children;
084: import org.openide.nodes.FilterNode;
085: import org.openide.nodes.Node;
086: import org.openide.util.ChangeSupport;
087: import org.openide.util.ContextAwareAction;
088: import org.openide.util.Exceptions;
089: import org.openide.util.Lookup;
090: import org.openide.util.RequestProcessor;
091: import org.openide.util.Utilities;
092: import org.openide.util.actions.SystemAction;
093: import org.openide.util.lookup.Lookups;
094: import org.w3c.dom.Element;
095:
096: /**
097: *
098: * @author mkleint
099: */
100: public class FolderNodeFactory implements NodeFactory {
101:
102: /** Creates a new instance of FolderNodeFactory */
103: public FolderNodeFactory() {
104: }
105:
106: public NodeList createNodes(Project p) {
107: FreeformProject project = p.getLookup().lookup(
108: FreeformProject.class);
109: assert project != null;
110: return new RootChildren(project);
111: }
112:
113: static boolean synchronous = false; // for ViewTest
114:
115: private static final class RootChildren implements
116: NodeList<Element>, AntProjectListener,
117: PropertyChangeListener {
118:
119: private final FreeformProject p;
120: private List<Element> keys = new ArrayList<Element>();
121: private ChangeSupport cs = new ChangeSupport(this );
122:
123: public RootChildren(FreeformProject p) {
124: this .p = p;
125: }
126:
127: public void addNotify() {
128: updateKeys(false);
129: p.helper().addAntProjectListener(this );
130: p.evaluator().addPropertyChangeListener(this );
131: }
132:
133: public void removeNotify() {
134: keys = null;
135: p.helper().removeAntProjectListener(this );
136: p.evaluator().removePropertyChangeListener(this );
137: }
138:
139: private void updateKeys(boolean fromListener) {
140: Element genldata = p.getPrimaryConfigurationData();
141: Element viewEl = Util.findElement(genldata, "view",
142: FreeformProjectType.NS_GENERAL); // NOI18N
143: if (viewEl != null) {
144: Element itemsEl = Util.findElement(viewEl, "items",
145: FreeformProjectType.NS_GENERAL); // NOI18N
146: keys = Util.findSubElements(itemsEl);
147: } else {
148: keys = Collections.<Element> emptyList();
149: }
150: if (fromListener && !synchronous) {
151: // #50328, #58491 - post setKeys to different thread to prevent deadlocks
152: RequestProcessor.getDefault().post(new Runnable() {
153: public void run() {
154: cs.fireChange();
155: }
156: });
157: } else {
158: cs.fireChange();
159: }
160: }
161:
162: public void configurationXmlChanged(AntProjectEvent ev) {
163: updateKeys(true);
164: }
165:
166: public void propertiesChanged(AntProjectEvent ev) {
167: // ignore
168: }
169:
170: public void propertyChange(PropertyChangeEvent ev) {
171: updateKeys(true);
172: }
173:
174: public List<Element> keys() {
175: return keys;
176: }
177:
178: public void addChangeListener(ChangeListener l) {
179: cs.addChangeListener(l);
180: }
181:
182: public void removeChangeListener(ChangeListener l) {
183: cs.removeChangeListener(l);
184: }
185:
186: public Node node(Element itemEl) {
187:
188: Element locationEl = Util.findElement(itemEl, "location",
189: FreeformProjectType.NS_GENERAL); // NOI18N
190: String location = Util.findText(locationEl);
191: String locationEval = p.evaluator().evaluate(location);
192: if (locationEval == null) {
193: return null;
194: }
195: FileObject file = p.helper()
196: .resolveFileObject(locationEval);
197: if (file == null) {
198: // Not there... skip this node.
199: return null;
200: }
201: String label;
202: Element labelEl = Util.findElement(itemEl, "label",
203: FreeformProjectType.NS_GENERAL); // NOI18N
204: if (labelEl != null) {
205: label = Util.findText(labelEl);
206: } else {
207: label = null;
208: }
209: if (itemEl.getLocalName().equals("source-folder")) { // NOI18N
210: if (!file.isFolder()) {
211: // Just a file. Skip it.
212: return null;
213: }
214: String includes = null;
215: Element includesEl = Util.findElement(itemEl,
216: "includes", FreeformProjectType.NS_GENERAL); // NOI18N
217: if (includesEl != null) {
218: includes = p.evaluator().evaluate(
219: Util.findText(includesEl));
220: if (includes.matches("\\$\\{[^}]+\\}")) { // NOI18N
221: // Clearly intended to mean "include everything".
222: includes = null;
223: }
224: }
225: String excludes = null;
226: Element excludesEl = Util.findElement(itemEl,
227: "excludes", FreeformProjectType.NS_GENERAL); // NOI18N
228: if (excludesEl != null) {
229: excludes = p.evaluator().evaluate(
230: Util.findText(excludesEl));
231: }
232: String style = itemEl.getAttribute("style"); // NOI18N
233: for (ProjectNature nature : Lookup.getDefault()
234: .lookupAll(ProjectNature.class)) {
235: if (nature.getSourceFolderViewStyles().contains(
236: style)) {
237: return nature.createSourceFolderView(p, file,
238: includes, excludes, style, location,
239: label);
240: }
241: }
242: if (style.equals("subproject")) { // NOI18N
243: try {
244: Project subproject = ProjectManager
245: .getDefault().findProject(file);
246: if (subproject != null) {
247: return new SubprojectNode(subproject, label);
248: }
249: } catch (IOException x) {
250: Exceptions.printStackTrace(x);
251: }
252: return null;
253: }
254: if (!style.equals("tree")) { // NOI18N
255: Logger
256: .getLogger(
257: FolderNodeFactory.class.getName())
258: .log(
259: Level.WARNING,
260: "Unrecognized <source-folder> style {0} on {1}",
261: new Object[] { style, file });
262: // ... but show it as a tree anyway (at least ViewTest cares)
263: }
264: DataObject fileDO;
265: try {
266: fileDO = DataObject.find(file);
267: } catch (DataObjectNotFoundException e) {
268: throw new AssertionError(e);
269: }
270: return new ViewItemNode((DataFolder) fileDO, includes,
271: excludes, location, label);
272: } else {
273: assert itemEl.getLocalName().equals("source-file") : itemEl; // NOI18N
274: DataObject fileDO;
275: try {
276: fileDO = DataObject.find(file);
277: } catch (DataObjectNotFoundException e) {
278: throw new AssertionError(e);
279: }
280: return new ViewItemNode(fileDO.getNodeDelegate(),
281: location, label);
282: }
283: }
284: }
285:
286: static final class VisibilityQueryDataFilter implements
287: ChangeListener, ChangeableDataFilter {
288:
289: private final ChangeSupport cs = new ChangeSupport(this );
290: private final FileObject root;
291: private final PathMatcher matcher;
292:
293: public VisibilityQueryDataFilter(FileObject root,
294: String includes, String excludes) {
295: this .root = root;
296: matcher = new PathMatcher(includes, excludes, FileUtil
297: .toFile(root));
298: VisibilityQuery.getDefault().addChangeListener(this );
299: }
300:
301: public boolean acceptDataObject(DataObject obj) {
302: FileObject fo = obj.getPrimaryFile();
303: String path = FileUtil.getRelativePath(root, fo);
304: assert path != null : fo + " not in " + root;
305: if (fo.isFolder()) {
306: path += "/"; // NOI18N
307: }
308: if (!matcher.matches(path, true)) {
309: return false;
310: }
311: return VisibilityQuery.getDefault().isVisible(fo);
312: }
313:
314: public void stateChanged(ChangeEvent e) {
315: cs.fireChange();
316: }
317:
318: public void addChangeListener(ChangeListener listener) {
319: cs.addChangeListener(listener);
320: }
321:
322: public void removeChangeListener(ChangeListener listener) {
323: cs.removeChangeListener(listener);
324: }
325:
326: }
327:
328: private static final class ViewItemNode extends FilterNode {
329:
330: private final String name;
331:
332: private final String displayName;
333:
334: public ViewItemNode(Node orig, String name, String displayName) {
335: super (orig);
336: this .name = name;
337: this .displayName = displayName;
338: }
339:
340: public ViewItemNode(DataFolder folder, String includes,
341: String excludes, String name, String displayName) {
342: super (
343: folder.getNodeDelegate(),
344: folder
345: .createNodeChildren(new VisibilityQueryDataFilter(
346: folder.getPrimaryFile(), includes,
347: excludes)));
348: this .name = name;
349: this .displayName = displayName;
350: }
351:
352: public String getName() {
353: return name;
354: }
355:
356: public String getDisplayName() {
357: if (displayName != null) {
358: return displayName;
359: } else {
360: // #50425: show original name incl. annotations
361: return super .getDisplayName();
362: }
363: }
364:
365: public boolean canRename() {
366: return false;
367: }
368:
369: public boolean canDestroy() {
370: return false;
371: }
372:
373: public boolean canCut() {
374: return false;
375: }
376:
377: }
378:
379: private static final class SubprojectNode extends AbstractNode { // #97442
380:
381: private final Project p;
382: private final String label;
383: private final ProjectInformation info;
384:
385: public SubprojectNode(Project p, String label) {
386: super (Children.LEAF, Lookups.singleton(p));
387: this .p = p;
388: this .label = label;
389: info = ProjectUtils.getInformation(p);
390: }
391:
392: @Override
393: public String getName() {
394: return info.getName();
395: }
396:
397: @Override
398: public String getDisplayName() {
399: return label != null ? label : info.getDisplayName();
400: }
401:
402: @Override
403: public Image getIcon(int type) {
404: return Utilities.icon2Image(info.getIcon());
405: }
406:
407: @Override
408: public Image getOpenedIcon(int type) {
409: return getIcon(type);
410: }
411:
412: private static final class OpenProjectAction extends
413: AbstractAction implements ContextAwareAction {
414: public void actionPerformed(ActionEvent ev) {
415: assert false;
416: }
417:
418: public Action createContextAwareInstance(Lookup selection) {
419: final Project[] projects = selection.lookupAll(
420: Project.class).toArray(new Project[0]);
421: return new AbstractAction(SystemAction.get(
422: OpenAction.class).getName()) {
423: public void actionPerformed(ActionEvent ev) {
424: OpenProjects.getDefault().open(projects, false);
425: }
426:
427: @Override
428: public boolean isEnabled() {
429: return !Arrays.asList(
430: OpenProjects.getDefault()
431: .getOpenProjects())
432: .containsAll(Arrays.asList(projects));
433: }
434: };
435: }
436: }
437:
438: private static final Action OPEN = new OpenProjectAction();
439:
440: @Override
441: public Action[] getActions(boolean context) {
442: return new Action[] { OPEN };
443: }
444:
445: }
446:
447: }
|