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-2006 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.mobility.project.ui;
043:
044: import java.io.CharConversionException;
045: import java.beans.PropertyChangeListener;
046: import java.beans.PropertyChangeEvent;
047: import java.util.HashSet;
048: import java.util.Iterator;
049: import org.netbeans.api.project.*;
050: import org.netbeans.api.project.libraries.LibraryManager;
051: import org.netbeans.spi.project.ProjectConfiguration;
052: import org.netbeans.modules.mobility.project.J2MEActionProvider;
053: import org.netbeans.modules.mobility.project.J2MEProject;
054: import org.netbeans.modules.mobility.project.ProjectConfigurationsHelper;
055: import org.netbeans.modules.mobility.project.ui.customizer.J2MEProjectProperties;
056: import org.netbeans.modules.mobility.project.DefaultPropertiesDescriptor;
057: import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport;
058: import org.netbeans.spi.java.project.support.ui.PackageView;
059: import org.netbeans.api.java.platform.JavaPlatformManager;
060: import org.netbeans.spi.project.ActionProvider;
061: import org.netbeans.spi.project.support.ant.AntProjectEvent;
062: import org.netbeans.spi.project.support.ant.AntProjectHelper;
063: import org.netbeans.spi.project.support.ant.AntProjectListener;
064: import org.netbeans.spi.project.support.ant.ReferenceHelper;
065: import org.netbeans.spi.project.ui.LogicalViewProvider;
066: import org.netbeans.spi.project.ui.support.CommonProjectActions;
067: import org.netbeans.spi.project.ui.support.ProjectSensitiveActions;
068: import org.openide.ErrorManager;
069: import org.openide.actions.FindAction;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileStateInvalidException;
072: import org.openide.filesystems.FileStatusEvent;
073: import org.openide.filesystems.FileStatusListener;
074: import org.openide.filesystems.FileSystem;
075: import org.openide.filesystems.FileUtil;
076: import org.openide.filesystems.Repository;
077: import org.openide.loaders.DataFolder;
078: import org.openide.loaders.DataObject;
079: import org.openide.loaders.DataObjectNotFoundException;
080: import org.openide.loaders.FolderLookup;
081: import org.openide.nodes.AbstractNode;
082: import org.openide.nodes.Children;
083: import org.openide.nodes.Node;
084: import org.openide.util.HelpCtx;
085: import org.openide.util.Lookup;
086: import org.openide.util.NbBundle;
087: import org.openide.util.RequestProcessor;
088: import org.openide.util.RequestProcessor.Task;
089: import org.openide.util.Utilities;
090: import org.openide.util.actions.SystemAction;
091: import org.openide.util.lookup.ProxyLookup;
092: import javax.swing.*;
093: import java.awt.*;
094: import java.awt.event.ActionEvent;
095: import java.util.ArrayList;
096: import java.util.Collection;
097: import java.util.Collections;
098: import java.util.Enumeration;
099: import java.util.HashMap;
100: import java.util.ResourceBundle;
101: import org.openide.filesystems.FileUtil;
102: import org.openide.util.WeakListeners;
103: import org.openide.util.lookup.Lookups;
104: import org.openide.xml.XMLUtil;
105:
106: /**
107: * Support for creating logical views.
108: * @author Petr Hrebejk, Adam Sotona
109: */
110: public class J2MEPhysicalViewProvider implements LogicalViewProvider {
111:
112: protected final ReferenceHelper refHelper;
113: protected final ProjectConfigurationsHelper pcp;
114: protected final AntProjectHelper helper;
115: protected final J2MEProject project;
116: J2MEProjectRootNode rootNode;
117:
118: public J2MEPhysicalViewProvider(Project project,
119: AntProjectHelper helper, ReferenceHelper refHelper,
120: ProjectConfigurationsHelper pcp) {
121: this .project = (J2MEProject) project;
122: assert project != null;
123: this .helper = helper;
124: assert helper != null;
125: this .refHelper = refHelper;
126: assert refHelper != null;
127: this .pcp = pcp;
128: assert pcp != null;
129: }
130:
131: public Node createLogicalView() {
132: try {
133: return rootNode = new J2MEProjectRootNode();
134: } catch (Exception e) {
135: ErrorManager.getDefault().notify(e);
136: return Node.EMPTY;
137: }
138: }
139:
140: public Node findPath(final Node root, final Object target) {
141: final Project project = root.getLookup().lookup(Project.class);
142: if (project == null) {
143: return null;
144: }
145: if (target instanceof FileObject) {
146: final FileObject fo = (FileObject) target;
147: final Project owner = FileOwnerQuery.getOwner(fo);
148: if (!project.equals(owner)) {
149: return null; // Don't waste time if project does not own the fo
150: }
151:
152: for (Node n : root.getChildren().getNodes(true)) {
153: Node result = PackageView.findPath(n, target);
154: if (result != null) {
155: return result;
156: }
157: }
158: }
159: return null;
160: }
161:
162: protected String[] getBreakableProperties() {
163: final ProjectConfiguration pc[] = pcp.getConfigurations()
164: .toArray(new ProjectConfiguration[0]);
165: String s[] = new String[2 * pc.length + 1];
166: s[0] = DefaultPropertiesDescriptor.SRC_DIR;
167: for (int i = 0; i < pc.length; i++) {
168: if (pcp.getDefaultConfiguration().equals(pc[i])) {
169: s[2 * i + 1] = DefaultPropertiesDescriptor.LIBS_CLASSPATH;
170: s[2 * i + 2] = DefaultPropertiesDescriptor.SIGN_KEYSTORE;
171: } else {
172: s[2 * i + 1] = J2MEProjectProperties.CONFIG_PREFIX
173: + pc[i].getDisplayName() + "."
174: + DefaultPropertiesDescriptor.LIBS_CLASSPATH; //NOI18N
175: s[2 * i + 2] = J2MEProjectProperties.CONFIG_PREFIX
176: + pc[i].getDisplayName() + "."
177: + DefaultPropertiesDescriptor.SIGN_KEYSTORE; //NOI18N
178: }
179: }
180: return s;
181: }
182:
183: protected String[] getBreakablePlatformProperties() {
184: final ProjectConfiguration pc[] = pcp.getConfigurations()
185: .toArray(new ProjectConfiguration[0]);
186: String s[] = new String[pc.length];
187: for (int i = 0; i < pc.length; i++) {
188: if (pcp.getDefaultConfiguration().equals(pc[i])) {
189: s[i] = DefaultPropertiesDescriptor.PLATFORM_ACTIVE;
190: } else {
191: s[i] = J2MEProjectProperties.CONFIG_PREFIX
192: + pc[i].getDisplayName() + "."
193: + DefaultPropertiesDescriptor.PLATFORM_ACTIVE; //NOI18N
194: }
195: }
196: return s;
197: }
198:
199: public void refreshNode(String name) {
200: if (rootNode != null) {
201: LogicalViewChildren children = (LogicalViewChildren) rootNode
202: .getChildren();
203: children.refreshNode(name);
204: rootNode.checkBroken();
205: }
206: }
207:
208: public boolean hasBrokenLinks() {
209: return BrokenReferencesSupport.isBroken(helper, refHelper,
210: getBreakableProperties(),
211: getBreakablePlatformProperties());
212: }
213:
214: // Common class for all nodes in our project
215: abstract static class ChildLookup extends ProxyLookup {
216: abstract public Node[] createNodes(J2MEProject project);
217: }
218:
219: // Private innerclasses ----------------------------------------------------
220: final class LogicalViewChildren extends Children.Keys {
221: final private J2MEProject project;
222: final private NodeCache cache;
223: final private HashMap<String, ChildLookup> keyMap = new HashMap<String, ChildLookup>();
224:
225: private class CfgListener implements PropertyChangeListener,
226: Runnable {
227: public void propertyChange(PropertyChangeEvent evt) {
228: RequestProcessor.getDefault().post(this );
229: }
230:
231: public void run() {
232: refreshResources();
233: }
234: }
235:
236: LogicalViewChildren(J2MEProject proj) {
237: project = proj;
238: cache = new NodeCache(proj);
239: keyMap.put("Sources", new SourcesViewProvider());
240: keyMap.put("Resources", new ResViewProvider(cache));
241: keyMap.put("Configurations", new LibResViewProvider(cache));
242: project.getConfigurationHelper().addPropertyChangeListener(
243: new CfgListener());
244: setKeys(getKeys());
245: }
246:
247: public void refreshResources() {
248: refreshKey("Resources");
249: }
250:
251: public void refreshConfigurations() {
252: refreshKey("Configurations");
253: }
254:
255: public void refreshNode(String name) {
256: cache.update(name);
257: }
258:
259: protected Node[] createNodes(final Object key) {
260: ChildLookup creator = keyMap.get(key);
261: return creator != null ? creator.createNodes(project)
262: : null;
263: }
264:
265: private Collection<String> getKeys() {
266: //#60800, #61584 - when the project is deleted externally do not try to create children, the source groups
267: //are not valid
268: if (project.getProjectDirectory() == null
269: || !project.getProjectDirectory().isValid()) {
270: return Collections.EMPTY_LIST;
271: }
272: final java.util.List<String> result = new java.util.ArrayList<String>();
273: result.add("Sources");
274: result.add("Resources");
275: result.add("Configurations");
276: return result;
277: }
278: }
279:
280: /** Filter node containin additional features for the J2ME physical
281: */
282: final class J2MEProjectRootNode extends AbstractNode implements
283: AntProjectListener, PropertyChangeListener,
284: FileStatusListener, Runnable {
285:
286: private Action[] actions, actionsBroken;
287:
288: boolean broken;
289: Image icon;
290: final Task nodeUpdateTask;
291: PropertyChangeListener ref1, ref3;
292: FileStatusListener ref2;
293:
294: public J2MEProjectRootNode() {
295: super (new LogicalViewChildren(project), Lookups
296: .singleton(project));
297: this .broken = hasBrokenLinks();
298: this .nodeUpdateTask = RequestProcessor.getDefault().create(
299: this );
300: setName(ProjectUtils.getInformation(project)
301: .getDisplayName());
302: helper.addAntProjectListener(this );
303: this .ref1 = WeakListeners.propertyChange(this ,
304: JavaPlatformManager.getDefault());
305: this .ref3 = WeakListeners.propertyChange(this ,
306: LibraryManager.getDefault());
307: LibraryManager.getDefault().addPropertyChangeListener(ref3);
308: JavaPlatformManager.getDefault().addPropertyChangeListener(
309: ref1);
310: try {
311: FileSystem fs = helper.getProjectDirectory()
312: .getFileSystem();
313: this .ref2 = FileUtil.weakFileStatusListener(this , fs);
314: fs.addFileStatusListener(ref2);
315: } catch (FileStateInvalidException fsie) {
316: ErrorManager.getDefault().notify(fsie);
317: }
318: }
319:
320: protected boolean testSourceRoot() {
321: return helper.resolveFileObject(helper
322: .getStandardPropertyEvaluator().getProperty(
323: "src.dir")) != null;
324: }
325:
326: protected void checkBroken() {
327: nodeUpdateTask.schedule(50);
328: }
329:
330: public void run() {
331: boolean br = hasBrokenLinks();
332: boolean changed = false;
333: synchronized (J2MEProjectRootNode.this ) {
334: if (broken != br) {
335: broken ^= true; //faster way of negation
336: changed = true;
337: }
338: }
339: if (changed) {
340: icon = createIcon();
341: }
342: fireIconChange();
343: fireOpenedIconChange();
344: fireDisplayNameChange(null, null);
345: }
346:
347: protected boolean isBroken() {
348: return hasBrokenLinks();
349: }
350:
351: public boolean canCopy() {
352: return false;
353: }
354:
355: public boolean canRename() {
356: return false;
357: }
358:
359: public boolean canCut() {
360: return false;
361: }
362:
363: public boolean canDestroy() {
364: return false;
365: }
366:
367: protected Image createIcon() {
368: final Image icon = Utilities
369: .loadImage(
370: "org/netbeans/modules/mobility/project/ui/resources/mobile-project.png",
371: true); // NOI18N
372: return broken ? Utilities
373: .mergeImages(
374: icon,
375: Utilities
376: .loadImage("org/netbeans/modules/mobility/project/ui/resources/brokenProjectBadge.gif"),
377: 8, 0)
378: : icon; //NOI18N
379: }
380:
381: public Image getIcon(final int type) {
382: if (icon == null) {
383: icon = createIcon();
384: }
385: final Sources src = ProjectUtils.getSources(project);
386: if (src != null) {
387: HashSet<FileObject> files = new HashSet();
388: for (SourceGroup sg : src
389: .getSourceGroups(Sources.TYPE_GENERIC))
390: files.add(sg.getRootFolder());
391: try {
392: final FileSystem.Status ann = helper
393: .getProjectDirectory().getFileSystem()
394: .getStatus();
395: return ann.annotateIcon(icon, type, files);
396: } catch (FileStateInvalidException fsie) {
397: ErrorManager.getDefault().notify(fsie);
398: }
399: }
400: return icon;
401: }
402:
403: public Image getOpenedIcon(final int type) {
404: return getIcon(type);
405: }
406:
407: public String getHtmlDisplayName() {
408: String dispName = super .getDisplayName();
409: try {
410: dispName = XMLUtil.toElementContent(dispName);
411: } catch (CharConversionException ex) {
412: // ignore
413: }
414: return broken ? "<font color=\"#A40000\">" + dispName
415: + "</font>" : null; //NOI18N
416: }
417:
418: public Node.PropertySet[] getPropertySets() {
419: return new Node.PropertySet[0];
420: }
421:
422: public HelpCtx getHelpCtx() {
423: return new HelpCtx(
424: J2MEPhysicalViewProvider.J2MEProjectRootNode.class);
425: }
426:
427: public synchronized Action[] getActions(final boolean context) {
428: if (context)
429: return new Action[0];
430: if (actions == null) {
431: final ArrayList<Action> act = new ArrayList<Action>();
432: final ResourceBundle bundle = NbBundle
433: .getBundle(J2MEPhysicalViewProvider.class);
434: act.add(CommonProjectActions.newFileAction());
435: act.add(null);
436: act.add(ProjectSensitiveActions.projectCommandAction(
437: ActionProvider.COMMAND_BUILD, bundle
438: .getString("LBL_BuildAction_Name"),
439: null)); // NOI18N
440: act.add(ProjectSensitiveActions.projectCommandAction(
441: ActionProvider.COMMAND_REBUILD, bundle
442: .getString("LBL_RebuildAction_Name"),
443: null)); // NOI18N
444: act.add(ProjectSensitiveActions.projectCommandAction(
445: ActionProvider.COMMAND_CLEAN, bundle
446: .getString("LBL_CleanAction_Name"),
447: null)); // NOI18N
448: act.add(ProjectSensitiveActions.projectCommandAction(
449: J2MEActionProvider.COMMAND_JAVADOC, bundle
450: .getString("LBL_JavadocAction_Name"),
451: null)); // NOI18N
452: act.add(ProjectSensitiveActions.projectCommandAction(
453: J2MEActionProvider.COMMAND_DEPLOY, bundle
454: .getString("LBL_DeployAction_Name"),
455: null)); // NOI18N
456: act.add(null);
457: act.add(ProjectSensitiveActions.projectCommandAction(
458: J2MEActionProvider.COMMAND_BUILD_ALL, bundle
459: .getString("LBL_BuildAllAction_Name"),
460: null)); // NOI18N
461: act.add(ProjectSensitiveActions.projectCommandAction(
462: J2MEActionProvider.COMMAND_REBUILD_ALL,
463: bundle.getString("LBL_RebuildAllAction_Name"),
464: null)); // NOI18N
465: act.add(ProjectSensitiveActions.projectCommandAction(
466: J2MEActionProvider.COMMAND_CLEAN_ALL, bundle
467: .getString("LBL_CleanAllAction_Name"),
468: null)); // NOI18N
469: act.add(ProjectSensitiveActions.projectCommandAction(
470: J2MEActionProvider.COMMAND_DEPLOY_ALL, bundle
471: .getString("LBL_DeployAllAction_Name"),
472: null)); // NOI18N
473: act.add(null);
474: act
475: .add(ProjectSensitiveActions
476: .projectCommandAction(
477: ActionProvider.COMMAND_RUN,
478: bundle
479: .getString("LBL_RunAction_Name"),
480: null)); // NOI18N
481: act.add(ProjectSensitiveActions.projectCommandAction(
482: J2MEActionProvider.COMMAND_RUN_WITH, bundle
483: .getString("LBL_RunWithAction_Name"),
484: null)); // NOI18N
485: act.add(ProjectSensitiveActions.projectCommandAction(
486: ActionProvider.COMMAND_DEBUG, bundle
487: .getString("LBL_DebugAction_Name"),
488: null)); // NOI18N
489: act.add(null);
490: act.add(CommonProjectActions.setAsMainProjectAction());
491: act.add(CommonProjectActions.openSubprojectsAction());
492: act.add(CommonProjectActions.closeProjectAction());
493: act.add(null);
494: act.add(CommonProjectActions.renameProjectAction());
495: act.add(CommonProjectActions.moveProjectAction());
496: act.add(CommonProjectActions.copyProjectAction());
497: act.add(CommonProjectActions.deleteProjectAction());
498: act.add(null);
499: act.add(SystemAction.get(FindAction.class));
500: act.add(null);
501: act.add(new RefreshPackagesAction());
502: act.add(null);
503: // honor 57874 contact
504:
505: try {
506: final Repository repository = Repository
507: .getDefault();
508: final FileSystem sfs = repository
509: .getDefaultFileSystem();
510: final FileObject fo = sfs
511: .findResource("Projects/Actions"); // NOI18N
512: if (fo != null) {
513: final DataObject dobj = DataObject.find(fo);
514: final FolderLookup actionRegistry = new FolderLookup(
515: (DataFolder) dobj);
516: final Lookup.Template<Object> query = new Lookup.Template<Object>(
517: Object.class);
518: final Lookup lookup = actionRegistry
519: .getLookup();
520: final Iterator it = lookup.lookup(query)
521: .allInstances().iterator();
522: if (it.hasNext()) {
523: act.add(null);
524: }
525: while (it.hasNext()) {
526: final Object next = it.next();
527: if (next instanceof Action) {
528: act.add((Action) next);
529: } else if (next instanceof JSeparator) {
530: act.add(null);
531: }
532: }
533: }
534: } catch (DataObjectNotFoundException ex) {
535: // data folder for exitinf fileobject expected
536: ErrorManager.getDefault().notify(ex);
537: }
538:
539: act.add(null);
540: act.add(CommonProjectActions.customizeProjectAction());
541: actions = act.toArray(new Action[act.size()]);
542: act.add(act.size() - 1, createBrokenLinksAction());
543: actionsBroken = act.toArray(new Action[act.size()]);
544: }
545: return broken ? actionsBroken : actions;
546: }
547:
548: private Action createBrokenLinksAction() {
549: final Action action = new AbstractAction() {
550: public void actionPerformed(@SuppressWarnings("unused")
551: ActionEvent e) {
552: // here is required list of all platforms, not just the default one !!!!!!!!!!!
553: BrokenReferencesSupport.showCustomizer(helper,
554: refHelper, getBreakableProperties(),
555: getBreakablePlatformProperties());
556: checkBroken();
557: }
558: };
559: action.putValue(Action.NAME, NbBundle.getMessage(
560: J2MEPhysicalViewProvider.class,
561: "LAB_ResolveReferenceProblems")); //NOI18N
562: return action;
563: }
564:
565: public void configurationXmlChanged(AntProjectEvent ev) {
566: }
567:
568: public void propertiesChanged(AntProjectEvent ev) {
569: checkBroken();
570: }
571:
572: public void propertyChange(PropertyChangeEvent evt) {
573: checkBroken();
574: }
575:
576: public void annotationChanged(FileStatusEvent ev) {
577: checkBroken();
578: }
579:
580: }
581:
582: private class RefreshPackagesAction extends AbstractAction {
583:
584: public RefreshPackagesAction() {
585: super (NbBundle.getMessage(J2MEPhysicalViewProvider.class,
586: "LAB_RefreshFolders")); //NOI18N
587: }
588:
589: public void actionPerformed(@SuppressWarnings("unused")
590: final ActionEvent e) {
591: refreshRecursivelly(helper.resolveFileObject(helper
592: .getStandardPropertyEvaluator().getProperty(
593: "src.dir")));//NOI18N
594: }
595:
596: private void refreshRecursivelly(final FileObject fo) {
597: if (fo == null)
598: return;
599: fo.refresh();
600: final Enumeration en = fo.getChildren(false);
601: while (en.hasMoreElements()) {
602: refreshRecursivelly((FileObject) en.nextElement());
603: }
604: }
605: }
606: }
|