package org.eclipse.help.internal.validation; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.help.IHelpResource; import org.eclipse.help.ILink; import org.eclipse.help.IToc; import org.eclipse.help.IUAElement; import org.eclipse.help.internal.protocols.HelpURLConnection; import org.eclipse.help.internal.toc.TocContribution; import org.eclipse.help.internal.toc.TocFile; import org.eclipse.help.internal.toc.TocFileParser; import org.xml.sax.SAXException; /******************************************************************************* * Copyright (c) 2007, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ public class TocValidator { private static final boolean DEBUG = false; private HashMap<String, Object> processedTocs; private TocFileParser parser; public static class BrokenLink { private String tocID; private String href; private BrokenLink(String tocID, String href) { this.tocID = tocID; this.href = href; } public String getTocID() { return tocID; } public String getHref() { return href; } } public static abstract class Filter { abstract public boolean isIncluded(String href); } public static class PassThroughFilter extends Filter { @Override public boolean isIncluded(String href) { return true; } } /** * Checks the validity of all <code>href</code> attributes on <code>topic</code> elements in the toc and the * <code>topic</code> attribute on the <code>toc</code> element if there is one. Also checks validity of any * nested tocs. * @param hrefs gives the list of paths to toc files to validate including the plug-in id * (i.e. "/<plug-in id>/<path>/<file>") * @return An ArrayList of BrokenLink objects in the toc. If no broken links are found, an empty ArrayList * is returned. * @throws IOException * @throws SAXException * @throws ParserConfigurationException */ public static ArrayList<BrokenLink> validate(String[] hrefs) throws IOException, SAXException, ParserConfigurationException{ return filteredValidate(hrefs, new PassThroughFilter()); } public static ArrayList<BrokenLink> filteredValidate (String[] hrefs, Filter filter) throws IOException, SAXException, ParserConfigurationException{ TocValidator v = new TocValidator(); ArrayList<BrokenLink> result = new ArrayList<>(); for (int i = 0; i < hrefs.length; i++) v.processToc(hrefs[i], null, result, filter); return result; } private TocValidator() { processedTocs = new HashMap<>(); parser = new TocFileParser(); } /* Checks validity of all links in a given toc. If all links are valid, an empty ArrayList is returned. * Otherwise an ArrayList of BrokenLink objects is returned. */ private void processToc(String href, String plugin, ArrayList<BrokenLink> result, Filter filter) throws IOException, SAXException, ParserConfigurationException { String path; if (href.startsWith("/")) { //$NON-NLS-1$ href = href.substring(1); int index = href.indexOf("/"); //$NON-NLS-1$ if (index == -1) throw new IOException("Invalid parameters supplied to the validate method."); //$NON-NLS-1$ plugin = href.substring(0, index); path = href.substring(index+1); } else { path = href; } if (plugin == null) throw new IOException("Invalid parameters supplied to the validate method."); //$NON-NLS-1$ String key = "/" + plugin + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$ if (processedTocs.get(key) != null) { if (DEBUG) System.out.println("Skipping toc because it has already been validated: " + key); //$NON-NLS-1$ return; } if (DEBUG) System.out.println("Starting toc: " + key); //$NON-NLS-1$ processedTocs.put(key, new Object()); TocContribution contribution = parser.parse(new TocFile(plugin,path, true, "en", null, null)); //$NON-NLS-1$ process(contribution.getToc(), plugin, path, result, filter); } /* Checks validity of all links in the given IUAElement and recursively calls itself to check all children. * If there are any links to other tocs, calls the processToc method to validate them. If any broken links * are found, an appropriate BrokenLink object will be added to the result ArrayList. */ private void process(IUAElement element, String plugin, String path, ArrayList<BrokenLink> result, Filter filter) throws SAXException, ParserConfigurationException { String href; if (element instanceof ILink) { href = ((ILink)element).getToc(); try { processToc(href, plugin, result, filter); } catch (IOException e) { result.add(new BrokenLink("/" + plugin + "/" + path, href)); //$NON-NLS-1$ //$NON-NLS-2$ } } else if (element instanceof IHelpResource) { if (element instanceof IToc) href = ((IToc)element).getTopic(null).getHref(); else href = ((IHelpResource)element).getHref(); if (href != null && filter.isIncluded(href)) if (!checkLink(href, plugin)) { result.add(new BrokenLink("/" + plugin + "/" + path, href)); //$NON-NLS-1$ //$NON-NLS-2$ } } IUAElement [] children = element.getChildren(); for (int i = 0; i < children.length; i++) process(children[i], plugin, path, result, filter); } /* Checks validity of a given link from a toc in a given plug-in. * returns true if the link is valid. */ private boolean checkLink(String href, String plugin) { if (href.startsWith("http")) { //$NON-NLS-1$ if (DEBUG) System.out.println(" Skipping href: " + href); //$NON-NLS-1$ return true; } if (DEBUG) System.out.print(" Checking href: " + href + "..."); //$NON-NLS-1$ //$NON-NLS-2$ boolean result = true; InputStream i = null; try { HelpURLConnection c = new HelpURLConnection(createURL(href, plugin)); if ((i = c.getInputStream()) == null) result = false; } catch (Exception e) { result = false; } if (i != null) { try { i.close(); } catch(Exception e) { } i = null; } if (DEBUG) System.out.println(result?"pass":"fail"); //$NON-NLS-1$ //$NON-NLS-2$ return result; } // Builds a URL for a given plug-in/href to create a HelpURLConnection private URL createURL(String href, String plugin) throws MalformedURLException { StringBuffer url = new StringBuffer("file:/"); //$NON-NLS-1$ url.append(plugin); url.append("/"); //$NON-NLS-1$ url.append(href); return new URL(url.toString()); } }