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.collab.channel.output;
042:
043: import org.openide.ErrorManager;
044: import org.openide.awt.StatusDisplayer;
045: import org.openide.cookies.EditorCookie;
046: import org.openide.filesystems.FileObject;
047: import org.openide.filesystems.URLMapper;
048: import org.openide.loaders.DataObject;
049: import org.openide.loaders.DataObjectNotFoundException;
050: import org.openide.text.Annotatable;
051: import org.openide.text.Annotation;
052: import org.openide.text.Line;
053: import org.openide.util.WeakSet;
054: import org.openide.windows.OutputEvent;
055: import org.openide.windows.OutputListener;
056:
057: import java.awt.Toolkit;
058:
059: import java.beans.PropertyChangeEvent;
060: import java.beans.PropertyChangeListener;
061:
062: import java.io.IOException;
063:
064: import java.net.URL;
065:
066: import java.util.Iterator;
067: import java.util.Set;
068:
069: /**
070: * Represents a linkable line (appears in red in Output Window).
071: * Line and column numbers start at 1, and -1 means an unknown value.
072: * Careful since org.openide.text seems to assume 0-based line and column numbers.
073: * @author Jesse Glick
074: */
075: public final class Hyperlink extends Annotation implements
076: OutputListener, PropertyChangeListener {
077: // #14804: detach everything before uninstalling module.
078: private static final Set hyperlinks = new WeakSet(); // Set<Hyperlink>
079: private final URL url;
080: private final String message;
081: private final int line1;
082: private int col1;
083: private final int line2;
084: private final int col2;
085: private boolean dead = false;
086:
087: private static final ErrorManager err = ErrorManager.getDefault()
088: .getInstance("org.apache.tools.ant.module"); // NOI18N
089:
090: public Hyperlink(URL url, String message, int line1, int col1,
091: int line2, int col2) {
092: this .url = url;
093: this .message = message;
094: this .line1 = line1;
095: this .col1 = col1;
096: this .line2 = line2;
097: this .col2 = col2;
098:
099: synchronized (hyperlinks) {
100: hyperlinks.add(this );
101: }
102: }
103:
104: public static void detachAllAnnotations() {
105: synchronized (hyperlinks) {
106: Iterator it = hyperlinks.iterator();
107:
108: while (it.hasNext()) {
109: ((Hyperlink) it.next()).destroy();
110: }
111: }
112: }
113:
114: /**
115: * Enables the column number of the hyperlink to be changed after the fact.
116: * If it is already set, this is ignored.
117: */
118: public void setColumn1(int col1) {
119: if (this .col1 == -1) {
120: this .col1 = col1;
121: }
122: }
123:
124: void destroy() {
125: doDetach();
126: dead = true;
127: }
128:
129: public void outputLineAction(OutputEvent ev) {
130: if (dead) {
131: return;
132: }
133:
134: FileObject file = URLMapper.findFileObject(url);
135:
136: if (file == null) { // #13115
137: Toolkit.getDefaultToolkit().beep();
138:
139: return;
140: }
141:
142: try {
143: DataObject dob = DataObject.find(file);
144: EditorCookie ed = (EditorCookie) dob
145: .getCookie(EditorCookie.class);
146:
147: if ((ed != null) && /* not true e.g. for *_ja.properties */
148: (file == dob.getPrimaryFile())) {
149: if (line1 == -1) {
150: // OK, just open it.
151: ed.open();
152: } else {
153: ed.openDocument(); // XXX getLineSet does not do it for you!
154: err.log("opened document for " + file);
155:
156: try {
157: Line l = ed.getLineSet().getOriginal(line1 - 1);
158:
159: if (!l.isDeleted()) {
160: attachAsNeeded(l);
161:
162: if (col1 == -1) {
163: l.show(Line.SHOW_GOTO);
164: } else {
165: l.show(Line.SHOW_GOTO, col1 - 1);
166: }
167: }
168: } catch (IndexOutOfBoundsException ioobe) {
169: // Probably harmless. Bogus line number.
170: ed.open();
171: }
172: }
173: } else {
174: Toolkit.getDefaultToolkit().beep();
175: }
176: } catch (DataObjectNotFoundException donfe) {
177: ErrorManager.getDefault().notify(ErrorManager.WARNING,
178: donfe);
179: } catch (IOException ioe) {
180: // XXX see above, should not be necessary to call openDocument at all
181: ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe);
182: }
183:
184: if (message != null) {
185: // Try to do after opening the file, since opening a new file
186: // clears the current status message.
187: StatusDisplayer.getDefault().setStatusText(message);
188: }
189: }
190:
191: public void outputLineSelected(OutputEvent ev) {
192: if (dead) {
193: return;
194: }
195:
196: FileObject file = URLMapper.findFileObject(url);
197:
198: if (file == null) {
199: return;
200: }
201:
202: try {
203: DataObject dob = DataObject.find(file);
204: EditorCookie ed = (EditorCookie) dob
205: .getCookie(EditorCookie.class);
206:
207: if (ed != null) {
208: if (ed.getDocument() == null) {
209: // The document is not opened, don't bother with it.
210: // The Line.Set will be corrupt anyway, currently.
211: err.log("no document for " + file);
212:
213: return;
214: }
215:
216: err.log("got document for " + file);
217:
218: if (line1 != -1) {
219: Line l = ed.getLineSet().getOriginal(line1 - 1);
220:
221: if (!l.isDeleted()) {
222: attachAsNeeded(l);
223:
224: if (col1 == -1) {
225: l.show(Line.SHOW_TRY_SHOW);
226: } else {
227: l.show(Line.SHOW_TRY_SHOW, col1 - 1);
228: }
229: }
230: }
231: }
232: } catch (DataObjectNotFoundException donfe) {
233: ErrorManager.getDefault().notify(ErrorManager.WARNING,
234: donfe);
235: } catch (IndexOutOfBoundsException iobe) {
236: // Probably harmless. Bogus line number.
237: }
238: }
239:
240: private synchronized void attachAsNeeded(Line l) {
241: if (getAttachedAnnotatable() == null) {
242: boolean log = err.isLoggable(ErrorManager.INFORMATIONAL);
243: Annotatable ann;
244:
245: // Text of the line, incl. trailing newline.
246: String text = l.getText();
247:
248: if (log) {
249: err.log("Attaching to line " + l.getDisplayName()
250: + " text=`" + text + "' line1=" + line1
251: + " line2=" + line2 + " col1=" + col1
252: + " col2=" + col2);
253: }
254:
255: if ((text != null) && ((line2 == -1) || (line1 == line2))
256: && (col1 != -1)) {
257: int new_col1 = convertTabColumnsToCharacterColumns(
258: text, col1 - 1, 8);
259: int new_col2 = convertTabColumnsToCharacterColumns(
260: text, col2 - 1, 8);
261:
262: if (log) {
263: err.log("\tfits on one line");
264: }
265:
266: if ((new_col2 != -1) && (new_col2 >= new_col1)
267: && (new_col2 < text.length())) {
268: if (log) {
269: err.log("\tspecified section of the line");
270: }
271:
272: ann = l.createPart(new_col1, new_col2 - new_col1
273: + 1);
274: } else if (new_col1 < text.length()) {
275: if (log) {
276: err.log("\tspecified column to end of line");
277: }
278:
279: ann = l.createPart(new_col1, text.length()
280: - new_col1 - 1);
281: } else {
282: if (log) {
283: err.log("\tcolumn numbers are bogus");
284: }
285:
286: ann = l;
287: }
288: } else {
289: if (log) {
290: err
291: .log("\tmultiple lines, something wrong with line, or no column given");
292: }
293:
294: ann = l;
295: }
296:
297: attach(ann);
298:
299: // #17625: detach others however
300: Iterator it = hyperlinks.iterator();
301:
302: while (it.hasNext()) {
303: Hyperlink h = (Hyperlink) it.next();
304:
305: if (h != this ) {
306: h.doDetach();
307: }
308: }
309:
310: ann.addPropertyChangeListener(this );
311: }
312: }
313:
314: // XXX should be handled in StandardLogger, perhaps?
315: private int convertTabColumnsToCharacterColumns(String text,
316: int column, int tabSize) {
317: // #16867 - jikes is right now only compiler which reports column of the error.
318: // If the text contains 'tab' character, the jikes expects
319: // that tab character is defined as 8 spaces, and so it sets the column accordingly.
320: // This method converts jikes columns back to character columns
321: char[] textChars = text.toCharArray();
322: int i;
323: int jikes_column = 0;
324:
325: for (i = 0; (i < textChars.length) && (jikes_column < column); i++) {
326: if (textChars[i] == 9) {
327: jikes_column += (tabSize - (jikes_column % tabSize));
328: } else {
329: jikes_column++;
330: }
331: }
332:
333: return i;
334: }
335:
336: private synchronized void doDetach() {
337: Annotatable ann = getAttachedAnnotatable();
338:
339: if (ann != null) {
340: if (err.isLoggable(ErrorManager.INFORMATIONAL)) {
341: err.log("Detaching from " + ann + " `" + ann.getText()
342: + "'");
343: }
344:
345: ann.removePropertyChangeListener(this );
346: detach();
347: }
348: }
349:
350: public void outputLineCleared(OutputEvent ev) {
351: doDetach();
352: }
353:
354: public void propertyChange(PropertyChangeEvent ev) {
355: if (dead) {
356: return;
357: }
358:
359: String prop = ev.getPropertyName();
360:
361: if ((prop == null) || prop.equals(Annotatable.PROP_TEXT)
362: || prop.equals(Annotatable.PROP_DELETED)) {
363: // Affected line has changed.
364: // Assume user has edited & corrected the error.
365: if (err.isLoggable(ErrorManager.INFORMATIONAL)) {
366: err
367: .log("Received Annotatable property change: "
368: + prop);
369: }
370:
371: doDetach();
372: }
373: }
374:
375: public String getAnnotationType() {
376: return "org-apache-tools-ant-module-error"; // NOI18N
377: }
378:
379: public String getShortDescription() {
380: if (message != null) {
381: return message;
382: } else {
383: return null;
384: }
385: }
386:
387: public String toString() {
388: return "Hyperlink[" + url + ":" + line1 + ":" + col1 + ":"
389: + line2 + ":" + col2 + "]"; // NOI18N
390: }
391: }
|