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 com.sun.collablet.*;
044:
045: import org.apache.tools.ant.module.spi.AntEvent;
046: import org.apache.tools.ant.module.spi.AntLogger;
047: import org.apache.tools.ant.module.spi.AntSession;
048: import org.openide.ErrorManager;
049: import org.openide.filesystems.FileObject;
050: import org.openide.filesystems.FileUtil;
051: import org.openide.util.NbBundle;
052: import org.openide.windows.OutputListener;
053:
054: import java.io.File;
055: import java.io.PrintWriter;
056: import java.io.StringWriter;
057:
058: import java.net.MalformedURLException;
059:
060: import java.util.StringTokenizer;
061: import java.util.regex.Matcher;
062: import java.util.regex.Pattern;
063:
064: import org.netbeans.api.project.*;
065:
066: import org.netbeans.modules.collab.channel.filesharing.*;
067: import org.netbeans.modules.collab.core.Debug;
068:
069: public class CollabAntLogger extends AntLogger {
070: private static final ErrorManager ERR = ErrorManager.getDefault()
071: .getInstance(CollabAntLogger.class.getName());
072: private static final boolean LOGGABLE = ERR
073: .isLoggable(ErrorManager.INFORMATIONAL);
074: private static final Pattern CARET_SHOWING_COLUMN = Pattern
075: .compile("^( *)\\^$"); // NOI18N
076: private StringBuffer buffer = new StringBuffer();
077: private Conversation[] convs;
078:
079: /**
080: * Creates a new instance of CollabAntLogger
081: */
082: public CollabAntLogger() {
083: }
084:
085: public boolean interestedInAllScripts(AntSession session) {
086: return true;
087: }
088:
089: public int[] interestedInLogLevels(AntSession session) {
090: int verb = session.getVerbosity();
091: assert (verb >= AntEvent.LOG_ERR)
092: && (verb <= AntEvent.LOG_DEBUG) : verb;
093:
094: int[] levels = new int[verb + 1];
095:
096: for (int i = 0; i <= verb; i++) {
097: levels[i] = i;
098: }
099:
100: return levels;
101: }
102:
103: public boolean interestedInSession(AntSession session) {
104: // we're only interested in those projects being shared
105: File buildScript = session.getOriginatingScript();
106: String[] targets = session.getOriginatingTargets();
107: FileObject fo = FileUtil.toFileObject(buildScript);
108: Project project = FileOwnerQuery.getOwner(fo);
109:
110: if (project == null) {
111: return false;
112: }
113:
114: convs = FilesharingCollablet.getConversations(project);
115: Debug.out.println("*********** interested in session: "
116: + convs.length);
117:
118: for (int i = 0; i < targets.length; i++) {
119: Debug.out.println("*********** targets : " + targets[i]);
120: }
121:
122: return (convs != null) && (convs.length > 0);
123: }
124:
125: public String[] interestedInTargets(AntSession session) {
126: return AntLogger.ALL_TARGETS;
127: }
128:
129: public String[] interestedInTasks(AntSession session) {
130: return AntLogger.ALL_TASKS;
131: }
132:
133: public void taskStarted(AntEvent event) {
134: super .taskStarted(event);
135: }
136:
137: public void targetStarted(AntEvent event) {
138: // if (event.isConsumed()) {
139: // return;
140: // }
141: // XXX this could start indenting messages, perhaps
142: String name = event.getTargetName();
143:
144: if (name != null) {
145: // Avoid printing internal targets normally:
146: int minlevel = ((name.length() > 0) && (name.charAt(0) == '-')) ? AntEvent.LOG_VERBOSE
147: : AntEvent.LOG_INFO;
148:
149: if (event.getSession().getVerbosity() >= minlevel) {
150: buffer.append(NbBundle.getMessage(
151: CollabAntLogger.class,
152: "MSG_target_started_printed", name)
153: + "\r\n");
154: }
155: }
156:
157: // event.consume();
158: }
159:
160: public void buildFinished(AntEvent event) {
161: // if (event.isConsumed()) {
162: // return;
163: // }
164: AntSession session = event.getSession();
165: Throwable t = event.getException();
166: long time = System.currentTimeMillis()
167: - getSessionData(session).startTime; // #10305
168:
169: if (t == null) {
170: buffer.append(formatMessageWithTime(
171: "FMT_finished_target_printed", time)
172: + "\r\n");
173: } else {
174: if (!session.isExceptionConsumed(t)) {
175: session.consumeException(t);
176:
177: if (t.getClass().getName().equals(
178: "org.apache.tools.ant.BuildException")
179: && (session.getVerbosity() < AntEvent.LOG_VERBOSE)) { // NOI18N
180:
181: // Stack trace probably not required.
182: // Check for hyperlink to handle e.g. <fail>
183: // which produces a BE whose toString is the location + message.
184: // But send to other loggers since they may wish to suppress such an error.
185: String msg = t.toString();
186: deliverBlockOfTextAsLines(msg, event,
187: AntEvent.LOG_ERR);
188: } else if (!(t instanceof ThreadDeath)
189: || (event.getSession().getVerbosity() >= AntEvent.LOG_VERBOSE)) {
190: // ThreadDeath can be thrown when killing an Ant process, so don't print it normally
191: deliverStackTrace(t, event);
192: }
193: }
194:
195: buffer.append(formatMessageWithTime(
196: "FMT_target_failed_printed", time)
197: + "\r\n");
198: }
199:
200: // event.consume();
201: Debug.out.println(buffer.toString());
202: shareOutput(event);
203:
204: reset();
205: }
206:
207: private void shareOutput(AntEvent event) {
208: String projectName = "";
209: String displayName = "";
210:
211: FileObject fo = FileUtil.toFileObject(event.getSession()
212: .getOriginatingScript());
213: Project project = FileOwnerQuery.getOwner(fo);
214:
215: if (project != null) {
216: projectName = ProjectUtils.getInformation(project)
217: .getDisplayName();
218: }
219:
220: String target = event.getTargetName();
221:
222: if (target == null) {
223: displayName = NbBundle.getMessage(CollabAntLogger.class,
224: "TITLE_output_notarget", projectName);
225: } else {
226: displayName = NbBundle.getMessage(CollabAntLogger.class,
227: "TITLE_output_target", projectName, target);
228: }
229:
230: Conversation[] conversations = getConversations();
231:
232: if (conversations == null) {
233: return;
234: }
235:
236: for (int i = 0; i < conversations.length; i++) {
237: Collablet[] channels = conversations[i].getChannels();
238:
239: for (int j = 0; j < channels.length; j++) {
240: if (channels[j] instanceof OutputCollablet) {
241: String id = conversations[i].getCollabSession()
242: .getUserPrincipal().getIdentifier()
243: + conversations[i].getIdentifier()
244: + projectName;
245:
246: ((OutputCollablet) channels[j]).shareAntOutput(
247: displayName, id, buffer.toString());
248: }
249: }
250: }
251: }
252:
253: public void buildInitializationFailed(AntEvent event) {
254: super .buildInitializationFailed(event);
255: }
256:
257: public void buildStarted(AntEvent event) {
258: Debug.out.println(" target " + event.getTargetName());
259:
260: // if (event.isConsumed()) {
261: // return;
262: // }
263: getSessionData(event.getSession()).startTime = System
264: .currentTimeMillis();
265:
266: //// StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(StandardLogger.class, "FMT_running_ant", event.getSession().getDisplayName()));
267: // // no messages printed for now
268: // event.consume();
269: }
270:
271: public void messageLogged(AntEvent event) {
272: // if (event.isConsumed()) {
273: // return;
274: // }
275: // event.consume();
276: AntSession session = event.getSession();
277: String line = event.getMessage();
278:
279: // if (LOGGABLE) ERR.log("Received message: " + line);
280: if (line.indexOf('\n') != -1) {
281: // Multiline message. Should be split into blocks and redelivered,
282: // to allow other loggers (e.g. JavaAntLogger) to process individual
283: // lines (e.g. stack traces). Note that other loggers are still capable
284: // of handling the original multiline message specially. Note also that
285: // only messages at or above the session verbosity will be split.
286: deliverBlockOfTextAsLines(line, event, event.getLogLevel());
287:
288: return;
289: }
290:
291: Matcher m = CARET_SHOWING_COLUMN.matcher(line);
292:
293: if (m.matches()) {
294: SessionData data = getSessionData(session);
295:
296: if (data.lastHyperlink != null) {
297: // For " ^", infer a column number of 3.
298: data.lastHyperlink.setColumn1(m.group(1).length() + 1);
299: data.lastHyperlink = null;
300:
301: // Don't print the actual caret line, just noise.
302: return;
303: }
304: }
305:
306: OutputListener hyperlink = findHyperlink(session, line);
307:
308: if (hyperlink instanceof Hyperlink) {
309: getSessionData(session).lastHyperlink = (Hyperlink) hyperlink;
310: }
311:
312: // XXX should translate tabs to spaces here as a safety measure (esp. since output window messes it up...)
313: // event.getSession().println(line, event.getLogLevel() <= AntEvent.LOG_WARN, hyperlink);
314: buffer.append(line + "\r\n");
315: }
316:
317: public void targetFinished(AntEvent event) {
318: }
319:
320: public void taskFinished(AntEvent event) {
321: }
322:
323: public boolean interestedInScript(File script, AntSession session) {
324: return true;
325: }
326:
327: private Conversation[] getConversations() {
328: return convs;
329: }
330:
331: private void reset() {
332: convs = null;
333: buffer = new StringBuffer();
334: }
335:
336: private SessionData getSessionData(AntSession session) {
337: SessionData data = (SessionData) session.getCustomData(this );
338:
339: if (data == null) {
340: data = new SessionData();
341: session.putCustomData(this , data);
342: }
343:
344: return data;
345: }
346:
347: /** Formats the millis in a human readable String.
348: * Total time: {0} minutes
349: * {1} seconds
350: */
351: private String formatMessageWithTime(String key, long millis) {
352: int secs = (int) (millis / 1000);
353: int minutes = secs / 60;
354: int seconds = secs % 60;
355:
356: return NbBundle.getMessage(CollabAntLogger.class, key,
357: new Integer(minutes), new Integer(seconds));
358: }
359:
360: private void deliverBlockOfTextAsLines(String lines,
361: AntEvent originalEvent, int level) {
362: StringTokenizer tok = new StringTokenizer(lines, "\r\n"); // NOI18N
363:
364: while (tok.hasMoreTokens()) {
365: String line = tok.nextToken();
366:
367: // originalEvent.getSession().deliverMessageLogged(originalEvent, line, level);
368: buffer.append(line + "\r\n");
369: }
370: }
371:
372: private void deliverStackTrace(Throwable t, AntEvent originalEvent) {
373: StringWriter sw = new StringWriter();
374: PrintWriter pw = new PrintWriter(sw);
375: t.printStackTrace(pw);
376: pw.flush();
377: deliverBlockOfTextAsLines(sw.toString(), originalEvent,
378: AntEvent.LOG_ERR);
379: }
380:
381: /**
382: * Possibly hyperlink a message logged event.
383: */
384: private static OutputListener findHyperlink(AntSession session,
385: String line) {
386: // #29246: handle new (Ant 1.5.1) URLifications:
387: // [PENDING] Under JDK 1.4, could use new File(URI)... if Ant uses URI too (Jakarta BZ #8031)
388: // XXX so tweak that for Ant 1.6 support!
389: // XXX would be much easier to use a regexp here
390: if (line.startsWith("file:///")) { // NOI18N
391: line = line.substring(7);
392:
393: if (LOGGABLE) {
394: ERR.log("removing file:///");
395: }
396: } else if (line.startsWith("file:")) { // NOI18N
397: line = line.substring(5);
398:
399: if (LOGGABLE) {
400: ERR.log("removing file:");
401: }
402: } else if ((line.length() > 0) && (line.charAt(0) == '/')) {
403: if (LOGGABLE) {
404: ERR.log("result: looks like Unix file");
405: }
406: } else if ((line.length() > 2) && (line.charAt(1) == ':')
407: && (line.charAt(2) == '\\')) {
408: if (LOGGABLE) {
409: ERR.log("result: looks like Windows file");
410: }
411: } else {
412: // not a file -> nothing to parse
413: if (LOGGABLE) {
414: ERR.log("result: not a file");
415: }
416:
417: return null;
418: }
419:
420: int colon1 = line.indexOf(':');
421:
422: if (colon1 == -1) {
423: if (LOGGABLE) {
424: ERR.log("result: no colon found");
425: }
426:
427: return null;
428: }
429:
430: String fileName = line.substring(0, colon1); //.replace(File.separatorChar, '/');
431: File file = FileUtil.normalizeFile(new File(fileName));
432:
433: if (!file.exists()) {
434: if (LOGGABLE) {
435: ERR.log("result: no FO for " + fileName);
436: }
437:
438: // maybe we are on Windows and filename is "c:\temp\file.java:25"
439: // try to do the same for the second colon
440: colon1 = line.indexOf(':', colon1 + 1);
441:
442: if (colon1 == -1) {
443: if (LOGGABLE) {
444: ERR.log("result: no second colon found");
445: }
446:
447: return null;
448: }
449:
450: fileName = line.substring(0, colon1);
451: file = FileUtil.normalizeFile(new File(fileName));
452:
453: if (!file.exists()) {
454: if (LOGGABLE) {
455: ERR.log("result: no FO for " + fileName);
456: }
457:
458: return null;
459: }
460: }
461:
462: int line1 = -1;
463: int col1 = -1;
464: int line2 = -1;
465: int col2 = -1;
466: int start = colon1 + 1; // start of message
467: int colon2 = line.indexOf(':', colon1 + 1);
468:
469: if (colon2 != -1) {
470: try {
471: line1 = Integer.parseInt(line.substring(colon1 + 1,
472: colon2).trim());
473: start = colon2 + 1;
474:
475: int colon3 = line.indexOf(':', colon2 + 1);
476:
477: if (colon3 != -1) {
478: col1 = Integer.parseInt(line.substring(colon2 + 1,
479: colon3).trim());
480: start = colon3 + 1;
481:
482: int colon4 = line.indexOf(':', colon3 + 1);
483:
484: if (colon4 != -1) {
485: line2 = Integer.parseInt(line.substring(
486: colon3 + 1, colon4).trim());
487: start = colon4 + 1;
488:
489: int colon5 = line.indexOf(':', colon4 + 1);
490:
491: if (colon5 != -1) {
492: col2 = Integer.parseInt(line.substring(
493: colon4 + 1, colon5).trim());
494:
495: if (col2 == col1) {
496: col2 = -1;
497: }
498:
499: start = colon5 + 1;
500: }
501: }
502: }
503: } catch (NumberFormatException nfe) {
504: // Fine, rest is part of the message.
505: }
506: }
507:
508: String message = line.substring(start).trim();
509:
510: if (message.length() == 0) {
511: message = null;
512: }
513:
514: if (LOGGABLE) {
515: ERR.log("Hyperlink: [" + file + "," + line1 + "," + col1
516: + "," + line2 + "," + col2 + "," + message + "]");
517: }
518:
519: try {
520: return session.createStandardHyperlink(
521: file.toURI().toURL(), message, line1, col1, line2,
522: col2);
523: } catch (MalformedURLException e) {
524: assert false : e;
525:
526: return null;
527: }
528: }
529:
530: /**
531: * Data stored in the session.
532: */
533: private static final class SessionData {
534: /** Time build was started. */
535: public long startTime;
536:
537: /** Last-created hyperlink, in case we need to adjust the column number. */
538: public Hyperlink lastHyperlink;
539:
540: public SessionData() {
541: }
542: }
543: }
|