KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > nbbuild > JHIndexer


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.nbbuild;
21
22 import java.io.*;
23 import java.lang.reflect.*;
24 import java.util.*;
25
26 import org.apache.tools.ant.AntClassLoader;
27 import org.apache.tools.ant.BuildException;
28 import org.apache.tools.ant.DirectoryScanner;
29 import org.apache.tools.ant.FileScanner;
30 import org.apache.tools.ant.Project;
31 import org.apache.tools.ant.taskdefs.Copy;
32 import org.apache.tools.ant.taskdefs.Delete;
33 import org.apache.tools.ant.taskdefs.MatchingTask;
34 import org.apache.tools.ant.taskdefs.Mkdir;
35 import org.apache.tools.ant.types.FileSet;
36 import org.apache.tools.ant.types.Mapper;
37 import org.apache.tools.ant.types.Path;
38
39 // ToDo:
40
// stopwords configuration
41
// verbose mode
42

43 /** Task to run JavaHelp search indexer.
44  * Creates the proper binary search database from source HTML.
45  * @author Jesse Glick
46  * @see <a HREF="http://java.sun.com/products/javahelp/">JavaHelp home page</a>
47  */

48 public class JHIndexer extends MatchingTask {
49
50     private Path classpath;
51     private File db;
52     private File basedir;
53     private String JavaDoc locale;
54     private List<BrandedFileSet> brandings = new LinkedList<BrandedFileSet>();
55
56     /** Set the location of <samp>jhall.jar</samp> or <samp>jsearch.jar</samp> (JavaHelp tools library). */
57     public Path createClasspath() {
58         // JavaHelp release notes say jhtools.jar is enough, but class NoClassDefFoundError
59
// on javax.help.search.IndexBuilder when I tried it...
60
if (classpath == null) {
61             classpath = new Path(getProject());
62         }
63         return classpath.createPath();
64     }
65
66     /** Set the location of the output database.
67      * E.g. <samp>JavaHelpSearch</samp>).
68      * <strong>Warning:</strong> the directory will be deleted and recreated.
69      */

70     public void setDb (File db) {
71         this.db = db;
72     }
73
74     /** Set the base directory from which to scan files.
75      * This should be the directory containing the helpset for the database to work correctly.
76      */

77     public void setBasedir (File basedir) {
78         this.basedir = basedir;
79     }
80     public void setLocale (String JavaDoc locale) {
81         this.locale = locale;
82     }
83     
84     /**
85      * A set of additional files forming a branding variant.
86      * @see #addBrandedFileSet
87      */

88     public static final class BrandedFileSet extends FileSet {
89         String JavaDoc branding;
90         public void setBranding(String JavaDoc b) {
91             this.branding = b;
92         }
93     }
94     
95     /**
96      * Add a set of branded files to be indexed.
97      * For example, you may have in <samp>/the/base/dir</samp>
98      * <ul>
99      * <li><samp>foo.html</samp>
100      * <li><samp>bar.html</samp>
101      * <li><samp>baz.html</samp>
102      * </ul>
103      * Now create a new directory <samp>/the/new/dir</samp>:
104      * <ul>
105      * <li><samp>foo_brand.html</samp>
106      * <li><samp>baz_brand.html</samp>
107      * </ul>
108      * If you include this with:
109      * <pre>
110 &lt;jhindexer basedir="/the/base/dir"&gt;
111     &lt;include name="*&#42;/*.html"/&gt;
112     &lt;brandedfileset dir="/the/new/dir" branding="brand"&gt;
113         &lt;include name="*&#42;/*.html"/&gt;
114     &lt;/brandedfileset&gt;
115 &lt;/jhindexer&gt;
116      * </pre>
117      * then the search database will contain entries:
118      * <table border="1">
119      * <tr><th>JH name</th><th>From file</th></tr>
120      * <tr><td><samp>foo.html</samp></td><td><samp>/the/new/dir/foo_brand.html</samp></td></tr>
121      * <tr><td><samp>bar.html</samp></td><td><samp>/the/base/dir/bar.html</samp></td></tr>
122      * <tr><td><samp>baz.html</samp></td><td><samp>/the/new/dir/baz_brand.html</samp></td></tr>
123      * </table>
124      * and every file in the database (<samp>TMAP</samp> etc.)
125      * will receive the special suffix <samp>_brand</samp>.
126      * <p>You may give multiple branding filesets, so long as the branding
127      * tokens supplied are nested: i.e. for every pair of tokens among the supplied
128      * filesets, one is a prefix of the other (with <samp>_</samp> being the
129      * separator between the prefix and suffix). The search database suffix is then
130      * an underscore followed by the longest branding token.
131      * <p>Such a database is suitable for branding NetBeans: consider a module
132      * with documentation entries such as the following:
133      * <ul>
134      * <li><samp>modules/docs/foo.jar!/some/pkg/foo/foo.html</samp>
135      * <li><samp>modules/docs/foo.jar!/some/pkg/foo/bar.html</samp>
136      * <li><samp>modules/docs/foo.jar!/some/pkg/foo/baz.html</samp>
137      * <li><samp>modules/docs/foo.jar!/some/pkg/foo/JavaHelpSearch/TMAP</samp> (etc.)
138      * <li><samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/foo_brand.html</samp>
139      * <li><samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/baz_brand.html</samp>
140      * <li><samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/JavaHelpSearch/TMAP_brand</samp> (etc.)
141      * </ul>
142      * where the files in <samp>modules/docs/foo.jar!/some/pkg/foo/JavaHelpSearch/</samp>
143      * were generated by a regular invocation of this task and the files in
144      * <samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/JavaHelpSearch/</samp>
145      * were generated by the variant above. Then a help set reference using a URL such as
146      * <samp>nbdocs:/some/pkg/foo/helpset.xml</samp> will, when running with branding
147      * <samp>brand</samp>, not only display the expected variants of <samp>foo.html</samp>
148      * and <samp>baz.html</samp>, but be able to search for strings specifically in them
149      * (including correct offsets).
150      * @see <a HREF="http://www.netbeans.org/issues/show_bug.cgi?id=31044">Issue #31044</a>
151      */

152     public void addBrandedFileSet(BrandedFileSet s) {
153         brandings.add(s);
154     }
155     
156     /** @deprecated Use {@link #createClasspath} instead. */
157     @Deprecated JavaDoc
158     public void setJhall(File f) {
159         log("The 'jhall' attribute to <jhindexer> is deprecated. Use a nested <classpath> instead.", Project.MSG_WARN);
160         createClasspath().setLocation(f);
161     }
162
163     /**
164      *
165      */

166     public void setClassPath(Path cp) {
167         classpath = cp;
168     }
169     
170     public void execute () throws BuildException {
171         if (classpath == null) throw new BuildException ("Must specify the classpath attribute to find jhall.jar or jsearch.jar");
172         if (db == null) throw new BuildException ("Must specify the db attribute");
173         if (basedir == null) throw new BuildException ("Must specify the basedir attribute");
174         FileScanner scanner = getDirectoryScanner (basedir);
175         scanner.scan ();
176         String JavaDoc[] files = scanner.getIncludedFiles ();
177         // First, an up-to-date check. ;-)
178
if (basedir.exists () && db.exists ()) {
179             long lastModified = Long.MIN_VALUE;
180             // First scan output dir for any files.
181
FileScanner output = new DirectoryScanner ();
182             output.setBasedir (db);
183             output.scan ();
184             String JavaDoc[] outfiles = output.getIncludedFiles ();
185             if (outfiles.length > 0) {
186                 for (int i = 0; i < outfiles.length; i++) {
187                     long mod = new File (db, outfiles[i]).lastModified ();
188                     if (mod > lastModified) {
189                         lastModified = mod;
190                     }
191                 }
192                 // Now check to see if any source files are newer.
193
boolean ok = true;
194                 for (int i = 0; i < files.length; i++) {
195                     long mod = new File (basedir, files[i]).lastModified ();
196                     if (mod > lastModified) {
197                         ok = false;
198                         break;
199                     }
200                 }
201                 if (!brandings.isEmpty()) {
202                     // Check these too!
203
for(FileSet fs: brandings) {
204                         FileScanner scanner2 = fs.getDirectoryScanner(getProject());
205                         String JavaDoc[] files2 = scanner2.getIncludedFiles();
206                         for (int i = 0; i < files2.length; i++) {
207                             long mod = new File (basedir, files2[i]).lastModified ();
208                             if (mod > lastModified) {
209                                 ok = false;
210                                 break;
211                             }
212                         }
213                     }
214                 }
215                 if (ok) {
216                     // No need to rebuild.
217
return;
218                 }
219             }
220         }
221         Delete delete = (Delete) getProject().createTask("delete");
222         delete.setDir (db);
223         delete.init ();
224         delete.setLocation(getLocation());
225         delete.execute ();
226         Mkdir mkdir = (Mkdir) getProject().createTask("mkdir");
227         mkdir.setDir (db);
228         mkdir.init ();
229         mkdir.setLocation(getLocation());
230         mkdir.execute ();
231         String JavaDoc maxbranding = null;
232         if (!brandings.isEmpty()) {
233             // Copy all files, overriding by branding, to a fresh dir somewhere.
234
// Does not suffice to simply use IndexRemove to strip off the basedirs
235
// of files in branded filesets, since their filenames will also include
236
// the branding token, and this will mess up the search database: it needs
237
// to store just the simple file name with no branding infix.
238
File tmp = new File(System.getProperty("java.io.tmpdir"), "jhindexer-branding-merge");
239             delete = (Delete) getProject().createTask("delete");
240             delete.setDir(tmp);
241             delete.init();
242             delete.setLocation(getLocation());
243             delete.execute();
244             tmp.mkdir();
245             // Start with the base files.
246
Copy copy = (Copy) getProject().createTask("copy");
247             copy.setTodir(tmp);
248             copy.addFileset(fileset);
249             copy.init();
250             copy.setLocation(getLocation());
251             copy.execute();
252             // Now branded filesets. Must be done in order of branding, so that
253
// more specific files override generic ones.
254
class BrandingLengthComparator implements Comparator<BrandedFileSet> {
255                 public int compare(BrandedFileSet a, BrandedFileSet b) {
256                     return a.branding.length() - b.branding.length();
257                 }
258             }
259             Collections.sort(brandings, new BrandingLengthComparator());
260             for (BrandedFileSet s: brandings) {
261                 if (maxbranding != null && !s.branding.startsWith(maxbranding + "_")) {
262                     throw new BuildException("Illegal branding: " + s.branding, getLocation());
263                 }
264                 maxbranding = s.branding; // only last one will be kept
265
String JavaDoc[] suffixes = {
266                     ".html",
267                     ".htm",
268                     ".xhtml",
269                     // XXX any others? unpleasant to hardcode but this is easiest,
270
// since glob mappers do not permit *_x* -> ** syntax.
271
};
272                 for (int i = 0; i < suffixes.length; i++) {
273                     String JavaDoc suffix = suffixes[i];
274                     copy = (Copy) getProject().createTask("copy");
275                     copy.setTodir(tmp);
276                     copy.setOverwrite(true);
277                     copy.addFileset(s);
278                     Mapper m = copy.createMapper();
279                     Mapper.MapperType mt = new Mapper.MapperType();
280                     mt.setValue("glob");
281                     m.setType(mt);
282                     m.setFrom("*_" + s.branding + suffix);
283                     m.setTo("*" + suffix);
284                     copy.init();
285                     copy.setLocation(getLocation());
286                     copy.execute();
287                     if (locale != null) {
288                         // Possibly have e.g. x_f4j_ja.html.
289
suffix = "_" + locale + suffix;
290                         copy = (Copy) getProject().createTask("copy");
291                         copy.setTodir(tmp);
292                         copy.setOverwrite(true);
293                         copy.addFileset(s);
294                         m = copy.createMapper();
295                         mt = new Mapper.MapperType();
296                         mt.setValue("glob");
297                         m.setType(mt);
298                         m.setFrom("*_" + s.branding + suffix);
299                         m.setTo("*" + suffix);
300                         copy.init();
301                         copy.setLocation(getLocation());
302                         copy.execute();
303                     }
304                 }
305             }
306             // Now replace basedir & files with this temp dir.
307
basedir = tmp;
308             FileSet tmpf = new FileSet();
309             tmpf.setProject(getProject());
310             tmpf.setDir(tmp);
311             files = tmpf.getDirectoryScanner(getProject()).getIncludedFiles();
312         }
313         log ("Running JavaHelp search database indexer...");
314         try {
315             File config = File.createTempFile ("jhindexer-config", ".txt");
316             try {
317                 OutputStream os = new FileOutputStream (config);
318                 try {
319                     PrintWriter pw = new PrintWriter (os);
320                     pw.println ("IndexRemove " + basedir + File.separator);
321                     String JavaDoc message = "Files to be indexed:";
322                     for (int i = 0; i < files.length; i++) {
323                         // [PENDING] JavaHelp docs say to use / as file sep for File directives;
324
// so what should the complete path be? Someone should test this on Windoze...
325
String JavaDoc path = basedir + File.separator + files[i];
326                         pw.println ("File " + path);
327                         message += "\n\t" + path;
328                     }
329                     log (message, Project.MSG_VERBOSE);
330                     pw.flush ();
331                 } finally {
332                     os.close ();
333                 }
334                 AntClassLoader loader = new AntClassLoader(getProject(), classpath);
335                 try {
336                     Class JavaDoc<?> clazz = loader.loadClass("com.sun.java.help.search.Indexer");
337                     Method main = clazz.getMethod("main", String JavaDoc[].class);
338                     List<String JavaDoc> args = Arrays.asList(
339                         "-c", config.getAbsolutePath(),
340                         "-db", db.getAbsolutePath()
341                     );
342                     if (locale != null) {
343                         args = new ArrayList<String JavaDoc>(args); // #35244
344
args.add("-locale");
345                         args.add(locale);
346                     }
347                     main.invoke(null, new Object JavaDoc[] {args.toArray(new String JavaDoc[args.size()])});
348                 } catch (InvocationTargetException ite) {
349                     throw new BuildException("Could not run indexer", ite.getTargetException(), getLocation());
350                 } catch (Exception JavaDoc e) { // ClassNotFoundException, NoSuchMethodException, ...
351
throw new BuildException("Could not run indexer", e, getLocation());
352                 }
353             } finally {
354                 config.delete ();
355             }
356         } catch (IOException ioe) {
357             throw new BuildException("Could not make temporary config file", ioe, getLocation());
358         }
359         if (maxbranding != null) {
360             // Now rename search DB files to include branding suffix.
361
// Note that DOCS.TAB -> DOCS_brand.TAB to work with nbdocs: protocol.
362
String JavaDoc[] dbfiles = db.list();
363             for (int i = 0; i < dbfiles.length; i++) {
364                 String JavaDoc basename, ext;
365                 int idx = dbfiles[i].lastIndexOf('.');
366                 if (idx != -1) {
367                     basename = dbfiles[i].substring(0, idx);
368                     ext = dbfiles[i].substring(idx);
369                 } else {
370                     basename = dbfiles[i];
371                     ext = "";
372                 }
373                 File old = new File(db, dbfiles[i]);
374                 File nue = new File(db, basename + "_" + maxbranding + ext);
375                 log("Moving " + old + " to " + nue, Project.MSG_VERBOSE);
376                 old.renameTo(nue);
377             }
378         }
379     }
380
381 }
382
Popular Tags