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.apache.tools.ant.module.nodes;
043:
044: import java.awt.Toolkit;
045: import java.awt.event.ActionEvent;
046: import java.io.IOException;
047: import javax.swing.AbstractAction;
048: import javax.swing.Action;
049: import javax.swing.UIManager;
050: import javax.swing.event.ChangeEvent;
051: import javax.swing.event.ChangeListener;
052: import javax.swing.text.StyledDocument;
053: import javax.xml.parsers.SAXParser;
054: import javax.xml.parsers.SAXParserFactory;
055: import org.apache.tools.ant.module.AntModule;
056: import org.apache.tools.ant.module.api.AntProjectCookie;
057: import org.apache.tools.ant.module.api.support.TargetLister;
058: import org.apache.tools.ant.module.run.TargetExecutor;
059: import org.apache.tools.ant.module.wizards.shortcut.ShortcutWizard;
060: import org.apache.tools.ant.module.xml.AntProjectSupport;
061: import org.openide.ErrorManager;
062: import org.openide.actions.OpenAction;
063: import org.openide.actions.PropertiesAction;
064: import org.openide.cookies.EditorCookie;
065: import org.openide.cookies.LineCookie;
066: import org.openide.cookies.OpenCookie;
067: import org.openide.filesystems.FileObject;
068: import org.openide.loaders.DataObject;
069: import org.openide.loaders.DataObjectNotFoundException;
070: import org.openide.nodes.AbstractNode;
071: import org.openide.nodes.Children;
072: import org.openide.nodes.PropertySupport;
073: import org.openide.nodes.Sheet;
074: import org.openide.text.Line;
075: import org.openide.util.NbBundle;
076: import org.openide.util.WeakListeners;
077: import org.openide.util.actions.SystemAction;
078: import org.xml.sax.Attributes;
079: import org.xml.sax.InputSource;
080: import org.xml.sax.Locator;
081: import org.xml.sax.SAXException;
082: import org.xml.sax.helpers.DefaultHandler;
083:
084: final class AntTargetNode extends AbstractNode implements
085: ChangeListener {
086:
087: /** main project, not necessarily the one defining this target */
088: private final AntProjectCookie project;
089: private final TargetLister.Target target;
090:
091: /**
092: * Create a new target node.
093: * @param project the <em>main</em> project with this target (may not be the project this is physically part of)
094: * @param target a representation of this target
095: * @param allTargets all other targets in the main project
096: */
097: public AntTargetNode(AntProjectCookie project,
098: TargetLister.Target target) {
099: super (Children.LEAF);
100: this .project = project;
101: assert !target.isOverridden() : "Cannot include overridden targets";
102: this .target = target;
103: target.getScript().addChangeListener(
104: WeakListeners.change(this , target.getScript()));
105: setName(target.getQualifiedName());
106: setDisplayName(target.getName());
107: if (target.isDescribed()) {
108: setShortDescription(target.getElement().getAttribute(
109: "description")); // NOI18N
110: setIconBaseWithExtension("org/apache/tools/ant/module/resources/EmphasizedTargetIcon.gif");
111: } else if (target.isDefault()) {
112: setIconBaseWithExtension("org/apache/tools/ant/module/resources/EmphasizedTargetIcon.gif");
113: } else {
114: setIconBaseWithExtension("org/apache/tools/ant/module/resources/TargetIcon.gif");
115: }
116: getCookieSet().add(new TargetOpenCookie(target));
117: }
118:
119: private static String internalTargetColor = null;
120:
121: /** Loosely copied from VcsFileSystem.annotateNameHtml */
122: private static synchronized String getInternalTargetColor() {
123: if (internalTargetColor == null) {
124: if (UIManager.getDefaults().getColor(
125: "Tree.selectionBackground").equals(
126: UIManager.getDefaults().getColor("controlShadow"))) { // NOI18N
127: internalTargetColor = "Tree.selectionBorderColor"; // NOI18N
128: } else {
129: internalTargetColor = "controlShadow"; // NOI18N
130: }
131: }
132: return internalTargetColor;
133: }
134:
135: @Override
136: public String getHtmlDisplayName() {
137: // Use markup to indicate the default target, imported targets, and internal targets.
138: boolean imported = target.getScript() != project;
139: if (!imported && !target.isDefault() && !target.isInternal()) {
140: return null;
141: }
142: StringBuffer name = new StringBuffer(target.getName());
143: if (imported) {
144: name.insert(0, "<i>"); // NOI18N
145: name.append("</i>"); // NOI18N
146: }
147: if (target.isDefault()) {
148: name.insert(0, "<b>"); // NOI18N
149: name.append("</b>"); // NOI18N
150: }
151: if (target.isInternal()) {
152: name.insert(0, "'>"); // NOI18N
153: name.insert(0, getInternalTargetColor());
154: name.insert(0, "<font color='!"); // NOI18N
155: name.append("</font>"); // NOI18N
156: }
157: return name.toString();
158: }
159:
160: public void stateChanged(ChangeEvent ev) {
161: firePropertyChange(null, null, null);
162: }
163:
164: @Override
165: public boolean canDestroy() {
166: return false;
167: }
168:
169: @Override
170: public boolean canRename() {
171: return false;
172: }
173:
174: @Override
175: public boolean canCopy() {
176: return true;
177: }
178:
179: @Override
180: public boolean canCut() {
181: return false;
182: }
183:
184: private final Action EXECUTE = new ExecuteAction();
185: private final Action CREATE_SHORTCUT = new CreateShortcutAction();
186:
187: @Override
188: public Action[] getActions(boolean context) {
189: if (!target.isInternal()) {
190: return new Action[] { SystemAction.get(OpenAction.class),
191: null, EXECUTE, CREATE_SHORTCUT, null,
192: SystemAction.get(PropertiesAction.class), };
193: } else {
194: return new Action[] { SystemAction.get(OpenAction.class),
195: null, SystemAction.get(PropertiesAction.class), };
196: }
197: }
198:
199: @Override
200: public Action getPreferredAction() {
201: return SystemAction.get(OpenAction.class);
202: }
203:
204: private final class ExecuteAction extends AbstractAction {
205:
206: ExecuteAction() {
207: super (NbBundle.getMessage(AntTargetNode.class,
208: "LBL_execute_target"));
209: }
210:
211: public void actionPerformed(ActionEvent e) {
212: try {
213: TargetExecutor te = new TargetExecutor(project,
214: new String[] { target.getName() });
215: te.execute();
216: } catch (IOException ioe) {
217: AntModule.err.notify(ioe);
218: }
219: }
220:
221: }
222:
223: /**
224: * Action to invoke the target shortcut wizard.
225: * Used to be a "template", but this is more natural.
226: * @see "issue #37374"
227: */
228: private final class CreateShortcutAction extends AbstractAction {
229:
230: CreateShortcutAction() {
231: super (NbBundle.getMessage(AntTargetNode.class,
232: "LBL_create_shortcut"));
233: }
234:
235: public void actionPerformed(ActionEvent e) {
236: ShortcutWizard.show(project, target.getElement());
237: }
238:
239: }
240:
241: @Override
242: protected Sheet createSheet() {
243: Sheet sheet = super .createSheet();
244: Sheet.Set props = sheet.get(Sheet.PROPERTIES);
245: if (props == null) {
246: props = Sheet.createPropertiesSet();
247: sheet.put(props);
248: }
249: String[] attrs = new String[] { "name", "description",
250: "depends" }; // NOI18N
251: for (String attr : attrs) {
252: org.openide.nodes.Node.Property<?> prop = new AntProperty(
253: target.getElement(), attr);
254: prop.setDisplayName(NbBundle.getMessage(
255: AntTargetNode.class, "PROP_target_" + attr));
256: prop.setShortDescription(NbBundle.getMessage(
257: AntTargetNode.class, "HINT_target_" + attr));
258: props.put(prop);
259: }
260: /*XXX
261: props.put (new BuildSequenceProperty());
262: */
263: return sheet;
264: }
265:
266: /**
267: * Node displaying the sequence of all called targets when executing.
268: */
269: private final class BuildSequenceProperty extends
270: PropertySupport.ReadOnly<String> {
271:
272: /** Creates new BuildSequenceProperty.
273: */
274: public BuildSequenceProperty() {
275: super ("buildSequence", // NOI18N
276: String.class, NbBundle
277: .getMessage(AntTargetNode.class,
278: "PROP_target_sequence"), NbBundle
279: .getMessage(AntTargetNode.class,
280: "HINT_target_sequence"));
281: }
282:
283: /** Computes the dependencies of all called targets and returns an ordered
284: * sequence String.
285: * @param target the target that gets executed
286: * /
287: protected String computeTargetDependencies(org.w3c.dom.Element target) {
288: if (target == null) {
289: return "";
290: }
291:
292: // get ProjectElement
293: Element proj = (Element) target.getParentNode ();
294: if (proj == null) {
295: // just return current target name
296: return target.getAttribute ("name"); // NOI18N
297: } else {
298: // List with all called targets. the last called target is the first
299: // in the list
300: List callingList = new LinkedList();
301: // add this target.
302: callingList = addTarget (callingList, target, 0, proj);
303: if (callingList != null) {
304: return getReverseString (callingList);
305: } else {
306: return NbBundle.getMessage (AntProjectNode.class, "MSG_target_sequence_illegaldepends");
307: }
308: }
309: }
310:
311: /** Adds a target to the List. Calls depends-on targets recursively.
312: * @param runningList List containing the ordered targets.
313: * @param target the target that should be added
314: * @param pos position where this target should be inserted
315: * @projectElement the Element of the Ant project.
316: *
317: * @return list with all targets or null if a target was not found.
318: * /
319: protected List addTarget(List runningList, Element target, int pos, Element projectElement) {
320: String targetName = target.getAttribute ("name"); // NOI18N
321: if (targetName == null) return runningList;
322:
323: // search target, skip it if found
324: Iterator it = runningList.iterator();
325: while (it.hasNext()) {
326: if (targetName.equals (it.next())) {
327: return runningList;
328: }
329: }
330: //add target at the given position...
331: runningList.add(pos, targetName);
332:
333: // check dependenciesList
334: String dependsString = target.getAttribute ("depends"); // NOI18N
335: if (dependsString == null) return runningList;
336:
337: // add each target of the dependencies List
338: StringTokenizer st = new StringTokenizer(dependsString, ", "); // NOI18N
339: while (st.hasMoreTokens() && runningList != null) {
340: Element dependsTarget = getTargetElement(st.nextToken(), projectElement);
341: if (dependsTarget != null) {
342: runningList = addTarget(runningList, dependsTarget, (pos + 1), projectElement);
343: } else {
344: // target is missing, we return null to indicate that something is wrong
345: return null;
346: }
347: }
348:
349: return runningList;
350: }
351:
352: /** Returns the Element of a target given by its name. * /
353: protected Element getTargetElement(String targetName, Element projectElement) {
354: NodeList nl = projectElement.getChildNodes();
355: for (int i = 0; i < nl.getLength (); i++) {
356: if (nl.item (i) instanceof Element) {
357: Element el = (Element) nl.item (i);
358: if (el.getTagName().equals("target") && el.getAttribute("name").equals(targetName)) { // NOI18N
359: return el;
360: }
361: }
362: }
363: return null;
364: }
365:
366: /** Returns a String of all Elements in the List in reverse order. * /
367: protected String getReverseString (List l) {
368: StringBuffer sb = new StringBuffer ();
369: for (int x= (l.size() - 1); x > -1; x--) {
370: sb.append (l.get(x));
371: if (x > 0) sb.append (", "); // NOI18N
372: }
373: return sb.toString ();
374: }
375:
376: /** Returns the value of this property. */
377: @Override
378: public String getValue() {
379: /*XXX
380: return computeTargetDependencies(getTarget());
381: */
382: return "XXX BuildSequenceProperty currently unimplemented";
383: }
384: }
385:
386: private static final class TargetOpenCookie implements OpenCookie {
387:
388: private final TargetLister.Target target;
389:
390: public TargetOpenCookie(TargetLister.Target target) {
391: this .target = target;
392: }
393:
394: public void open() {
395: if (target.getScript().getParseException() != null) {
396: Toolkit.getDefaultToolkit().beep();
397: return;
398: }
399: FileObject script = target.getScript().getFileObject();
400: assert script != null : "No build script for " + target;
401: EditorCookie editor;
402: LineCookie lines;
403: try {
404: DataObject d = DataObject.find(script);
405: editor = d.getCookie(EditorCookie.class);
406: lines = d.getCookie(LineCookie.class);
407: assert editor != null;
408: assert lines != null;
409: } catch (DataObjectNotFoundException e) {
410: throw new AssertionError(e);
411: }
412: try {
413: StyledDocument doc = editor.openDocument();
414: InputSource in = AntProjectSupport.createInputSource(
415: script, doc);
416: SAXParserFactory factory = SAXParserFactory
417: .newInstance();
418: SAXParser parser = factory.newSAXParser();
419: final int[] line = new int[1];
420: final String name = target.getName();
421: class Handler extends DefaultHandler {
422: private Locator locator;
423:
424: @Override
425: public void setDocumentLocator(Locator l) {
426: locator = l;
427: }
428:
429: @Override
430: public void startElement(String uri,
431: String localname, String qname,
432: Attributes attr) throws SAXException {
433: if (line[0] == 0) {
434: if (qname.equals("target")
435: && name.equals(attr
436: .getValue("name"))) { // NOI18N
437: line[0] = locator.getLineNumber();
438: }
439: }
440: }
441: }
442: parser.parse(in, new Handler());
443: if (line[0] < 1) {
444: Toolkit.getDefaultToolkit().beep();
445: return;
446: }
447: lines.getLineSet().getCurrent(line[0] - 1).show(
448: Line.SHOW_GOTO);
449: } catch (Exception e) {
450: AntModule.err.notify(ErrorManager.INFORMATIONAL, e);
451: return;
452: }
453: }
454:
455: }
456:
457: }
|