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.web.jspcompiler;
043:
044: import java.io.File;
045: import java.io.IOException;
046:
047: import java.net.MalformedURLException;
048: import java.util.logging.Level;
049: import java.util.logging.Logger;
050: import java.util.regex.Pattern;
051: import org.apache.tools.ant.module.spi.AntEvent;
052: import org.apache.tools.ant.module.spi.AntLogger;
053: import org.apache.tools.ant.module.spi.AntSession;
054: import org.netbeans.modules.web.api.webmodule.WebModule;
055: import org.openide.filesystems.FileObject;
056: import org.openide.filesystems.FileUtil;
057: import org.openide.windows.OutputListener;
058:
059: /**
060: * Ant logger which handles compilation of JSPs, both the JSP -> Java and
061: * the Java -> class compilation stages.
062: * Specifically, handles hyperlinking of errors from JspC and from Javac run on
063: * classes generated from JSPs.
064: * @author Petr Jiricka, Jesse Glick
065: * @see "#42525"
066: */
067: public final class JSPJavacAntLogger extends AntLogger {
068:
069: // private static PrintWriter debugwriter = null;
070: // private static void debug(String s) {
071: // if (debugwriter == null) {
072: // try {
073: // debugwriter = new PrintWriter(new java.io.FileWriter("/local/repo/trunk/nb_all/nbbuild/AntOutputParser.log")); // NOI18N
074: // } catch (java.io.IOException ioe) {
075: // return;
076: // }
077: // }
078: // debugwriter.println(s);
079: // debugwriter.flush();
080: // }
081:
082: private static final Logger ERR = Logger
083: .getLogger(JSPJavacAntLogger.class.getName());
084: private static final boolean LOGGABLE = ERR.isLoggable(Level.FINE);
085:
086: /**
087: * Regexp matching the compilation error from JspC. Sample message could look like this:
088: * org.apache.jasper.JasperException: file:C:/project/AntParseTestProject2/build/web/index.jsp(6,0) Include action: Mandatory attribute page missing
089: */
090: private static final Pattern JSP_COMPILER_ERROR = Pattern
091: .compile("(.*)(org.apache.jasper.JasperException: file:)([^\\(]*)\\(([0-9]+),([0-9]+)\\)(.*)"); // NOI18N
092:
093: private static final String[] TASKS_OF_INTEREST = AntLogger.ALL_TASKS;
094:
095: private static final int[] LEVELS_OF_INTEREST = {
096: //AntEvent.LOG_DEBUG, // XXX is this needed?
097: //AntEvent.LOG_VERBOSE, // XXX is this needed?
098: AntEvent.LOG_INFO, // XXX is this needed?
099: AntEvent.LOG_WARN, // XXX is this needed?
100: AntEvent.LOG_ERR, // XXX is this needed?
101: };
102:
103: /** Default constructor for lookup. */
104: public JSPJavacAntLogger() {
105: }
106:
107: public boolean interestedInSession(AntSession session) {
108: return true;
109: }
110:
111: public boolean interestedInAllScripts(AntSession session) {
112: return true;
113: }
114:
115: public boolean interestedInScript(File script, AntSession session) {
116: return true;
117: }
118:
119: public String[] interestedInTargets(AntSession session) {
120: return AntLogger.ALL_TARGETS;
121: }
122:
123: public String[] interestedInTasks(AntSession session) {
124: return TASKS_OF_INTEREST;
125: }
126:
127: public int[] interestedInLogLevels(AntSession session) {
128: // XXX could exclude those in [INFO..ERR] greater than session.verbosity
129: return LEVELS_OF_INTEREST;
130: }
131:
132: private static FileObject guessWebModuleOutputRoot(WebModule wm,
133: FileObject fo) {
134: /*
135: File outputF = wm.getWebOutputRoot();
136: if (outputF != null) {
137: return FileUtil.toFileObject(outputF);
138: }
139: */
140: FileObject potentialRoot = fo.getParent();
141: while (potentialRoot != null) {
142: if (potentialRoot.getFileObject("WEB-INF") != null) {
143: return potentialRoot;
144: }
145: potentialRoot = potentialRoot.getParent();
146: }
147: return null;
148: }
149:
150: public void messageLogged(AntEvent event) {
151: if (event.isConsumed()) {
152: return;
153: }
154: AntSession session = event.getSession();
155: String line = event.getMessage();
156: OutputListener hyper = findHyperlink(session, line);
157: // XXX should translate tabs to spaces here as a safety measure
158: if (hyper != null) {
159: event.getSession().println(line,
160: event.getLogLevel() <= AntEvent.LOG_WARN, hyper);
161: event.consume();
162: }
163: }
164:
165: /**
166: * Possibly hyperlink a message logged event.
167: */
168: private static OutputListener findHyperlink(AntSession session,
169: String line) {
170: if (LOGGABLE)
171: ERR.log(Level.FINE, "line: " + line);
172: // #29246: handle new (Ant 1.5.1) URLifications:
173: // [PENDING] Under JDK 1.4, could use new File(URI)... if Ant uses URI too (Jakarta BZ #8031)
174: // XXX so tweak that for Ant 1.6 support!
175: // XXX would be much easier to use a regexp here
176: if (line.startsWith("file:///")) { // NOI18N
177: line = line.substring(7);
178: if (LOGGABLE)
179: ERR.log(Level.FINE, "removing file:///");
180: } else if (line.startsWith("file:")) { // NOI18N
181: line = line.substring(5);
182: if (LOGGABLE)
183: ERR.log(Level.FINE, "removing file:");
184: } else if (line.length() > 0 && line.charAt(0) == '/') {
185: if (LOGGABLE)
186: ERR.log(Level.FINE, "result: looks like Unix file");
187: } else if (line.length() > 2 && line.charAt(1) == ':'
188: && line.charAt(2) == '\\') {
189: if (LOGGABLE)
190: ERR.log(Level.FINE, "result: looks like Windows file");
191: } else {
192: // not a file -> nothing to parse
193: if (LOGGABLE)
194: ERR.log(Level.FINE, "result: not a file");
195: return null;
196: }
197:
198: int colon1 = line.indexOf(':');
199: if (colon1 == -1) {
200: if (LOGGABLE)
201: ERR.log(Level.FINE, "result: no colon found");
202: return null;
203: }
204: String fileName = line.substring(0, colon1); //.replace(File.separatorChar, '/');
205: File file = FileUtil.normalizeFile(new File(fileName));
206: if (!file.exists()) {
207: if (LOGGABLE)
208: ERR.log(Level.FINE, "result: no FO for " + fileName);
209: // maybe we are on Windows and filename is "c:\temp\file.java:25"
210: // try to do the same for the second colon
211: colon1 = line.indexOf(':', colon1 + 1);
212: if (colon1 == -1) {
213: if (LOGGABLE)
214: ERR
215: .log(Level.FINE,
216: "result: no second colon found");
217: return null;
218: }
219: fileName = line.substring(0, colon1);
220: file = FileUtil.normalizeFile(new File(fileName));
221: if (!file.exists()) {
222: if (LOGGABLE)
223: ERR
224: .log(Level.FINE, "result: no FO for "
225: + fileName);
226: return null;
227: }
228: }
229:
230: int line1 = -1, col1 = -1, line2 = -1, col2 = -1;
231: int start = colon1 + 1; // start of message
232: int colon2 = line.indexOf(':', colon1 + 1);
233: if (colon2 != -1) {
234: try {
235: line1 = Integer.parseInt(line.substring(colon1 + 1,
236: colon2).trim());
237: start = colon2 + 1;
238: int colon3 = line.indexOf(':', colon2 + 1);
239: if (colon3 != -1) {
240: col1 = Integer.parseInt(line.substring(colon2 + 1,
241: colon3).trim());
242: start = colon3 + 1;
243: int colon4 = line.indexOf(':', colon3 + 1);
244: if (colon4 != -1) {
245: line2 = Integer.parseInt(line.substring(
246: colon3 + 1, colon4).trim());
247: start = colon4 + 1;
248: int colon5 = line.indexOf(':', colon4 + 1);
249: if (colon5 != -1) {
250: col2 = Integer.parseInt(line.substring(
251: colon4 + 1, colon5).trim());
252: if (col2 == col1)
253: col2 = -1;
254: start = colon5 + 1;
255: }
256: }
257: }
258: } catch (NumberFormatException nfe) {
259: // Fine, rest is part of the message.
260: }
261: }
262: String message = line.substring(start).trim();
263: if (message.length() == 0) {
264: message = null;
265: }
266: if (LOGGABLE)
267: ERR.log(Level.FINE, "Hyperlink: [" + file + "," + line1
268: + "," + col1 + "," + line2 + "," + col2 + ","
269: + message + "]");
270:
271: File smapFile = getSMAPFileForFile(file);
272: if (LOGGABLE)
273: ERR.log(Level.FINE, "smapfile: [" + smapFile + "]");
274: if ((smapFile != null) && (smapFile.exists())) {
275: try {
276: SmapResolver resolver = new SmapResolver(
277: new SmapFileReader(smapFile));
278: String jspName = resolver.getJspFileName(line1, col1);
279: if (jspName == null) {
280: return null;
281: }
282: if (LOGGABLE)
283: ERR.log(Level.FINE, "translate: [" + line1 + ", "
284: + col1 + "]");
285: int newRow = resolver.unmangle(line1, col1);
286: //debug ("translated to '" + jspName + ":" + newRow + "'");
287: // some mappings may not exist, so try next or previous lines, too
288: if (newRow == -1) {
289: newRow = resolver.unmangle(line1 - 1, col1);
290: jspName = resolver.getJspFileName(line1 - 1, col1);
291: }
292: if (newRow == -1) {
293: newRow = resolver.unmangle(line1 + 1, col1);
294: jspName = resolver.getJspFileName(line1 + 1, col1);
295: }
296: try {
297: WebModule wm = WebModule.getWebModule(FileUtil
298: .toFileObject(file));
299: if (wm == null) {
300: return null;
301: }
302: FileObject jspFO = wm.getDocumentBase()
303: .getFileObject(jspName);
304: //debug ("jsp '" + jspFO + "'");
305: if (jspFO != null) {
306: return session.createStandardHyperlink(FileUtil
307: .toFile(jspFO).toURI().toURL(),
308: message, newRow, -1, -1, -1);
309: }
310: return null;
311: } catch (MalformedURLException e) {
312: assert false : e;
313: return null;
314: }
315: } catch (IOException e) {
316: ERR.log(Level.INFO, null, e);
317: // PENDING
318: return null;
319: } catch (Exception e) {
320: // PENDING - this catch clause should not be here, it's here only to
321: // hide bugs in the SmapResolver library
322: ERR.log(Level.INFO, null, e);
323: return null;
324: }
325: } else {
326: return null;
327: }
328:
329: }
330:
331: private static final String JAVA_SUFFIX = ".java"; // NOI18N
332: private static final String SMAP_SUFFIX = ".class.smap"; // NOI18N
333:
334: /** Returns a SMAP file corresponding to the given file, if exists.
335: */
336: public static File getSMAPFileForFile(File javaFile) {
337: File f = FileUtil.normalizeFile(javaFile);
338: File dir = f.getAbsoluteFile().getParentFile();
339: String name = f.getName();
340: if (!name.endsWith(JAVA_SUFFIX)) {
341: return null;
342: }
343: name = name.substring(0, name.length() - JAVA_SUFFIX.length());
344: File newFile = new File(dir, name + SMAP_SUFFIX);
345: return newFile;
346: }
347:
348: }
|