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: // See #13931.
043: package org.netbeans.nbbuild;
044:
045: import java.io.ByteArrayInputStream;
046: import java.io.File;
047: import java.net.URI;
048: import java.net.URL;
049: import java.util.ArrayList;
050: import java.util.Collections;
051: import java.util.Enumeration;
052: import java.util.HashSet;
053: import java.util.Hashtable;
054: import java.util.Iterator;
055: import java.util.List;
056: import java.util.Locale;
057: import java.util.Set;
058: import javax.help.HelpSet;
059: import javax.help.IndexItem;
060: import javax.help.IndexView;
061: import javax.help.NavigatorView;
062: import javax.help.TOCItem;
063: import javax.help.TOCView;
064: import javax.help.TreeItem;
065: import javax.help.TreeItemFactory;
066: import javax.swing.tree.DefaultMutableTreeNode;
067: import javax.xml.parsers.SAXParser;
068: import javax.xml.parsers.SAXParserFactory;
069: import org.apache.tools.ant.BuildException;
070: import org.apache.tools.ant.FileScanner;
071: import org.apache.tools.ant.Location;
072: import org.apache.tools.ant.Project;
073: import org.apache.tools.ant.Task;
074: import org.apache.tools.ant.types.FileSet;
075: import org.apache.tools.ant.types.Mapper;
076: import org.xml.sax.Attributes;
077: import org.xml.sax.InputSource;
078: import org.xml.sax.SAXException;
079: import org.xml.sax.helpers.DefaultHandler;
080:
081: /** Task to check various aspects of JavaHelp helpsets.
082: * <ol>
083: * <li>General parsability as far as JavaHelp is concerned.
084: * <li>Map IDs are not duplicated.
085: * <li>Map IDs point to real HTML files (and anchors where specified).
086: * <li>TOC/Index navigators refer to real map IDs.
087: * <li>HTML links in reachable HTML files point to valid places (including anchors).
088: * </ol>
089: * @author Jesse Glick
090: */
091: public class CheckHelpSets extends Task {
092:
093: private List<FileSet> filesets = new ArrayList<FileSet>();
094:
095: /** Add a fileset with one or more helpsets in it.
096: * <strong>Only</strong> the <samp>*.hs</samp> should match!
097: * All other files will be found from it.
098: */
099: public void addFileset(FileSet fs) {
100: filesets.add(fs);
101: }
102:
103: public void execute() throws BuildException {
104: Iterator it = filesets.iterator();
105: while (it.hasNext()) {
106: FileSet fs = (FileSet) it.next();
107: FileScanner scanner = fs.getDirectoryScanner(getProject());
108: File dir = scanner.getBasedir();
109: String[] files = scanner.getIncludedFiles();
110: for (int i = 0; i < files.length; i++) {
111: File helpset = new File(dir, files[i]);
112: try {
113: checkHelpSet(helpset);
114: } catch (BuildException be) {
115: throw be;
116: } catch (Exception e) {
117: throw new BuildException("Error checking helpset",
118: e, new Location(helpset.getAbsolutePath()));
119: }
120: }
121: }
122: }
123:
124: private void checkHelpSet(File hsfile) throws Exception {
125: log("Checking helpset: " + hsfile);
126: HelpSet hs = new HelpSet(null, hsfile.toURI().toURL());
127: javax.help.Map map = hs.getCombinedMap();
128: log("Parsed helpset, checking map IDs in TOC/Index navigators...");
129: NavigatorView[] navs = hs.getNavigatorViews();
130: for (int i = 0; i < navs.length; i++) {
131: String name = navs[i].getName();
132: File navfile = new File(hsfile.getParentFile(),
133: (String) navs[i].getParameters().get("data"));
134: if (!navfile.exists())
135: throw new BuildException("Navigator " + name
136: + " not found", new Location(navfile
137: .getAbsolutePath()));
138: if (navs[i] instanceof IndexView) {
139: log("Checking index navigator " + name,
140: Project.MSG_VERBOSE);
141: IndexView.parse(navfile.toURI().toURL(), hs, Locale
142: .getDefault(), new VerifyTIFactory(hs, map,
143: navfile, false));
144: } else if (navs[i] instanceof TOCView) {
145: log("Checking TOC navigator " + name,
146: Project.MSG_VERBOSE);
147: TOCView.parse(navfile.toURI().toURL(), hs, Locale
148: .getDefault(), new VerifyTIFactory(hs, map,
149: navfile, true));
150: } else {
151: log("Skipping non-TOC/Index view: " + name,
152: Project.MSG_VERBOSE);
153: }
154: }
155: log("Checking for duplicate map IDs...");
156: HelpSet.parse(hsfile.toURI().toURL(), null,
157: new VerifyHSFactory());
158: log("Checking links from help map and between HTML files...");
159: Enumeration e = map.getAllIDs();
160: Set<URI> okurls = new HashSet<URI>(1000);
161: Set<URI> badurls = new HashSet<URI>(1000);
162: Set<URI> cleanurls = new HashSet<URI>(1000);
163: while (e.hasMoreElements()) {
164: javax.help.Map.ID id = (javax.help.Map.ID) e.nextElement();
165: URL u = map.getURLFromID(id);
166: if (u == null) {
167: throw new BuildException("Bogus map ID: " + id.id,
168: new Location(hsfile.getAbsolutePath()));
169: }
170: log("Checking ID " + id.id, Project.MSG_VERBOSE);
171: List<String> errors = new ArrayList<String>();
172: CheckLinks.scan(this , null, null, id.id, "", u.toURI(),
173: okurls, badurls, cleanurls, false, false, false, 2,
174: Collections.<Mapper> emptyList(), errors);
175: for (String error : errors) {
176: log(error, Project.MSG_WARN);
177: }
178: }
179: }
180:
181: private final class VerifyTIFactory implements TreeItemFactory {
182:
183: private final HelpSet hs;
184: private final javax.help.Map map;
185: private final File navfile;
186: private final boolean toc;
187:
188: public VerifyTIFactory(HelpSet hs, javax.help.Map map,
189: File navfile, boolean toc) {
190: this .hs = hs;
191: this .map = map;
192: this .navfile = navfile;
193: this .toc = toc;
194: }
195:
196: // The useful method:
197:
198: public TreeItem createItem(String str, Hashtable hashtable,
199: HelpSet helpSet, Locale locale) {
200: String target = (String) hashtable.get("target");
201: if (target != null) {
202: if (!map.isValidID(target, hs)) {
203: log(navfile + ": invalid map ID: " + target,
204: Project.MSG_WARN);
205: } else {
206: log("OK map ID: " + target, Project.MSG_VERBOSE);
207: }
208: }
209: return createItem();
210: }
211:
212: // Filler methods:
213:
214: public Enumeration listMessages() {
215: return Collections.enumeration(Collections
216: .<String> emptyList());
217: }
218:
219: public void processPI(HelpSet helpSet, String str, String str2) {
220: }
221:
222: public void reportMessage(String str, boolean param) {
223: log(str, param ? Project.MSG_VERBOSE : Project.MSG_WARN);
224: }
225:
226: public void processDOCTYPE(String str, String str1, String str2) {
227: }
228:
229: public void parsingStarted(URL uRL) {
230: }
231:
232: public DefaultMutableTreeNode parsingEnded(
233: DefaultMutableTreeNode defaultMutableTreeNode) {
234: return defaultMutableTreeNode;
235: }
236:
237: public TreeItem createItem() {
238: if (toc) {
239: return new TOCItem();
240: } else {
241: return new IndexItem();
242: }
243: }
244:
245: }
246:
247: private final class VerifyHSFactory extends
248: HelpSet.DefaultHelpSetFactory {
249:
250: private Set<String> ids = new HashSet<String>(1000);
251:
252: public void processMapRef(HelpSet hs, Hashtable attrs) {
253: try {
254: URL map = new URL(hs.getHelpSetURL(), (String) attrs
255: .get("location"));
256: SAXParserFactory factory = SAXParserFactory
257: .newInstance();
258: factory.setValidating(false);
259: factory.setNamespaceAware(false);
260: SAXParser parser = factory.newSAXParser();
261: parser.parse(new InputSource(map.toExternalForm()),
262: new Handler(map.getFile()));
263: } catch (Exception e) {
264: e.printStackTrace();
265: }
266: }
267:
268: private final class Handler extends DefaultHandler {
269:
270: private final String map;
271:
272: public Handler(String map) {
273: this .map = map;
274: }
275:
276: public void startElement(String uri, String lname,
277: String name, Attributes attributes)
278: throws SAXException {
279: if (name.equals("mapID")) {
280: String target = attributes.getValue("target");
281: if (target != null) {
282: if (ids.add(target)) {
283: log("Found map ID: " + target,
284: Project.MSG_DEBUG);
285: } else {
286: log(map + ": duplicated ID: " + target,
287: Project.MSG_WARN);
288: }
289: }
290: }
291: }
292:
293: public InputSource resolveEntity(String pub, String sys)
294: throws SAXException {
295: if (pub
296: .equals("-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN")
297: || pub
298: .equals("-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN")) {
299: // Ignore.
300: return new InputSource(new ByteArrayInputStream(
301: new byte[0]));
302: } else {
303: return null;
304: }
305: }
306:
307: }
308:
309: }
310:
311: }
|