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.apisupport.project.ui;
043:
044: import java.awt.Dialog;
045: import java.awt.Image;
046: import java.awt.event.ActionEvent;
047: import java.io.File;
048: import java.io.IOException;
049: import java.net.URL;
050: import java.util.ArrayList;
051: import java.util.Arrays;
052: import java.util.Collections;
053: import java.util.List;
054: import java.util.SortedSet;
055: import java.util.TreeSet;
056: import java.util.logging.Level;
057: import java.util.logging.Logger;
058: import javax.swing.AbstractAction;
059: import javax.swing.Action;
060: import javax.swing.Icon;
061: import javax.swing.ImageIcon;
062: import org.netbeans.api.project.Project;
063: import org.netbeans.api.project.ProjectManager;
064: import org.netbeans.api.project.ui.OpenProjects;
065: import org.netbeans.modules.apisupport.project.NbModuleProject;
066: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
067: import org.netbeans.modules.apisupport.project.ProjectXMLManager;
068: import org.netbeans.modules.apisupport.project.Util;
069: import org.netbeans.modules.apisupport.project.ui.ModulesNodeFactory.AddNewLibraryWrapperAction;
070: import org.netbeans.modules.apisupport.project.ui.customizer.AddModulePanel;
071: import org.netbeans.modules.apisupport.project.ui.customizer.EditDependencyPanel;
072: import org.netbeans.modules.apisupport.project.ui.customizer.ModuleDependency;
073: import org.netbeans.modules.apisupport.project.ui.customizer.SingleModuleProperties;
074: import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
075: import org.netbeans.modules.apisupport.project.universe.NbPlatform;
076: import org.netbeans.modules.apisupport.project.universe.TestModuleDependency;
077: import org.netbeans.spi.java.project.support.ui.PackageView;
078: import org.netbeans.spi.project.support.ant.AntProjectEvent;
079: import org.netbeans.spi.project.support.ant.AntProjectHelper;
080: import org.netbeans.spi.project.support.ant.AntProjectListener;
081: import org.openide.DialogDescriptor;
082: import org.openide.DialogDisplayer;
083: import org.openide.ErrorManager;
084: import org.openide.actions.DeleteAction;
085: import org.openide.actions.FindAction;
086: import org.openide.awt.HtmlBrowser;
087: import org.openide.awt.StatusDisplayer;
088: import org.openide.filesystems.FileObject;
089: import org.openide.filesystems.FileUtil;
090: import org.openide.filesystems.URLMapper;
091: import org.openide.nodes.AbstractNode;
092: import org.openide.nodes.Children;
093: import org.openide.nodes.FilterNode;
094: import org.openide.nodes.Node;
095: import org.openide.util.HelpCtx;
096: import org.openide.util.Mutex;
097: import org.openide.util.MutexException;
098: import org.openide.util.NbBundle;
099: import org.openide.util.RequestProcessor;
100: import org.openide.util.Utilities;
101: import org.openide.util.actions.CookieAction;
102: import org.openide.util.actions.SystemAction;
103: import org.openide.util.lookup.Lookups;
104: import org.openide.util.lookup.ProxyLookup;
105:
106: /**
107: * @author Martin Krauskopf
108: */
109: final class LibrariesNode extends AbstractNode {
110:
111: static final String LIBRARIES_NAME = "libraries"; // NOI18N
112:
113: private static final String DISPLAY_NAME = getMessage("LBL_libraries");
114:
115: /** Package private for unit tests only. */
116: static final RequestProcessor RP = new RequestProcessor();
117:
118: private final Action[] actions;
119:
120: public LibrariesNode(final NbModuleProject project) {
121: super (new LibrariesChildren(project));
122: setName(LIBRARIES_NAME);
123: setDisplayName(DISPLAY_NAME);
124: if (Util.getModuleType(project) == NbModuleProvider.SUITE_COMPONENT) {
125: actions = new Action[] {
126: new AddModuleDependencyAction(project),
127: new AddNewLibraryWrapperAction(project, project) };
128: } else {
129: actions = new Action[] { new AddModuleDependencyAction(
130: project), };
131: }
132: }
133:
134: public Image getIcon(int type) {
135: return getIcon(false);
136: }
137:
138: public Image getOpenedIcon(int type) {
139: return getIcon(true);
140: }
141:
142: private Image getIcon(boolean opened) {
143: Image badge = Utilities
144: .loadImage(
145: "org/netbeans/modules/apisupport/project/ui/resources/libraries-badge.png",
146: true);
147: return Utilities.mergeImages(UIUtil.getTreeFolderIcon(opened),
148: badge, 8, 8);
149: }
150:
151: public Action[] getActions(boolean context) {
152: return actions;
153: }
154:
155: private static String createHtmlDescription(
156: final ModuleDependency dep) {
157: // assemble an html short description (tooltip actually)
158: StringBuffer shortDesc = new StringBuffer("<html><u>"
159: + dep.getModuleEntry().getCodeNameBase() + "</u><br>"); // NOI18N
160: if (dep.hasImplementationDepedendency()) {
161: shortDesc.append("<br><font color=\"red\">"
162: + getMessage("CTL_ImplementationDependency")
163: + "</font>");
164: }
165: if (dep.hasCompileDependency()) {
166: shortDesc.append("<br>").append(
167: getMessage("CTL_NeededToCompile"));
168: }
169: if (dep.getReleaseVersion() != null) {
170: shortDesc.append("<br>").append(
171: NbBundle.getMessage(LibrariesNode.class,
172: "CTL_MajorReleaseVersion", dep
173: .getReleaseVersion()));
174: }
175: if (dep.getSpecificationVersion() != null) {
176: shortDesc.append("<br>").append(
177: NbBundle.getMessage(LibrariesNode.class,
178: "CTL_SpecificationVersion", dep
179: .getSpecificationVersion()));
180: }
181: shortDesc.append("</html>"); // NOI18N
182: return shortDesc.toString();
183: }
184:
185: private static String getMessage(String bundleKey) {
186: return NbBundle.getMessage(LibrariesNode.class, bundleKey);
187: }
188:
189: private static final class LibrariesChildren
190: extends
191: Children.Keys<Object/*JDK_PLATFORM_NAME|ModuleDependency*/>
192: implements AntProjectListener {
193:
194: private static final String JDK_PLATFORM_NAME = "jdkPlatform"; // NOI18N
195:
196: private static final String LIBRARIES_ICON = "org/netbeans/modules/apisupport/project/ui/resources/libraries.gif"; // NOI18N
197:
198: private final NbModuleProject project;
199:
200: private ImageIcon librariesIcon;
201:
202: LibrariesChildren(final NbModuleProject project) {
203: this .project = project;
204: }
205:
206: protected void addNotify() {
207: super .addNotify();
208: project.getHelper().addAntProjectListener(this );
209: refreshKeys();
210: }
211:
212: protected void removeNotify() {
213: setKeys(Collections.emptySet());
214: project.getHelper().removeAntProjectListener(this );
215: super .removeNotify();
216: }
217:
218: private void refreshKeys() {
219: // Since miscellaneous operations may be run upon the project.xml
220: // of individual projects we could be called from the same thread
221: // with already acquired ProjectManager.mutex. This could lead to
222: // refreshing during the misconfigurated suite/suite_component
223: // relationship.
224: RP.post(new Runnable() {
225: public void run() {
226: try {
227: ProjectManager.mutex().readAccess(
228: new Mutex.ExceptionAction<Void>() {
229: public Void run()
230: throws IOException {
231: ProjectXMLManager pxm = new ProjectXMLManager(
232: project);
233: final List<Object> keys = new ArrayList<Object>();
234: keys.add(JDK_PLATFORM_NAME);
235: SortedSet<ModuleDependency> deps = new TreeSet<ModuleDependency>(
236: ModuleDependency.LOCALIZED_NAME_COMPARATOR);
237: deps
238: .addAll(pxm
239: .getDirectDependencies());
240: keys.addAll(deps);
241: // XXX still not good when dependency was just edited, since Children use
242: // hashCode/equals (probably HashMap) to find lastly selected node (so neither
243: // SortedSet would help here). Use probably wrapper instead to keep selection.
244: RP.post(new Runnable() {
245: public void run() {
246: setKeys(Collections
247: .unmodifiableList(keys));
248: }
249: });
250: return null;
251: }
252: });
253: } catch (MutexException e) {
254: Logger.getLogger(LibrariesNode.class.getName())
255: .log(Level.FINE, null, e);
256: }
257: }
258: });
259: }
260:
261: protected Node[] createNodes(Object key) {
262: Node node;
263: if (key == JDK_PLATFORM_NAME) {
264: node = PlatformNode.create(project.evaluator(),
265: "nbjdk.home"); // NOI18N
266: } else {
267: ModuleDependency dep = (ModuleDependency) key;
268: File srcF = dep.getModuleEntry().getSourceLocation();
269: if (srcF == null) {
270: File jarF = dep.getModuleEntry().getJarLocation();
271: URL jarRootURL = Util.urlForJar(jarF);
272: assert jarRootURL != null;
273: FileObject root = URLMapper
274: .findFileObject(jarRootURL);
275: ModuleEntry me = dep.getModuleEntry();
276: String name = me.getLocalizedName() + " - "
277: + me.getCodeNameBase(); // NOI18N
278: Icon icon = getLibrariesIcon();
279: Node pvNode = ActionFilterNode
280: .create(PackageView
281: .createPackageView(new LibrariesSourceGroup(
282: root, name, icon, icon)));
283: node = new LibraryDependencyNode(dep, project,
284: pvNode);
285: } else {
286: node = new ProjectDependencyNode(dep, project);
287: }
288: }
289: assert node != null;
290: return new Node[] { node };
291: }
292:
293: public void configurationXmlChanged(AntProjectEvent ev) {
294: // XXX this is a little strange but happens during project move. Bad ordering.
295: // Probably bug in moving implementation (our or in general Project API).
296: if (project.getHelper().resolveFileObject(
297: AntProjectHelper.PROJECT_XML_PATH) != null) {
298: refreshKeys();
299: }
300: }
301:
302: public void propertiesChanged(AntProjectEvent ev) {
303: // do not need
304: }
305:
306: /*
307: private Node getNodeDelegate(final File jarF) {
308: Node n = null;
309: assert jarF != null;
310: FileObject jar = FileUtil.toFileObject(jarF);
311: if (jarF != null) {
312: DataObject dobj;
313: try {
314: dobj = DataObject.find(jar);
315: if (dobj != null) {
316: n = dobj.getNodeDelegate();
317: }
318: } catch (DataObjectNotFoundException e) {
319: assert false : e;
320: }
321: }
322: return n;
323: }
324: */
325:
326: private Icon getLibrariesIcon() {
327: if (librariesIcon == null) {
328: librariesIcon = new ImageIcon(Utilities.loadImage(
329: LIBRARIES_ICON, true));
330: }
331: return librariesIcon;
332: }
333:
334: }
335:
336: private static final class ProjectDependencyNode extends
337: AbstractNode {
338:
339: private final ModuleDependency dep;
340: private final NbModuleProject project;
341:
342: ProjectDependencyNode(final ModuleDependency dep,
343: final NbModuleProject project) {
344: super (Children.LEAF, Lookups.fixed(dep, project, dep
345: .getModuleEntry()));
346: this .dep = dep;
347: this .project = project;
348: ModuleEntry me = dep.getModuleEntry();
349: setIconBaseWithExtension(NbModuleProject.NB_PROJECT_ICON_PATH);
350: setDisplayName(me.getLocalizedName());
351: setShortDescription(LibrariesNode
352: .createHtmlDescription(dep));
353: }
354:
355: public Action[] getActions(boolean context) {
356: return new Action[] {
357: SystemAction.get(OpenProjectAction.class),
358: new EditDependencyAction(dep, project),
359: new ShowJavadocAction(dep, project),
360: SystemAction.get(RemoveAction.class), };
361: }
362:
363: public Action getPreferredAction() {
364: return getActions(false)[0]; // open
365: }
366:
367: public boolean canDestroy() {
368: return true;
369: }
370:
371: public void destroy() throws IOException {
372: removeDependency(project, dep);
373: }
374:
375: }
376:
377: private static final class LibraryDependencyNode extends FilterNode {
378:
379: private final ModuleDependency dep;
380: private final NbModuleProject project;
381:
382: LibraryDependencyNode(final ModuleDependency dep,
383: final NbModuleProject project, final Node original) {
384: super (original, null, new ProxyLookup(original.getLookup(),
385: Lookups.fixed(dep, project)));
386: this .dep = dep;
387: this .project = project;
388: setShortDescription(LibrariesNode
389: .createHtmlDescription(dep));
390: }
391:
392: public Action[] getActions(boolean context) {
393: return new Action[] {
394: new EditDependencyAction(dep, project),
395: SystemAction.get(FindAction.class),
396: new ShowJavadocAction(dep, project),
397: SystemAction.get(RemoveAction.class), };
398: }
399:
400: public Action getPreferredAction() {
401: return new EditDependencyAction(dep, project);
402: }
403:
404: public boolean canDestroy() {
405: return true;
406: }
407:
408: public void destroy() throws IOException {
409: removeDependency(project, dep);
410: }
411:
412: }
413:
414: private static void removeDependency(NbModuleProject project,
415: ModuleDependency dep) throws IOException {
416: new ProjectXMLManager(project).removeDependencies(Collections
417: .singleton(dep));
418: ProjectManager.getDefault().saveProject(project);
419: }
420:
421: private static final class AddModuleDependencyAction extends
422: AbstractAction {
423:
424: private final NbModuleProject project;
425:
426: AddModuleDependencyAction(final NbModuleProject project) {
427: super (getMessage("CTL_AddModuleDependency"));
428: this .project = project;
429: }
430:
431: public void actionPerformed(ActionEvent ev) {
432: SingleModuleProperties props = SingleModuleProperties
433: .getInstance(project);
434: ModuleDependency[] newDeps = AddModulePanel
435: .selectDependencies(props);
436: ProjectXMLManager pxm = new ProjectXMLManager(project);
437: try {
438: for (ModuleDependency dep : newDeps) {
439: pxm.addDependency(dep);
440: }
441: ProjectManager.getDefault().saveProject(project);
442: } catch (IOException e) {
443: ErrorManager.getDefault().annotate(
444: e,
445: "Cannot add selected dependencies: "
446: + Arrays.asList(newDeps)); // NOI18N
447: ErrorManager.getDefault().notify(e);
448: }
449: }
450:
451: }
452:
453: private static final class EditDependencyAction extends
454: AbstractAction {
455:
456: private final ModuleDependency dep;
457: private final NbModuleProject project;
458:
459: EditDependencyAction(final ModuleDependency dep,
460: final NbModuleProject project) {
461: super (getMessage("CTL_EditDependency"));
462: this .dep = dep;
463: this .project = project;
464: }
465:
466: public void actionPerformed(ActionEvent ev) {
467: // XXX duplicated from CustomizerLibraries --> Refactor
468: EditDependencyPanel editPanel = new EditDependencyPanel(
469: dep, NbPlatform.getPlatformByDestDir(dep
470: .getModuleEntry().getDestDir()));
471: DialogDescriptor descriptor = new DialogDescriptor(
472: editPanel, NbBundle.getMessage(LibrariesNode.class,
473: "CTL_EditModuleDependencyTitle", dep
474: .getModuleEntry()
475: .getLocalizedName()));
476: descriptor
477: .setHelpCtx(new HelpCtx(EditDependencyPanel.class));
478: Dialog d = DialogDisplayer.getDefault().createDialog(
479: descriptor);
480: d.setVisible(true);
481: if (descriptor.getValue()
482: .equals(DialogDescriptor.OK_OPTION)) {
483: ModuleDependency editedDep = editPanel
484: .getEditedDependency();
485: try {
486: ProjectXMLManager pxm = new ProjectXMLManager(
487: project);
488: SortedSet<ModuleDependency> deps = new TreeSet<ModuleDependency>(
489: pxm.getDirectDependencies());
490: deps.remove(dep);
491: deps.add(editedDep);
492: pxm.replaceDependencies(deps);
493: ProjectManager.getDefault().saveProject(project);
494: } catch (IOException e) {
495: ErrorManager.getDefault().annotate(e,
496: "Cannot store dependency: " + editedDep); // NOI18N
497: ErrorManager.getDefault().notify(e);
498: }
499: }
500: d.dispose();
501: }
502:
503: }
504:
505: private static final class ShowJavadocAction extends AbstractAction {
506:
507: private final ModuleDependency dep;
508: private final NbModuleProject project;
509:
510: private URL currectJavadoc;
511:
512: ShowJavadocAction(final ModuleDependency dep,
513: final NbModuleProject project) {
514: super (getMessage("CTL_ShowJavadoc"));
515: this .dep = dep;
516: this .project = project;
517: }
518:
519: public void actionPerformed(ActionEvent ev) {
520: HtmlBrowser.URLDisplayer.getDefault().showURL(
521: currectJavadoc);
522: }
523:
524: public boolean isEnabled() {
525: if (Util.getModuleType(project) == NbModuleProvider.NETBEANS_ORG) {
526: currectJavadoc = Util
527: .findJavadocForNetBeansOrgModules(dep);
528: } else {
529: currectJavadoc = Util.findJavadoc(dep, project
530: .getPlatform(true));
531: }
532: return currectJavadoc != null;
533: }
534:
535: }
536:
537: static final class OpenProjectAction extends CookieAction {
538:
539: protected void performAction(Node[] activatedNodes) {
540: try {
541: final Project[] projects = new Project[activatedNodes.length];
542: for (int i = 0; i < activatedNodes.length; i++) {
543: ModuleEntry me = activatedNodes[i].getLookup()
544: .lookup(ModuleEntry.class);
545: assert me != null;
546: File prjDir = me.getSourceLocation();
547: assert prjDir != null;
548: Project project = ProjectManager.getDefault()
549: .findProject(FileUtil.toFileObject(prjDir));
550: assert project != null;
551: projects[i] = project;
552: }
553: RequestProcessor.getDefault().post(new Runnable() {
554: public void run() {
555: StatusDisplayer.getDefault().setStatusText(
556: getMessage("MSG_OpeningProjects"));
557: OpenProjects.getDefault().open(projects, false);
558: }
559: });
560: } catch (IOException e) {
561: assert false : e;
562: }
563: }
564:
565: public boolean isEnabled() {
566: return true;
567: }
568:
569: public String getName() {
570: return getMessage("CTL_Open");
571: }
572:
573: public HelpCtx getHelpCtx() {
574: return HelpCtx.DEFAULT_HELP;
575: }
576:
577: protected boolean asynchronous() {
578: return false;
579: }
580:
581: protected int mode() {
582: return CookieAction.MODE_ALL;
583: }
584:
585: protected Class[] cookieClasses() {
586: return new Class[] { ModuleDependency.class,
587: TestModuleDependency.class };
588: }
589:
590: }
591:
592: private static final class RemoveAction extends DeleteAction {
593:
594: public String getName() {
595: return getMessage("CTL_RemoveDependency");
596: }
597:
598: protected void initialize() {
599: super .initialize();
600: putValue(Action.ACCELERATOR_KEY, SystemAction.get(
601: DeleteAction.class)
602: .getValue(Action.ACCELERATOR_KEY));
603: }
604:
605: }
606:
607: }
|