/******************************************************************************* * Copyright (c) 2000, 2016 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 * James Blackburn - fix for Bug 230842 *******************************************************************************/ package org.eclipse.help.internal.toc; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.Platform; import org.eclipse.help.AbstractTocProvider; import org.eclipse.help.IToc; import org.eclipse.help.ITocContribution; import org.eclipse.help.ITopic; import org.eclipse.help.internal.HelpData; import org.eclipse.help.internal.HelpPlugin; import org.eclipse.help.internal.Topic; import org.eclipse.help.internal.UAElement; import org.eclipse.help.internal.UAElementFactory; /* * Manages toc contributions (TocContribution) supplied by the various toc * providers (AbstractTocProvider). */ public class TocManager { private static final String EXTENSION_POINT_ID_TOC = HelpPlugin.PLUGIN_ID + ".toc"; //$NON-NLS-1$ private static final String ELEMENT_NAME_TOC_PROVIDER = "tocProvider"; //$NON-NLS-1$ private static final String ATTRIBUTE_NAME_CLASS = "class"; //$NON-NLS-1$ private AbstractTocProvider[] tocProviders; // There are two sets of TOC contributions, one is used for Toc Assembly and is modified from the original // The other is used by the TocServlet and is unprocessed, i.e. anchors are not replaced with the contributions private Map<String, TocContribution[]> tocContributionsByLocale = new HashMap<>(); private Map<String, TocContribution[]> tocContributionsForTocByLocale = new HashMap<>(); private Map<String, Toc[]> tocsByLocale = new HashMap<>(); private Map<String, Toc> tocsById = new HashMap<>(); private Map<String, Toc> tocsByTopic; /* * Returns all toc entries (complete books) for the given locale. */ public synchronized Toc[] getTocs(String locale) { Toc[] tocs = tocsByLocale.get(locale); if (tocs == null) { long start = System.currentTimeMillis(); if (HelpPlugin.DEBUG_TOC) { System.out.println("Start to build toc for locale " + locale); //$NON-NLS-1$ } Set<String> tocsToFilter = getIgnoredTocContributions(); TocContribution[] raw = getRootTocContributions(locale, tocsToFilter); TocContribution[] filtered = filterTocContributions(raw, tocsToFilter); ITocContribution[] ordered = new TocSorter().orderTocContributions(filtered); List<Toc> orderedTocs = new ArrayList<>(ordered.length); for (int i=0;i<ordered.length;++i) { try { Toc toc = (Toc)ordered[i].getToc(); orderedTocs.add(toc); tocsById.put(ordered[i].getId(), toc); } catch (Throwable t) { // log and skip String msg = "Error getting " + Toc.class.getName() + " from " + ITocContribution.class.getName() + ": " + ordered[i]; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ HelpPlugin.logError(msg, t); } } tocs = orderedTocs.toArray(new Toc[orderedTocs.size()]); TopicSorter topicSorter = new TopicSorter(); for (int i = 0; i < tocs.length; i++) { topicSorter.sortChildren(tocs[i]); } tocsByLocale.put(locale, tocs); long stop = System.currentTimeMillis(); if (HelpPlugin.DEBUG_TOC) { System.out.println("Milliseconds to update toc for locale " + locale + " = " + (stop - start)); //$NON-NLS-1$ //$NON-NLS-2$ } } return tocs; } /* * Returns the toc whose toc contribution has the given id, for the * given locale. */ public synchronized Toc getToc(String id, String locale) { getTocs(locale); return tocsById.get(id); } public synchronized Toc getOwningToc(String href) { if (tocsByTopic == null) { tocsByTopic = new HashMap<>(); Toc[] tocs = HelpPlugin.getTocManager().getTocs(Platform.getNL()); for (int i=0;i<tocs.length;++i) { ITocContribution contribution = tocs[i].getTocContribution(); String[] extraDocuments = contribution.getExtraDocuments(); for (int j=0;j<extraDocuments.length;++j) { tocsByTopic.put(extraDocuments[j], tocs[i]); } } } return tocsByTopic.get(href); } public synchronized ITopic getTopic(String href, String locale) { Toc[] tocs = HelpPlugin.getTocManager().getTocs(locale); for (int i=0;i<tocs.length;++i) { ITopic topic = tocs[i].getTopic(href); if (topic != null) { return topic; } } int index = href.indexOf('#'); if (index != -1) { return getTopic(href.substring(0, index), locale); } return null; } public synchronized int[] getTopicPath(String href, String locale) { ITopic topic = getTopic(href, locale); try { if (topic != null && topic instanceof UAElement) { List<Integer> path = new ArrayList<>(); UAElement element = (UAElement) topic; while (!(element instanceof Toc)) { UAElement parent = element.getParentElement(); path.add(new Integer(indexOf(parent, (Topic)element))); element = parent; } Toc[] tocs = getTocs(locale); for (int i=0;i<tocs.length;++i) { if (tocs[i] == element) { path.add(new Integer(i)); int[] array = new int[path.size()]; for (int j=0;j<array.length;++j) { array[j] = path.get(array.length - 1 - j).intValue(); } return array; } } } } catch (Exception e) { return null; } // no path; not in toc return null; } /* * Returns the zero-based index at which the child topic is located under * the parent topic/toc. */ private int indexOf(UAElement parent, Topic child) { ITopic[] children; if (parent instanceof Topic) { children = ((Topic)parent).getSubtopics(); } else if (parent instanceof Toc) { children = ((Toc)parent).getTopics(); } else { return -1; } for (int i=0;i<children.length;++i) { if (children[i] == child) { return i; } } return -1; } /* * Returns all toc contributions for the given locale, from all toc * providers. */ public TocContribution[] getTocContributions(String locale) { return getAndCacheTocContributions(locale, tocContributionsByLocale); } private TocContribution[] getTocContributionsForToc(String locale) { return getAndCacheTocContributions(locale, tocContributionsForTocByLocale); } private synchronized TocContribution[] getAndCacheTocContributions(String locale, Map<String, TocContribution[]> contributionsByLocale) { TocContribution[] cached = contributionsByLocale.get(locale); if (cached == null) { HashMap<String, TocContribution> contributions = new HashMap<>(); AbstractTocProvider[] providers = getTocProviders(); for (int i=0;i<providers.length;++i) { ITocContribution[] contrib; try { contrib = providers[i].getTocContributions(locale); for (int j=0;j<contrib.length;++j) { TocContribution contribution = new TocContribution(); contribution.setCategoryId(contrib[j].getCategoryId()); contribution.setContributorId(contrib[j].getContributorId()); contribution.setExtraDocuments(contrib[j].getExtraDocuments()); contribution.setId(contrib[j].getId()); contribution.setLocale(contrib[j].getLocale()); contribution.setPrimary(contrib[j].isPrimary()); IToc toc = contrib[j].getToc(); Toc t = toc instanceof Toc ? (Toc)toc : (Toc)UAElementFactory.newElement(toc); t.setLinkTo(contrib[j].getLinkTo()); contribution.setToc(t); if(!contributions.containsKey(contrib[j].getId())) contributions.put(contrib[j].getId(), contribution); } } catch (Throwable t) { // log, and skip the offending provider String msg = "Error getting help table of contents data from provider: " + providers[i].getClass().getName() + " (skipping provider)"; //$NON-NLS-1$ //$NON-NLS-2$ HelpPlugin.logError(msg, t); continue; } } cached = contributions.values().toArray(new TocContribution[contributions.size()]); contributionsByLocale.put(locale, cached); } return cached; } /* * Clears all cached contributions, forcing the manager to query the * providers again next time a request is made. */ public void clearCache() { tocContributionsByLocale.clear(); tocContributionsForTocByLocale.clear(); tocsByLocale.clear(); tocsById.clear(); tocsByTopic = null; tocProviders=null; } /* * Internal hook for unit testing. */ public AbstractTocProvider[] getTocProviders() { if (tocProviders == null) { List<AbstractTocProvider> providers = new ArrayList<>(); IExtensionRegistry registry = Platform.getExtensionRegistry(); IConfigurationElement[] elements = registry.getConfigurationElementsFor(EXTENSION_POINT_ID_TOC); for (int i=0;i<elements.length;++i) { IConfigurationElement elem = elements[i]; if (elem.getName().equals(ELEMENT_NAME_TOC_PROVIDER)) { try { AbstractTocProvider provider = (AbstractTocProvider)elem.createExecutableExtension(ATTRIBUTE_NAME_CLASS); providers.add(provider); } catch (CoreException e) { // log and skip String msg = "Error instantiating help table of contents provider class \"" + elem.getAttribute(ATTRIBUTE_NAME_CLASS) + '"'; //$NON-NLS-1$ HelpPlugin.logError(msg, e); } } } Collections.sort(providers, new TocProviderComparator()); tocProviders = providers.toArray(new AbstractTocProvider[providers.size()]); } return tocProviders; } /* * Internal hook for unit testing. */ public void setTocProviders(AbstractTocProvider[] tocProviders) { this.tocProviders = tocProviders; } /* * Filters the given contributions according to product preferences. If * either the contribution's id or its category's id is listed in the * ignoredTocs, filter the contribution. */ private TocContribution[] filterTocContributions(TocContribution[] unfiltered, Set<String> tocsToFilter) { List<TocContribution> filtered = new ArrayList<>(); for (int i=0;i<unfiltered.length;++i) { if (!tocsToFilter.contains(unfiltered[i].getId()) && !tocsToFilter.contains(unfiltered[i].getCategoryId())) { filtered.add(unfiltered[i]); } } return filtered.toArray(new TocContribution[filtered.size()]); } private TocContribution[] getRootTocContributions(String locale, Set<String> tocsToFilter) { TocContribution[] contributions = getTocContributionsForToc(locale); List<TocContribution> unassembled = new ArrayList<>(Arrays.asList(contributions)); TocAssembler assembler = new TocAssembler(tocsToFilter); List<TocContribution> assembled = assembler.assemble(unassembled); return assembled.toArray(new TocContribution[assembled.size()]); } private Set<String> getIgnoredTocContributions() { HelpData helpData = HelpData.getProductHelpData(); if (helpData != null) { return helpData.getHiddenTocs(); } else { HashSet<String> ignored = new HashSet<>(); String preferredTocs = Platform.getPreferencesService().getString(HelpPlugin.PLUGIN_ID, HelpPlugin.IGNORED_TOCS_KEY, "", null); //$NON-NLS-1$ if (preferredTocs.length() > 0) { StringTokenizer suggestdOrderedInfosets = new StringTokenizer(preferredTocs, " ;,"); //$NON-NLS-1$ while (suggestdOrderedInfosets.hasMoreTokens()) { ignored.add(suggestdOrderedInfosets.nextToken()); } } return ignored; } } /* * Returns whether or not the toc for the given locale has been completely * loaded yet or not. */ public boolean isTocLoaded(String locale) { return tocsByLocale.get(locale) != null; } }