blob: 2d87e8949ec0f62da9789b61fc2a7c367296823d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// See #13931.
package org.netbeans.nbbuild;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.help.HelpSet;
import javax.help.IndexItem;
import javax.help.IndexView;
import javax.help.NavigatorView;
import javax.help.TOCItem;
import javax.help.TOCView;
import javax.help.TreeItem;
import javax.help.TreeItemFactory;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.FileScanner;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Mapper;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/** Task to check various aspects of JavaHelp helpsets.
* <ol>
* <li>General parsability as far as JavaHelp is concerned.
* <li>Map IDs are not duplicated.
* <li>Map IDs point to real HTML files (and anchors where specified).
* <li>TOC/Index navigators refer to real map IDs.
* <li>HTML links in reachable HTML files point to valid places (including anchors).
* </ol>
* @author Jesse Glick
*/
public class CheckHelpSets extends Task {
private List<FileSet> filesets = new ArrayList<>();
/** Add a fileset with one or more helpsets in it.
* <strong>Only</strong> the <samp>*.hs</samp> should match!
* All other files will be found from it.
*/
public void addFileset(FileSet fs) {
filesets.add(fs);
}
public void execute() throws BuildException {
for(FileSet fs: filesets) {
FileScanner scanner = fs.getDirectoryScanner(getProject());
File dir = scanner.getBasedir();
String[] files = scanner.getIncludedFiles();
for (int i = 0; i < files.length; i++) {
File helpset = new File(dir, files[i]);
try {
checkHelpSet(helpset);
} catch (BuildException be) {
throw be;
} catch (Exception e) {
throw new BuildException("Error checking helpset", e, new Location(helpset.getAbsolutePath()));
}
}
}
}
private void checkHelpSet(File hsfile) throws Exception {
log("Checking helpset: " + hsfile);
HelpSet hs = new HelpSet(null, hsfile.toURI().toURL());
javax.help.Map map = hs.getCombinedMap();
log("Parsed helpset, checking map IDs in TOC/Index navigators...");
NavigatorView[] navs = hs.getNavigatorViews();
for (int i = 0; i < navs.length; i++) {
String name = navs[i].getName();
File navfile = new File(hsfile.getParentFile(), (String)navs[i].getParameters().get("data"));
if (! navfile.exists()) throw new BuildException("Navigator " + name + " not found", new Location(navfile.getAbsolutePath()));
if (navs[i] instanceof IndexView) {
log("Checking index navigator " + name, Project.MSG_VERBOSE);
IndexView.parse(navfile.toURI().toURL(), hs, Locale.getDefault(), new VerifyTIFactory(hs, map, navfile, false));
} else if (navs[i] instanceof TOCView) {
log("Checking TOC navigator " + name, Project.MSG_VERBOSE);
TOCView.parse(navfile.toURI().toURL(), hs, Locale.getDefault(), new VerifyTIFactory(hs, map, navfile, true));
} else {
log("Skipping non-TOC/Index view: " + name, Project.MSG_VERBOSE);
}
}
log("Checking for duplicate map IDs...");
HelpSet.parse(hsfile.toURI().toURL(), null, new VerifyHSFactory());
log("Checking links from help map and between HTML files...");
@SuppressWarnings("unchecked")
Enumeration<javax.help.Map.ID> e = map.getAllIDs();
Set<URI> okurls = new HashSet<>(1000);
Set<URI> badurls = new HashSet<>(1000);
Set<URI> cleanurls = new HashSet<>(1000);
while (e.hasMoreElements()) {
javax.help.Map.ID id = e.nextElement();
URL u = map.getURLFromID(id);
if (u == null) {
throw new BuildException("Bogus map ID: " + id.id, new Location(hsfile.getAbsolutePath()));
}
log("Checking ID " + id.id, Project.MSG_VERBOSE);
List<String> errors = new ArrayList<>();
CheckLinks.scan(this, null, null, id.id, "",
u.toURI(), okurls, badurls, cleanurls, false, false, false, 2,
Collections.<Mapper>emptyList(), errors);
for (String error : errors) {
log(error, Project.MSG_WARN);
}
}
}
private final class VerifyTIFactory implements TreeItemFactory {
private final HelpSet hs;
private final javax.help.Map map;
private final File navfile;
private final boolean toc;
public VerifyTIFactory(HelpSet hs, javax.help.Map map, File navfile, boolean toc) {
this.hs = hs;
this.map = map;
this.navfile = navfile;
this.toc = toc;
}
// The useful method:
@Override
public TreeItem createItem(String str, @SuppressWarnings("rawtypes") Hashtable hashtable, HelpSet helpSet, Locale locale) {
String target = (String)hashtable.get("target");
if (target != null) {
if (! map.isValidID(target, hs)) {
log(navfile + ": invalid map ID: " + target, Project.MSG_WARN);
} else {
log("OK map ID: " + target, Project.MSG_VERBOSE);
}
}
return createItem();
}
// Filler methods:
@Override
public @SuppressWarnings("rawtypes") Enumeration listMessages() {
return Collections.enumeration(Collections.<String>emptyList());
}
@Override
public void processPI(HelpSet helpSet, String str, String str2) {
}
@Override
public void reportMessage(String str, boolean param) {
log(str, param ? Project.MSG_VERBOSE : Project.MSG_WARN);
}
@Override
public void processDOCTYPE(String str, String str1, String str2) {
}
@Override
public void parsingStarted(URL uRL) {
}
public DefaultMutableTreeNode parsingEnded(DefaultMutableTreeNode defaultMutableTreeNode) {
return defaultMutableTreeNode;
}
@Override
public TreeItem createItem() {
if (toc) {
return new TOCItem();
} else {
return new IndexItem();
}
}
}
private final class VerifyHSFactory extends HelpSet.DefaultHelpSetFactory {
private final Set<String> ids = new HashSet<>(1000);
@Override
public void processMapRef(HelpSet hs, @SuppressWarnings("rawtypes") Hashtable attrs) {
try {
URL map = new URL(hs.getHelpSetURL(), (String)attrs.get("location"));
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(false);
SAXParser parser = factory.newSAXParser();
parser.parse(new InputSource(map.toExternalForm()), new Handler(map.getFile()));
} catch (Exception e) {
e.printStackTrace();
}
}
private final class Handler extends DefaultHandler {
private final String map;
public Handler(String map) {
this.map = map;
}
public void startElement(String uri, String lname, String name, Attributes attributes) throws SAXException {
if (name.equals("mapID")) {
String target = attributes.getValue("target");
if (target != null) {
if (ids.add(target)) {
log("Found map ID: " + target, Project.MSG_DEBUG);
} else {
log(map + ": duplicated ID: " + target, Project.MSG_WARN);
}
}
}
}
public InputSource resolveEntity(String pub, String sys) throws SAXException {
if (pub.equals("-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN") ||
pub.equals("-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN")) {
// Ignore.
return new InputSource(new ByteArrayInputStream(new byte[0]));
} else {
return null;
}
}
}
}
}