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: package org.netbeans.modules.xml.actions;
042:
043: import java.beans.PropertyChangeEvent;
044: import java.beans.PropertyChangeListener;
045: import java.io.IOException;
046: import java.text.MessageFormat;
047: import java.util.*;
048: import java.net.*;
049:
050: import org.openide.loaders.DataObject;
051: import org.openide.loaders.DataObjectNotFoundException;
052: import org.openide.text.Line;
053: import org.openide.filesystems.FileObject;
054: import org.openide.filesystems.URLMapper;
055: import org.openide.*;
056: import org.openide.nodes.*;
057: import org.openide.cookies.*;
058: import org.openide.windows.*;
059: import org.openide.util.WeakSet;
060:
061: import org.netbeans.api.xml.cookies.*;
062: import org.openide.text.Annotatable;
063: import org.openide.text.Annotation;
064:
065: /**
066: * Provides InputOutput UI for CheckXMLCookie.
067: * <p>
068: * Implementation: <code>display</code> method samples <code>Line</code> where error occured so
069: * further modifications (fixes) do not affect it and installs a InputOutput
070: * line handler for SAXParseErrors.
071: *
072: * @author Petr Kuzel
073: * @see InputOutput
074: * @deprecated XML tools actions API candidate
075: */
076: public final class InputOutputReporter implements CookieObserver {
077:
078: //0 extends message, 1 line number, 2 url of external entity
079: private final String FORMAT = "{0} [{1}] {2}"; // NOI18N
080:
081: private String ioName;
082:
083: private DataObject dataObject;
084:
085: // remember all attached annotations
086: private static final Set hyperlinks = Collections
087: .synchronizedSet(new WeakSet()); // Set<Hyperlink>
088:
089: /**
090: * Creates new InputOutputReporter regirecting ProcessorListener
091: * to InputOutput. To finish per call initialization setNode()
092: * must be called.
093: */
094: public InputOutputReporter() {
095: this (Util.THIS.getString("TITLE_XML_check_window"));
096: }
097:
098: public InputOutputReporter(String name) {
099: initInputOutput(name);
100: }
101:
102: /**
103: * Somehow helps to properly link to external entities.
104: * XXX But actual test case is not know.
105: */
106: public void setNode(Node node) {
107: if (Util.THIS.isLoggable()) /* then */
108: Util.THIS.debug("InputOutputReporter.setNode: " + node,
109: new RuntimeException(
110: "Who calls InputOutputReporter.setNode"));
111:
112: dataObject = node.getCookie(DataObject.class);
113: }
114:
115: /**
116: * Associated data object accessor
117: */
118: private DataObject dataObject() {
119: return dataObject;
120: }
121:
122: public void receive(CookieMessage msg) {
123: Object detail = msg.getDetail(XMLProcessorDetail.class);
124:
125: if (Util.THIS.isLoggable()) /* then */{
126: Util.THIS.debug("InputOutputReporter.receive:");
127: Util.THIS.debug(" dataObject = " + dataObject);
128: Util.THIS.debug(" Message = " + msg);
129: Util.THIS.debug(" detail = " + detail);
130: if (detail == null) {
131: Util.THIS.debug(new RuntimeException(
132: "Message's Detail is _null_!!!"));
133: }
134: }
135:
136: if (detail instanceof XMLProcessorDetail) {
137: display(dataObject(), msg.getMessage(),
138: (XMLProcessorDetail) detail);
139: } else {
140: message(msg.getMessage());
141: }
142: }
143:
144: /**
145: * Display plain message in output window.
146: */
147: public void message(String message) {
148: out().println(message);
149: }
150:
151: /**
152: * Try to move InputOutput to front. Suitable for first message.
153: */
154: public final void moveToFront() {
155: moveToFront(false);
156: }
157:
158: /**
159: * Try to move InputOUtput to front. Suitable for first and last messages.
160: * @param lastMessage if true close OutputWriter relation
161: */
162: public final void moveToFront(boolean lastMessage) {
163: boolean wasFocusTaken = tab().isFocusTaken();
164: tab().select();
165: tab().setFocusTaken(true);
166: out().write("\r");
167: tab().setFocusTaken(wasFocusTaken);
168: if (lastMessage) {
169: out().close();
170: }
171: }
172:
173: /** Show using SAX parser error format */
174: private void display(DataObject dobj, String message,
175: XMLProcessorDetail detail) {
176: // resolve actual data object that caused exception
177: // it may differ from XML document for external entities
178:
179: DataObject actualDataObject = null;
180: try {
181: String systemId = detail.getSystemId();
182: URL url = new URL(systemId);
183: FileObject fos = URLMapper.findFileObject(url);
184: if (fos != null) {
185: actualDataObject = DataObject.find(fos);
186: }
187:
188: if (Util.THIS.isLoggable()) /* then */{
189: Util.THIS.debug("InputOutputReporter.display: "
190: + message);
191: Util.THIS.debug(" systemId = "
192: + detail.getSystemId());
193: Util.THIS.debug(" url = " + url);
194: Util.THIS.debug(" fos = " + fos);
195: }
196: } catch (MalformedURLException ex) {
197: // we test for null
198: if (Util.THIS.isLoggable()) /* then */
199: Util.THIS.debug(ex);
200: } catch (DataObjectNotFoundException ex) {
201: // we test for null
202: if (Util.THIS.isLoggable()) /* then */
203: Util.THIS.debug(ex);
204: }
205:
206: // external should contain systemID for unresolned external entities
207:
208: String external = ""; // NOI18N
209:
210: if (actualDataObject == null) {
211: external = detail.getSystemId();
212: }
213:
214: display(actualDataObject, message, external, detail
215: .getLineNumber(), detail.getColumnNumber());
216: }
217:
218: /** Show it in output tab formatted and with attached controller. */
219: private void display(DataObject dobj, String message, String ext,
220: int line, int col) {
221:
222: String text = null;
223: if (line >= 0) {
224: Object[] args = new Object[] { message, new Integer(line),
225: ext };
226:
227: text = MessageFormat.format(FORMAT, args);
228: } else {
229: // unknown line so attach controller to file only
230: text = message;
231: }
232:
233: if (dobj == null) {
234: out().println(text); // print without controller
235: } else {
236: try {
237: Hyperlink ec = new Hyperlink(text, dobj, Math.max(
238: line - 1, 0), Math.max(col - 1, 0));
239: out().println(text, ec);
240: } catch (IOException catchIt) {
241: out().println(text); // print without controller
242: }
243: }
244: }
245:
246: /** Set output writer used by this displayer.
247: * Share existing, clear content on reuse.
248: */
249: private void initInputOutput(String name) {
250: ioName = name;
251: tab().setFocusTaken(false);
252:
253: // clear previous output
254: try {
255: out().reset();
256: } catch (IOException e) {
257: ErrorManager.getDefault().notify(e);
258: }
259: }
260:
261: private OutputWriter out() {
262: return tab().getOut();
263: }
264:
265: private InputOutput tab() {
266: return IOProvider.getDefault().getIO(ioName, false);
267: }
268:
269: /**
270: * Release all annotations attached by this class
271: */
272: public static void releaseAllAnnotations() {
273: synchronized (hyperlinks) {
274: Iterator it = hyperlinks.iterator();
275: while (it.hasNext()) {
276: ((Hyperlink) it.next()).detach();
277: }
278: hyperlinks.clear();
279: }
280: }
281:
282: private static class Hyperlink extends Annotation implements
283: OutputListener, PropertyChangeListener {
284:
285: /** sampled line containing the error */
286: private Line xline;
287:
288: /** original column with the err or -1 */
289: private int column;
290:
291: private final String message;
292:
293: public Hyperlink(String message, DataObject data, int line,
294: int column) throws IOException {
295: this .column = column;
296: this .message = message;
297: LineCookie cookie = data.getCookie(LineCookie.class);
298: if (cookie == null) {
299: throw new java.io.FileNotFoundException();
300: } else {
301: xline = cookie.getLineSet().getCurrent(line);
302: }
303: }
304:
305: public void outputLineSelected(OutputEvent ev) {
306: try {
307: markError();
308: show(Line.SHOW_TRY_SHOW);
309: } catch (IndexOutOfBoundsException ex) {
310: } catch (ClassCastException ex) {
311: // This is hack because of CloneableEditorSupport error -- see CloneableEditorSupport:1193
312: }
313: }
314:
315: public void outputLineAction(OutputEvent ev) {
316: try {
317: markError();
318: show(Line.SHOW_GOTO);
319: } catch (IndexOutOfBoundsException ex) {
320: } catch (ClassCastException ex) {
321: // This is hack because of CloneableEditorSupport error -- see CloneableEditorSupport:1193
322: }
323: }
324:
325: public void outputLineCleared(OutputEvent ev) {
326: hyperlinks.remove(this );
327: detach();
328: }
329:
330: protected void notifyDetached(Annotatable ann) {
331: ann.removePropertyChangeListener(this );
332: }
333:
334: protected void notifyAttached(Annotatable ann) {
335: ann.addPropertyChangeListener(this );
336: }
337:
338: /**
339: * Prepare annotation target
340: */
341: private Annotatable createAnnotatable() {
342: // if (column < 1 ) {
343: return xline;
344: // } else {
345: // I have never got proper property changes on Line.Part
346: // return xline.createPart(0, column - 1);
347: // }
348: }
349:
350: // open document in editor
351: private void show(int mode) {
352: if (column == -1) {
353: xline.show(mode);
354: } else {
355: xline.show(mode, column);
356: }
357: }
358:
359: // we need to have one error at time
360: private void markError() {
361: releaseAllAnnotations();
362: hyperlinks.add(this );
363: attach(createAnnotatable());
364: }
365:
366: /**
367: * Returns name of the file which describes the annotation type.
368: * The file must be defined in module installation layer in the
369: * directory "Editors/AnnotationTypes"
370: */
371: public String getAnnotationType() {
372: return "org-netbeans-modules-xml-error"; // NOI18N
373: }
374:
375: /**
376: * Returns the tooltip text for this annotation.
377: */
378: public String getShortDescription() {
379: return message;
380: }
381:
382: // Affected line has changed.
383: public void propertyChange(PropertyChangeEvent ev) {
384: String prop = ev.getPropertyName();
385: if (prop == null || prop.equals(Annotatable.PROP_TEXT)
386: || prop.equals(Annotatable.PROP_DELETED)) {
387: // Assume user has edited & corrected the error (or at least we do
388: // nok know error column position anymore).
389: column = -1;
390: hyperlinks.remove(this);
391: detach();
392: }
393: }
394: }
395:
396: }
|