KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > help > internal > toc > TocAssembler


1 /*******************************************************************************
2  * Copyright (c) 2006, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.help.internal.toc;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.HashMap JavaDoc;
16 import java.util.HashSet JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.ListIterator JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.Set JavaDoc;
22
23 import org.eclipse.help.ITocContribution;
24 import org.eclipse.help.IUAElement;
25 import org.eclipse.help.internal.Anchor;
26 import org.eclipse.help.internal.HelpPlugin;
27 import org.eclipse.help.internal.Topic;
28 import org.eclipse.help.internal.UAElement;
29 import org.eclipse.help.internal.dynamic.DocumentProcessor;
30 import org.eclipse.help.internal.dynamic.DocumentReader;
31 import org.eclipse.help.internal.dynamic.ExtensionHandler;
32 import org.eclipse.help.internal.dynamic.IncludeHandler;
33 import org.eclipse.help.internal.dynamic.ProcessorHandler;
34 import org.eclipse.help.internal.dynamic.ValidationHandler;
35
36 /*
37  * Assembles toc contributions (toc fragments) into complete, linked, and
38  * assembled books.
39  */

40 public class TocAssembler {
41
42     private DocumentProcessor processor;
43     private ProcessorHandler[] handlers;
44     
45     private Map JavaDoc anchorsByContributionId;
46     private List JavaDoc contributions;
47     private Map JavaDoc contributionsById;
48     private Map JavaDoc contributionsByLinkTo;
49     private Set JavaDoc processedContributions;
50     private Map JavaDoc requiredAttributes;
51     
52     /*
53      * Assembles the given toc contributions into complete, linked
54      * books. The originals are not modified.
55      */

56     public List JavaDoc assemble(List JavaDoc contributions) {
57         this.contributions = contributions;
58         anchorsByContributionId = null;
59         contributionsById = null;
60         contributionsByLinkTo = null;
61         processedContributions = null;
62         
63         List JavaDoc books = getBooks();
64         Iterator JavaDoc iter = books.iterator();
65         while (iter.hasNext()) {
66             TocContribution book = (TocContribution)iter.next();
67             process(book);
68         }
69         return books;
70     }
71     
72     /*
73      * Returns the list of contributions that should appear as root TOCs
74      * (books). Contributions are books if the following conditions are
75      * true:
76      *
77      * 1. isPrimary() returns true.
78      * 2. The toc has no "link_to" attribute defined (does not link into
79      * another toc), or the link_to target anchor doesn't exist.
80      * 3. No other toc has a link to this contribution (via "link" element).
81      */

82     private List JavaDoc getBooks() {
83         Set JavaDoc linkedContributionIds = getLinkedContributionIds(contributions);
84         List JavaDoc books = new ArrayList JavaDoc();
85         Iterator JavaDoc iter = contributions.iterator();
86         while (iter.hasNext()) {
87             TocContribution contrib = (TocContribution)iter.next();
88             if (contrib.isPrimary() && !hasValidLinkTo(contrib) && !linkedContributionIds.contains(contrib.getId())) {
89                 books.add(contrib);
90             }
91         }
92         return books;
93     }
94     
95     /*
96      * Returns the set of ids of contributions that are linked to by other
97      * contributions, i.e. at least one other contribution has a link element
98      * pointing to it.
99      */

100     private Set JavaDoc getLinkedContributionIds(List JavaDoc contributions) {
101         if (processor == null) {
102             processor = new DocumentProcessor();
103         }
104         final Set JavaDoc linkedContributionIds = new HashSet JavaDoc();
105         ProcessorHandler[] linkFinder = new ProcessorHandler[] {
106             new ValidationHandler(getRequiredAttributes()),
107             new ProcessorHandler() {
108                 public short handle(UAElement element, String JavaDoc id) {
109                     if (element instanceof Link) {
110                         Link link = (Link)element;
111                         String JavaDoc toc = link.getToc();
112                         if (toc != null) {
113                             TocContribution srcContribution = getContribution(id);
114                             linkedContributionIds.add(HrefUtil.normalizeHref(srcContribution.getContributorId(), toc));
115                         }
116                     }
117                     return UNHANDLED;
118                 }
119             }
120         };
121         processor.setHandlers(linkFinder);
122         ListIterator JavaDoc iter = contributions.listIterator();
123         while (iter.hasNext()) {
124             TocContribution contrib = (TocContribution)iter.next();
125             try {
126                 processor.process((Toc)contrib.getToc(), contrib.getId());
127             }
128             catch (Throwable JavaDoc t) {
129                 iter.remove();
130                 String JavaDoc msg = "Error processing help table of contents: " + contrib.getId() + " (skipping)"; //$NON-NLS-1$ //$NON-NLS-2$
131
HelpPlugin.logError(msg, t);
132             }
133         }
134         return linkedContributionIds;
135     }
136     
137     /*
138      * Checks whether the toc contribution with the given id contains the
139      * given anchor.
140      */

141     private boolean hasAnchor(String JavaDoc tocContributionId, String JavaDoc anchorId) {
142         TocContribution contrib = getContribution(tocContributionId);
143         if (contrib != null) {
144             process(contrib);
145             if (anchorsByContributionId != null) {
146                 Set JavaDoc anchors = (Set JavaDoc)anchorsByContributionId.get(tocContributionId);
147                 if (anchors != null) {
148                     return anchors.contains(anchorId);
149                 }
150             }
151         }
152         // invalid contribution, or no anchors
153
return false;
154     }
155     
156     /*
157      * Checks whether the given contribution has a link_to defined, and it
158      * is valid (contribution and anchor exist).
159      */

160     private boolean hasValidLinkTo(TocContribution contrib) {
161         String JavaDoc linkTo = contrib.getLinkTo();
162         if (linkTo != null) {
163             String JavaDoc normalized = HrefUtil.normalizeHref(contrib.getContributorId(), linkTo);
164             int index = normalized.indexOf('#');
165             if (index != -1) {
166                 String JavaDoc id = normalized.substring(0, index);
167                 String JavaDoc anchorId = normalized.substring(index + 1);
168                 return hasAnchor(id, anchorId);
169             }
170         }
171         return false;
172     }
173     
174     /*
175      * Processes the given contribution, if it hasn't been processed yet. This
176      * performs the following operations:
177      *
178      * 1. Topic hrefs are normalized, e.g. "path/doc.html" ->
179      * "/my.plugin/path/doc.html"
180      * 2. Links are resolved, link is replaced with target content, extra docs
181      * are merged.
182      * 3. Anchor contributions are resolved, tocs with link_to's are inserted
183      * at anchors and extra docs merged.
184      */

185     private void process(ITocContribution contribution) {
186         if (processedContributions == null) {
187             processedContributions = new HashSet JavaDoc();
188         }
189         // don't process the same one twice
190
if (!processedContributions.contains(contribution)) {
191             if (processor == null) {
192                 processor = new DocumentProcessor();
193             }
194             if (handlers == null) {
195                 DocumentReader reader = new DocumentReader();
196                 handlers = new ProcessorHandler[] {
197                     new NormalizeHandler(),
198                     new LinkHandler(),
199                     new AnchorHandler(),
200                     new IncludeHandler(reader, contribution.getLocale()),
201                     new ExtensionHandler(reader, contribution.getLocale()),
202                 };
203             }
204             processor.setHandlers(handlers);
205             processor.process((Toc)contribution.getToc(), contribution.getId());
206             processedContributions.add(contribution);
207         }
208     }
209     
210     /*
211      * Returns the contribution with the given id.
212      */

213     private TocContribution getContribution(String JavaDoc id) {
214         if (contributionsById == null) {
215             contributionsById = new HashMap JavaDoc();
216             Iterator JavaDoc iter = contributions.iterator();
217             while (iter.hasNext()) {
218                 TocContribution contribution = (TocContribution)iter.next();
219                 contributionsById.put(contribution.getId(), contribution);
220             }
221         }
222         return (TocContribution)contributionsById.get(id);
223     }
224     
225     /*
226      * Returns all contributions that define a link_to attribute pointing to
227      * the given anchor path. The path has the form "<contributionId>#<anchorId>",
228      * e.g. "/my.plugin/toc.xml#myAnchor".
229      */

230     private TocContribution[] getAnchorContributions(String JavaDoc anchorPath) {
231         if (contributionsByLinkTo == null) {
232             contributionsByLinkTo = new HashMap JavaDoc();
233             Iterator JavaDoc iter = contributions.iterator();
234             while (iter.hasNext()) {
235                 TocContribution srcContribution = (TocContribution)iter.next();
236                 String JavaDoc linkTo = srcContribution.getLinkTo();
237                 if (linkTo != null) {
238                     String JavaDoc destAnchorPath = HrefUtil.normalizeHref(srcContribution.getContributorId(), linkTo);
239                     ITocContribution[] array = (ITocContribution[])contributionsByLinkTo.get(destAnchorPath);
240                     if (array == null) {
241                         array = new TocContribution[] { srcContribution };
242                     }
243                     else {
244                         TocContribution[] temp = new TocContribution[array.length + 1];
245                         System.arraycopy(array, 0, temp, 0, array.length);
246                         temp[array.length] = srcContribution;
247                         array = temp;
248                     }
249                     contributionsByLinkTo.put(destAnchorPath, array);
250                 }
251             }
252         }
253         TocContribution[] contributions = (TocContribution[])contributionsByLinkTo.get(anchorPath);
254         if (contributions == null) {
255             contributions = new TocContribution[0];
256         }
257         return contributions;
258     }
259     
260     private Map JavaDoc getRequiredAttributes() {
261         if (requiredAttributes == null) {
262             requiredAttributes = new HashMap JavaDoc();
263             requiredAttributes.put(Toc.NAME, new String JavaDoc[] { Toc.ATTRIBUTE_LABEL });
264             requiredAttributes.put(Topic.NAME, new String JavaDoc[] { Topic.ATTRIBUTE_LABEL });
265             requiredAttributes.put("anchor", new String JavaDoc[] { "id" }); //$NON-NLS-1$ //$NON-NLS-2$
266
requiredAttributes.put("include", new String JavaDoc[] { "path" }); //$NON-NLS-1$ //$NON-NLS-2$
267
requiredAttributes.put("link", new String JavaDoc[] { "toc" }); //$NON-NLS-1$ //$NON-NLS-2$
268
}
269         return requiredAttributes;
270     }
271     
272     /*
273      * Adds the given extra documents to the contribution.
274      */

275     private void addExtraDocuments(TocContribution contribution, String JavaDoc[] extraDocuments) {
276         if (extraDocuments.length > 0) {
277             String JavaDoc[] destExtraDocuments = contribution.getExtraDocuments();
278             String JavaDoc[] combinedExtraDocuments;
279             if (destExtraDocuments.length == 0) {
280                 combinedExtraDocuments = extraDocuments;
281             }
282             else {
283                 Set JavaDoc set = new HashSet JavaDoc();
284                 set.addAll(Arrays.asList(destExtraDocuments));
285                 set.addAll(Arrays.asList(extraDocuments));
286                 combinedExtraDocuments = (String JavaDoc[])set.toArray(new String JavaDoc[set.size()]);
287             }
288             contribution.setExtraDocuments(combinedExtraDocuments);
289         }
290     }
291     
292     /*
293      * Handler that resolves link elements (replaces the link element with
294      * the linked-to toc's children.
295      */

296     private class LinkHandler extends ProcessorHandler {
297         public short handle(UAElement element, String JavaDoc id) {
298             if (element instanceof Link) {
299                 Link link = (Link)element;
300                 UAElement parent = link.getParentElement();
301                 if (parent != null) {
302                     String JavaDoc toc = link.getToc();
303                     if (toc != null) {
304                         TocContribution destContribution = getContribution(id);
305                         TocContribution srcContribution = getContribution(HrefUtil.normalizeHref(destContribution.getContributorId(), toc));
306                         if (srcContribution != null) {
307                             process(srcContribution);
308                             IUAElement[] children = srcContribution.getToc().getChildren();
309                             for (int i=0;i<children.length;++i) {
310                                 parent.insertBefore((UAElement)children[i], link);
311                             }
312                             addExtraDocuments(destContribution, srcContribution.getExtraDocuments());
313                         }
314                         parent.removeChild(link);
315                     }
316                 }
317                 return HANDLED_SKIP;
318             }
319             return UNHANDLED;
320         }
321     }
322
323     /*
324      * Handles anchor contributions. If any contribution's toc wants to link
325      * into this one at the current anchor, link it in.
326      */

327     private class AnchorHandler extends ProcessorHandler {
328         public short handle(UAElement element, String JavaDoc id) {
329             if (element instanceof Anchor) {
330                 Anchor anchor = (Anchor)element;
331                 UAElement parent = anchor.getParentElement();
332                 if (parent != null) {
333                     String JavaDoc anchorId = anchor.getId();
334                     if (anchorId != null) {
335                         // add to set of known anchors
336
if (anchorsByContributionId == null) {
337                             anchorsByContributionId = new HashMap JavaDoc();
338                         }
339                         Set JavaDoc set = (Set JavaDoc)anchorsByContributionId.get(id);
340                         if (set == null) {
341                             set = new HashSet JavaDoc();
342                             anchorsByContributionId.put(id, set);
343                         }
344                         set.add(anchorId);
345                         
346                         // process contributions
347
TocContribution destContribution = getContribution(id);
348                         if (destContribution != null) {
349                             TocContribution[] srcContributions = getAnchorContributions(destContribution.getId() + '#' + anchorId);
350                             for (int i=0;i<srcContributions.length;++i) {
351                                 process(srcContributions[i]);
352                                 IUAElement[] children = srcContributions[i].getToc().getChildren();
353                                 for (int j=0;j<children.length;++j) {
354                                     parent.insertBefore((UAElement)children[j], anchor);
355                                 }
356                                 addExtraDocuments(destContribution, srcContributions[i].getExtraDocuments());
357                             }
358                         }
359                     }
360                 }
361             }
362             // allow the extension handler to act on anchors afterwards
363
return UNHANDLED;
364         }
365     }
366
367     /*
368      * Normalizes topic hrefs, by prepending the plug-in id to form an href.
369      * e.g. "path/myfile.html" -> "/my.plugin/path/myfile.html"
370      */

371     private class NormalizeHandler extends ProcessorHandler {
372         public short handle(UAElement element, String JavaDoc id) {
373             if (element instanceof Topic) {
374                 Topic topic = (Topic)element;
375                 String JavaDoc href = topic.getHref();
376                 if (href != null) {
377                     topic.setHref(normalize(href, id));
378                 }
379                 return HANDLED_CONTINUE;
380             }
381             else if (element instanceof Toc) {
382                 Toc toc = (Toc)element;
383                 toc.setHref(id);
384                 String JavaDoc topic = toc.getTopic();
385                 if (topic != null) {
386                     toc.setTopic(normalize(topic, id));
387                 }
388                 return HANDLED_CONTINUE;
389             }
390             return UNHANDLED;
391         }
392         
393         private String JavaDoc normalize(String JavaDoc href, String JavaDoc id) {
394             ITocContribution contribution = getContribution(id);
395             if (contribution != null) {
396                 String JavaDoc pluginId = contribution.getContributorId();
397                 return HrefUtil.normalizeHref(pluginId, href);
398             }
399             else {
400                 int index = id.indexOf('/', 1);
401                 if (index != -1) {
402                     String JavaDoc pluginId = id.substring(1, index);
403                     return HrefUtil.normalizeHref(pluginId, href);
404                 }
405             }
406             return href;
407         }
408     }
409 }
410
Popular Tags