blob: ecbdf4e61b4e5edc9f37bfd98379450621891a58 [file] [log] [blame]
/*******************************************************************************
* 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(Integer.valueOf(indexOf(parent, (Topic)element)));
element = parent;
}
Toc[] tocs = getTocs(locale);
for (int i=0;i<tocs.length;++i) {
if (tocs[i] == element) {
path.add(Integer.valueOf(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;
}
}