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.nbbuild;
043:
044: import java.io.*;
045: import java.net.*;
046: import java.util.*;
047: import java.util.regex.*;
048:
049: import org.apache.tools.ant.BuildException;
050: import org.apache.tools.ant.FileScanner;
051: import org.apache.tools.ant.Project;
052: import org.apache.tools.ant.Task;
053: import org.apache.tools.ant.taskdefs.MatchingTask;
054:
055: import org.apache.tools.ant.types.Mapper;
056:
057: // XXX in Ant 1.6, permit <xmlcatalog> entries to make checking of "external" links
058: // work better in the case of cross-links between APIs
059:
060: /** Task to check for broken links in HTML.
061: * Note that this is a matching task and you must give it a list of things to match.
062: * The Java VM's configured HTTP proxy will be used (${http.proxyHost} and ${http.proxyPort}).
063: * @author Jesse Glick
064: */
065: public class CheckLinks extends MatchingTask {
066:
067: private File basedir;
068: private boolean checkexternal = true;
069: private boolean checkspaces = true;
070: private boolean checkforbidden = true;
071: private List<Mapper> mappers = new LinkedList<Mapper>();
072: private List<Filter> filters = new ArrayList<Filter>();
073: private File report;
074:
075: /** Set whether to check external links (absolute URLs).
076: * Local relative links are always checked.
077: * By default, external links are checked.
078: */
079: public void setCheckexternal(boolean ce) {
080: checkexternal = ce;
081: }
082:
083: /** False if spaces in URLs shall not be reported. Default to true.
084: */
085: public void setCheckspaces(boolean s) {
086: checkspaces = s;
087: }
088:
089: /** Allows to disable check for forbidden links.
090: */
091: public void setCheckforbidden(boolean s) {
092: checkforbidden = s;
093: }
094:
095: /** Set the base directory from which to scan files.
096: */
097: public void setBasedir(File basedir) {
098: this .basedir = basedir;
099: }
100:
101: public Filter createFilter() {
102: Filter f = new Filter();
103: filters.add(f);
104: return f;
105: }
106:
107: /**
108: * If set, create a JUnit-style report on failure, rather than halting the build.
109: */
110: public void setReport(File report) {
111: this .report = report;
112: }
113:
114: /**
115: * Add a mapper to translate file names to the "originals".
116: */
117: public Mapper createMapper() {
118: Mapper m = new Mapper(getProject());
119: mappers.add(m);
120: return m;
121: }
122:
123: public void execute() throws BuildException {
124: if (basedir == null)
125: throw new BuildException(
126: "Must specify the basedir attribute");
127: FileScanner scanner = getDirectoryScanner(basedir);
128: scanner.scan();
129: String message = "Scanning for broken links in " + basedir
130: + " ...";
131: if (!checkexternal)
132: message += " (external URLs will be skipped)";
133: log(message);
134: String[] files = scanner.getIncludedFiles();
135: Set<URI> okurls = new HashSet<URI>(1000);
136: Set<URI> badurls = new HashSet<URI>(100);
137: Set<URI> cleanurls = new HashSet<URI>(100);
138: List<String> errors = new ArrayList<String>();
139: for (int i = 0; i < files.length; i++) {
140: File file = new File(basedir, files[i]);
141: URI fileurl = file.toURI();
142: log("Scanning " + file, Project.MSG_VERBOSE);
143: try {
144: scan(this , null, null, getLocation().toString(), "",
145: fileurl, okurls, badurls, cleanurls,
146: checkexternal, checkspaces, checkforbidden, 1,
147: mappers, filters, errors);
148: } catch (IOException ioe) {
149: throw new BuildException("Could not scan " + file
150: + ": " + ioe, ioe, getLocation());
151: }
152: }
153: String testMessage = null;
154: if (!errors.isEmpty()) {
155: StringBuilder b = new StringBuilder(
156: "There were broken links");
157: for (String error : errors) {
158: b.append("\n" + error);
159: }
160: testMessage = b.toString();
161: }
162: JUnitReportWriter.writeReport(this , report, Collections
163: .singletonMap("testBrokenLinks", testMessage));
164: }
165:
166: private static Pattern hrefOrAnchor = Pattern
167: .compile(
168: "<(a|img)(\\s+shape=\"rect\")?\\s+(href|name|src)=\"([^\"#]*)(#[^\"]+)?\"(\\s+shape=\"rect\")?\\s*/?>",
169: Pattern.CASE_INSENSITIVE);
170: private static Pattern lineBreak = Pattern.compile("^",
171: Pattern.MULTILINE);
172:
173: /**
174: * Scan for broken links.
175: * @param task an Ant task to associate with this
176: * @param referrer the referrer file path (or full URL if not file:)
177: * @param referrerLocation the location in the referrer, e.g. ":38:12", or "" if unavailable
178: * @param u the URI to check
179: * @param okurls a set of URIs known to be fully checked (including all anchored variants etc.)
180: * @param badurls a set of URIs known to be bogus
181: * @param cleanurls a set of (base) URIs known to have had their contents checked
182: * @param checkexternal if true, check external links (all protocols besides file:)
183: * @param recurse one of:
184: * 0 - just check that it can be opened;
185: * 1 - check also that any links from it can be opened;
186: * 2 - recurse
187: * @param mappers a list of Mappers to apply to get source files from HTML files
188: */
189: public static void scan(Task task, ClassLoader globalClassLoader,
190: java.util.Map classLoaderMap, String referrer,
191: String referrerLocation, URI u, Set<URI> okurls,
192: Set<URI> badurls, Set<URI> cleanurls,
193: boolean checkexternal, boolean checkspaces,
194: boolean checkforbidden, int recurse, List<Mapper> mappers,
195: List<String> errors) throws IOException {
196: scan(task, globalClassLoader, classLoaderMap, referrer,
197: referrerLocation, u, okurls, badurls, cleanurls,
198: checkexternal, checkspaces, checkforbidden, recurse,
199: mappers, Collections.<Filter> emptyList(), errors);
200: }
201:
202: private static void scan(Task task, ClassLoader globalClassLoader,
203: java.util.Map classLoaderMap, String referrer,
204: String referrerLocation, URI u, Set<URI> okurls,
205: Set<URI> badurls, Set<URI> cleanurls,
206: boolean checkexternal, boolean checkspaces,
207: boolean checkforbidden, int recurse, List<Mapper> mappers,
208: List<Filter> filters, List<String> errors)
209: throws IOException {
210: //task.log("scan: u=" + u + " referrer=" + referrer + " okurls=" + okurls + " badurls=" + badurls + " cleanurls=" + cleanurls + " recurse=" + recurse, Project.MSG_DEBUG);
211: //System.out.println("");
212: //System.out.println("CheckLinks.scan ref: " + referrer);
213: //System.out.println("CheckLinks.scan u: " + u);
214: if (okurls.contains(u) && recurse == 0) {
215: // Yes it is OK.
216: return;
217: }
218: //Check if referrer is jar file and if u is relative if yes make path absolute
219: if (referrer.startsWith("jar:file:") && (u.getScheme() == null)
220: && !u.toString().startsWith("#")) {
221: if (u.toString().length() == 0) {
222: System.out
223: .println("Invalid URL: Empty URL referred from: "
224: + referrer);
225: return;
226: }
227: if (!u.isAbsolute()) {
228: //This is to make inner jar path after ! absolute.
229: //It uses java.io.File to remove ../ sequences but as file path
230: //on Windows is different from inner jar path it requires some
231: //'fix' on Windows.
232: int pos = referrer.indexOf("!");
233: if (pos != -1) {
234: String base = referrer.substring(0, pos + 1);
235: String path1 = referrer.substring(pos + 1, referrer
236: .length());
237: //System.out.println("base:" + base);
238: //System.out.println("path1:" + path1);
239: File f1 = new File(path1);
240: File p = f1.getParentFile();
241: File f2 = new File(p, u.getPath());
242: //System.out.println("f1:" + f1);
243: //System.out.println("f2:" + f2);
244: String path2 = null;
245: try {
246: path2 = f2.getCanonicalPath();
247: } catch (IOException e) {
248: e.printStackTrace();
249: }
250: //Ugly hack to get jar inner path from Win FS path
251: //System.out.println("path2:" + path2);
252: if (System.getProperty("os.name").startsWith(
253: "Windows")) {
254: path2 = path2.substring(2).replace('\\', '/');
255: }
256: try {
257: u = new URI(base + path2);
258: } catch (URISyntaxException ex) {
259: ex.printStackTrace();
260: }
261: //System.out.println("u:" + u);
262: }
263: }
264: }
265: URI base;
266: if (u.toString().startsWith("#")) {
267: try {
268: u = new URI(referrer + u.toString());
269: } catch (URISyntaxException ex) {
270: ex.printStackTrace();
271: }
272: }
273: String b = u.toString();
274: int i = b.lastIndexOf('#');
275: if (i != -1) {
276: b = b.substring(0, i);
277: }
278: try {
279: base = new URI(b);
280: //base = new URI(u.getScheme(), u.getUserInfo(), u.getHost(), u.getPort(), u.toURL().getPath(), u.getQuery(), /*fragment*/null);
281: } catch (URISyntaxException e) {
282: e.printStackTrace();
283: throw new Error(e);
284: }
285: String frag = u.getFragment();
286: String basepath = base.toString();
287: if ("file".equals(base.getScheme())) {
288: try {
289: basepath = new File(base).getAbsolutePath();
290: } catch (IllegalArgumentException e) {
291: errors.add(normalize(referrer, mappers)
292: + referrerLocation + ": malformed URL: " + base
293: + " (" + e.getLocalizedMessage() + ")");
294: }
295: }
296: //task.log("scan: base=" + base + " frag=" + frag, Project.MSG_DEBUG);
297: if (badurls.contains(u) || badurls.contains(base)) {
298: errors.add(normalize(referrer, mappers) + referrerLocation
299: + ": broken link (already reported): " + u);
300: return;
301: }
302:
303: if (checkforbidden) {
304: for (Filter f : filters) {
305: Boolean decision = f.isOk(u);
306: if (Boolean.TRUE.equals(decision)) {
307: break;
308: }
309: if (Boolean.FALSE.equals(decision)) {
310: errors.add(normalize(referrer, mappers)
311: + referrerLocation + ": forbidden link: "
312: + base);
313: //System.out.println("badurls ADD1 base:" + base);
314: badurls.add(base);
315: //System.out.println("badurls ADD1 u:" + u);
316: badurls.add(u);
317: return;
318: }
319: }
320: }
321:
322: if (!checkexternal && !"file".equals(u.getScheme())
323: && !"jar".equals(u.getScheme())
324: && !"nbdocs".equals(u.getScheme())) {
325: task.log("Skipping external link: " + base,
326: Project.MSG_VERBOSE);
327: cleanurls.add(base);
328: okurls.add(base);
329: okurls.add(u);
330: return;
331: }
332:
333: //Translate nbdocs protocol to jar protocol
334: if ("nbdocs".equals(u.getScheme())) {
335: //If called from CheckHelpSets following params are not set =>
336: //we cannot check nbdocs URLs.
337: if ((classLoaderMap == null) || (globalClassLoader == null)) {
338: return;
339: }
340: //System.out.println("");
341: //System.out.println("r:" + referrer);
342: //System.out.println("u:" + u);
343: //System.out.println("u.getScheme:" + u.getScheme());
344: //System.out.println("u.getHost:" + u.getHost());
345: //System.out.println("u.toURL.getHost:" + u.toURL().getHost());
346: //System.out.println("u.getPath:" + u.getPath());
347: //If no module base name is specified as host name check if given
348: //resource is available in current module or globally.
349: if (u.toURL().getHost() == null) {
350: errors.add("Missing host in nbdocs protocol URL. URI: "
351: + u);
352: errors.add("Referrer: " + referrer);
353: String name = u.getPath();
354: //Strip leading "/" as findResource does not work when leading slash is present
355: if (name.startsWith("/")) {
356: name = name.substring(1, name.length());
357: //System.out.println("name:" + name);
358: }
359: URL res;
360: res = globalClassLoader.getResource(name);
361: //System.out.println("res:" + res);
362: if (res != null) {
363: try {
364: base = res.toURI();
365: u = base;
366: basepath = base.toString();
367: //System.out.println("base:" + base);
368: } catch (URISyntaxException ex) {
369: ex.printStackTrace();
370: }
371: //Try to find out module for link
372: Set keySet = classLoaderMap.keySet();
373: for (Iterator it1 = keySet.iterator(); it1
374: .hasNext();) {
375: Object key = it1.next();
376: URLClassLoader cl = (URLClassLoader) classLoaderMap
377: .get(key);
378: if (cl != null) {
379: URL moduleRes = cl.findResource(name);
380: if (moduleRes != null) {
381: task.log("INFO: Link found in module:"
382: + key + ". URI: " + u,
383: Project.MSG_INFO);
384: task.log("INFO: Referrer: " + referrer,
385: Project.MSG_INFO);
386: break;
387: }
388: }
389: }
390: } else {
391: errors.add("Link not found globally. URI: " + u);
392: errors.add("Referrer: " + referrer);
393: return;
394: }
395: //System.out.println("res:" + res);
396: } else {
397: String name = u.getPath();
398: //Strip leading "/" as findResource does not work when leading slash is present
399: if (name.startsWith("/")) {
400: name = name.substring(1, name.length());
401: //System.out.println("name:" + name);
402: }
403: URL res = null;
404: URLClassLoader moduleClassLoader = (URLClassLoader) classLoaderMap
405: .get(u.toURL().getHost());
406: //Log warning
407: if (moduleClassLoader == null) {
408: errors
409: .add("Module "
410: + u.toURL().getHost()
411: + " not found among modules containing helpsets. URI: "
412: + u);
413: errors.add("Referrer: " + referrer);
414: }
415: if (moduleClassLoader != null) {
416: res = moduleClassLoader.findResource(name);
417: //System.out.println("res1:" + res);
418: if (res != null) {
419: try {
420: base = res.toURI();
421: u = base;
422: basepath = base.toString();
423: //System.out.println("base:" + base);
424: } catch (URISyntaxException ex) {
425: ex.printStackTrace();
426: }
427: }
428: }
429: if (res == null) {
430: if (moduleClassLoader != null) {
431: errors.add("Link not found in module "
432: + u.toURL().getHost() + " URI: " + u);
433: errors.add("Referrer: " + referrer);
434: }
435: res = globalClassLoader.getResource(name);
436: //System.out.println("res2:" + res);
437: if (res != null) {
438: try {
439: base = res.toURI();
440: u = base;
441: basepath = base.toString();
442: //System.out.println("base:" + base);
443: } catch (URISyntaxException ex) {
444: ex.printStackTrace();
445: }
446: //Try to find out module for link
447: Set keySet = classLoaderMap.keySet();
448: for (Iterator it1 = keySet.iterator(); it1
449: .hasNext();) {
450: Object key = it1.next();
451: URLClassLoader cl = (URLClassLoader) classLoaderMap
452: .get(key);
453: if (cl != null) {
454: URL moduleRes = cl.findResource(name);
455: if (moduleRes != null) {
456: task.log(
457: "INFO: Link found in module:"
458: + key + ". URI: "
459: + u,
460: Project.MSG_INFO);
461: task.log("INFO: Referrer: "
462: + referrer,
463: Project.MSG_INFO);
464: break;
465: }
466: }
467: }
468: } else {
469: errors
470: .add("Link not found globally. URI: "
471: + u);
472: errors.add("Referrer: " + referrer);
473: return;
474: }
475: }
476: }
477: }
478: task.log(
479: "Checking " + u + " (recursion level " + recurse + ")",
480: Project.MSG_VERBOSE);
481: String content;
482: String mimeType;
483: try {
484: // XXX for protocol 'file', could more efficiently use a memmapped char buffer
485: URLConnection conn = base.toURL().openConnection();
486: //System.out.println("CALL OF connect");
487: conn.connect();
488: mimeType = conn.getContentType();
489: InputStream is = conn.getInputStream();
490: String enc = conn.getContentEncoding();
491: if (enc == null) {
492: enc = "UTF-8";
493: }
494: try {
495: ByteArrayOutputStream baos = new ByteArrayOutputStream();
496: int read;
497: byte[] buf = new byte[4096];
498: while ((read = is.read(buf)) != -1) {
499: baos.write(buf, 0, read);
500: }
501: content = baos.toString(enc);
502: } finally {
503: is.close();
504: }
505: } catch (IOException ioe) {
506: errors.add(normalize(referrer, mappers) + referrerLocation
507: + ": Broken link: " + base);
508: task.log("WARNING: URI: " + u, Project.MSG_VERBOSE);
509: task.log("ERROR: " + ioe, Project.MSG_VERBOSE);
510: badurls.add(base);
511: badurls.add(u);
512: //Log exception stack trace only in verbose mode
513: StringWriter sw = new StringWriter(500);
514: PrintWriter pw = new PrintWriter(sw);
515: ioe.printStackTrace(pw);
516: task.log(sw.toString(), Project.MSG_VERBOSE);
517: return;
518: } catch (NullPointerException exc) {
519: errors.add("NPE Link referred from: "
520: + normalize(referrer, mappers) + referrerLocation
521: + " Broken link: " + base);
522: task.log("WARNING: URI: " + u);
523: task.log("ERROR: " + exc, Project.MSG_WARN);
524: badurls.add(base);
525: badurls.add(u);
526: //Log exception stack trace only in verbose mode
527: StringWriter sw = new StringWriter(500);
528: PrintWriter pw = new PrintWriter(sw);
529: exc.printStackTrace(pw);
530: task.log(sw.toString(), Project.MSG_WARN);
531: return;
532: }
533: okurls.add(base);
534: // map from other URIs (hrefs) to line/col info where they occur in this file (format: ":1:2")
535: Map<URI, String> others = null;
536: if (recurse > 0 && cleanurls.add(base)) {
537: others = new HashMap<URI, String>(100);
538: }
539: if (recurse == 0 && frag == null) {
540: // That is all we wanted to check.
541: return;
542: }
543: if ("text/html".equals(mimeType)) {
544: task.log("Parsing " + base, Project.MSG_VERBOSE);
545: Matcher m = hrefOrAnchor.matcher(content);
546: Set<String> names = new HashSet<String>(100); // Set<String>
547: while (m.find()) {
548: // Get the stuff involved:
549: String type = m.group(3);
550: if (type.equalsIgnoreCase("name")) {
551: // We have an anchor, therefore refs to it are valid.
552: String name = unescape(m.group(4));
553: if (names.add(name)) {
554: try {
555: //URI does not handle jar:file: protocol
556: //okurls.add(new URI(base.getScheme(), base.getUserInfo(), base.getHost(), base.getPort(), base.getPath(), base.getQuery(), /*fragment*/name));
557: okurls.add(new URI(base + "#"
558: + name.replaceAll(" ", "%20")));
559: } catch (URISyntaxException e) {
560: errors.add(normalize(basepath, mappers)
561: + findLocation(content, m.start(4))
562: + ": bad anchor name: "
563: + e.getMessage());
564: }
565: } else if (recurse == 1) {
566: errors.add(normalize(basepath, mappers)
567: + findLocation(content, m.start(4))
568: + ": duplicate anchor name: " + name);
569: }
570: } else {
571: // A link to some other document: href=, src=.
572:
573: // check whether this URL is not commented out
574: int previousCommentStart = content.lastIndexOf(
575: "<!--", m.start(0));
576: int previousCommentEnd = content.lastIndexOf("-->",
577: m.start(0));
578: boolean commentedOut = false;
579: if (previousCommentEnd < previousCommentStart) {
580: // comment start is there and end is before it
581: commentedOut = true;
582: }
583:
584: if (others != null && !commentedOut) {
585: String otherbase = unescape(m.group(4));
586: String otheranchor = unescape(m.group(5));
587: String uri = (otheranchor == null) ? otherbase
588: : otherbase + otheranchor;
589: String location = findLocation(content, m
590: .start(4));
591: String fixedUri;
592: if (uri.indexOf(' ') != -1) {
593: fixedUri = uri.replaceAll(" ", "%20");
594: if (checkspaces) {
595: errors
596: .add(normalize(basepath,
597: mappers)
598: + location
599: + ": spaces in URIs should be encoded as \"%20\": "
600: + uri);
601: }
602: } else {
603: fixedUri = uri;
604: }
605: try {
606: URI relUri = new URI(fixedUri);
607: if (!relUri.isOpaque()) {
608: URI o = base.resolve(relUri)
609: .normalize();
610: //task.log("href: " + o);
611: if (!others.containsKey(o)) {
612: // Only keep location info for first reference.
613: others.put(o, location);
614: }
615: } // else mailto: or similar
616: } catch (URISyntaxException e) {
617: // Message should contain the URI.
618: errors.add(normalize(basepath, mappers)
619: + location + ": bad relative URI: "
620: + e.getMessage());
621: }
622: } // else we are only checking that this one has right anchors
623: }
624: }
625: } else {
626: task.log("Not checking contents of " + base,
627: Project.MSG_VERBOSE);
628: }
629: if (!okurls.contains(u)) {
630: errors.add(normalize(referrer, mappers) + referrerLocation
631: + ": broken link: " + u);
632: badurls.add(u); // #97784
633: }
634: if (others != null) {
635: Iterator it = others.entrySet().iterator();
636: while (it.hasNext()) {
637: Map.Entry entry = (Map.Entry) it.next();
638: URI other = (URI) entry.getKey();
639: String location = (String) entry.getValue();
640: //System.out.println("CALL OF scan basepath:" + basepath + " location:" + location + " other:" + other);
641: scan(task, globalClassLoader, classLoaderMap, basepath,
642: location, other, okurls, badurls, cleanurls,
643: checkexternal, checkspaces, checkforbidden,
644: recurse == 1 ? 0 : 2, mappers, filters, errors);
645: }
646: }
647: }
648:
649: private static String normalize(String path, List<Mapper> mappers)
650: throws IOException {
651: try {
652: for (Mapper m : mappers) {
653: String[] nue = m.getImplementation().mapFileName(path);
654: if (nue != null) {
655: for (int i = 0; i < nue.length; i++) {
656: File f = new File(nue[i]);
657: if (f.isFile()) {
658: return new File(f.toURI().normalize())
659: .getAbsolutePath();
660: }
661: }
662: }
663: }
664: return path;
665: } catch (BuildException e) {
666: throw new IOException(e.toString());
667: }
668: }
669:
670: private static String unescape(String text) {
671: if (text == null) {
672: return null;
673: }
674: int pos = 0;
675: int search;
676: while ((search = text.indexOf('&', pos)) != -1) {
677: int semi = text.indexOf(';', search + 1);
678: if (semi == -1) {
679: // Unterminated &... leave rest as is??
680: return text;
681: }
682: String entity = text.substring(search + 1, semi);
683: String repl;
684: if (entity.equals("amp")) {
685: repl = "&";
686: } else if (entity.equals("quot")) {
687: repl = "\"";
688: } else if (entity.equals("lt")) {
689: repl = "<";
690: } else if (entity.equals("gt")) {
691: repl = ">";
692: } else if (entity.equals("apos")) {
693: repl = "'";
694: } else {
695: // ???
696: pos = semi + 1;
697: continue;
698: }
699: text = text.substring(0, search) + repl
700: + text.substring(semi + 1);
701: pos = search + repl.length();
702: }
703: return text;
704: }
705:
706: private static String findLocation(CharSequence content, int pos) {
707: Matcher lbm = lineBreak.matcher(content);
708: int line = 0;
709: int col = 1;
710: while (lbm.find()) {
711: if (lbm.start() <= pos) {
712: line++;
713: col = pos - lbm.start() + 1;
714: } else {
715: break;
716: }
717: }
718: return ":" + line + ":" + col;
719: }
720:
721: public final class Filter extends Object {
722: private Boolean accept;
723: private Pattern pattern;
724:
725: public void setAccept(boolean a) {
726: accept = Boolean.valueOf(a);
727: }
728:
729: public void setPattern(String s) {
730: pattern = Pattern.compile(s, Pattern.CASE_INSENSITIVE);
731: }
732:
733: /** Checks whether a URI is ok.
734: * @return null if not applicable, Boolean.TRUE if the URL is accepted, Boolean.FALSE if not
735: */
736: final Boolean isOk(URI u) throws BuildException {
737: if (accept == null) {
738: throw new BuildException(
739: "Each filter must have accept attribute");
740: }
741: if (pattern == null) {
742: throw new BuildException(
743: "Each filter must have pattern attribute");
744: }
745:
746: if (pattern.matcher(u.toString()).matches()) {
747: log("Matched " + u + " accepted: " + accept,
748: org.apache.tools.ant.Project.MSG_VERBOSE);
749: return accept;
750: }
751: return null;
752: }
753: }
754: }
|