| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.nbbuild; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import org.apache.tools.ant.AntClassLoader; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.FileScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.Copy; |
| import org.apache.tools.ant.taskdefs.Delete; |
| import org.apache.tools.ant.taskdefs.MatchingTask; |
| import org.apache.tools.ant.taskdefs.Mkdir; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.types.Mapper; |
| import org.apache.tools.ant.types.Path; |
| |
| // ToDo: |
| // stopwords configuration |
| // verbose mode |
| |
| /** Task to run JavaHelp search indexer. |
| * Creates the proper binary search database from source HTML. |
| * @author Jesse Glick |
| * @see <a href="http://java.sun.com/products/javahelp/">JavaHelp home page</a> |
| */ |
| public class JHIndexer extends MatchingTask { |
| |
| private Path classpath; |
| private File db; |
| private File basedir; |
| private String locale; |
| private List<BrandedFileSet> brandings = new LinkedList<>(); |
| |
| /** Set the location of <samp>jhall.jar</samp> or <samp>jsearch.jar</samp> (JavaHelp tools library). */ |
| public Path createClasspath() { |
| // JavaHelp release notes say jhtools.jar is enough, but class NoClassDefFoundError |
| // on javax.help.search.IndexBuilder when I tried it... |
| if (classpath == null) { |
| classpath = new Path(getProject()); |
| } |
| return classpath.createPath(); |
| } |
| |
| /** Set the location of the output database. |
| * E.g. <samp>JavaHelpSearch</samp>). |
| * <strong>Warning:</strong> the directory will be deleted and recreated. |
| */ |
| public void setDb (File db) { |
| this.db = db; |
| } |
| |
| /** Set the base directory from which to scan files. |
| * This should be the directory containing the helpset for the database to work correctly. |
| */ |
| public void setBasedir (File basedir) { |
| this.basedir = basedir; |
| } |
| public void setLocale (String locale) { |
| this.locale = locale; |
| } |
| |
| /** |
| * A set of additional files forming a branding variant. |
| * @see #addBrandedFileSet |
| */ |
| public static final class BrandedFileSet extends FileSet { |
| String branding; |
| public void setBranding(String b) { |
| this.branding = b; |
| } |
| } |
| |
| /** |
| * Add a set of branded files to be indexed. |
| * For example, you may have in <samp>/the/base/dir</samp> |
| * <ul> |
| * <li><samp>foo.html</samp> |
| * <li><samp>bar.html</samp> |
| * <li><samp>baz.html</samp> |
| * </ul> |
| * Now create a new directory <samp>/the/new/dir</samp>: |
| * <ul> |
| * <li><samp>foo_brand.html</samp> |
| * <li><samp>baz_brand.html</samp> |
| * </ul> |
| * If you include this with: |
| * <pre> |
| <jhindexer basedir="/the/base/dir"> |
| <include name="**/*.html"/> |
| <brandedfileset dir="/the/new/dir" branding="brand"> |
| <include name="**/*.html"/> |
| </brandedfileset> |
| </jhindexer> |
| * </pre> |
| * then the search database will contain entries: |
| * <table border="1"> |
| * <tr><th>JH name</th><th>From file</th></tr> |
| * <tr><td><samp>foo.html</samp></td><td><samp>/the/new/dir/foo_brand.html</samp></td></tr> |
| * <tr><td><samp>bar.html</samp></td><td><samp>/the/base/dir/bar.html</samp></td></tr> |
| * <tr><td><samp>baz.html</samp></td><td><samp>/the/new/dir/baz_brand.html</samp></td></tr> |
| * </table> |
| * and every file in the database (<samp>TMAP</samp> etc.) |
| * will receive the special suffix <samp>_brand</samp>. |
| * <p>You may give multiple branding filesets, so long as the branding |
| * tokens supplied are nested: i.e. for every pair of tokens among the supplied |
| * filesets, one is a prefix of the other (with <samp>_</samp> being the |
| * separator between the prefix and suffix). The search database suffix is then |
| * an underscore followed by the longest branding token. |
| * <p>Such a database is suitable for branding NetBeans: consider a module |
| * with documentation entries such as the following: |
| * <ul> |
| * <li><samp>modules/docs/foo.jar!/some/pkg/foo/foo.html</samp> |
| * <li><samp>modules/docs/foo.jar!/some/pkg/foo/bar.html</samp> |
| * <li><samp>modules/docs/foo.jar!/some/pkg/foo/baz.html</samp> |
| * <li><samp>modules/docs/foo.jar!/some/pkg/foo/JavaHelpSearch/TMAP</samp> (etc.) |
| * <li><samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/foo_brand.html</samp> |
| * <li><samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/baz_brand.html</samp> |
| * <li><samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/JavaHelpSearch/TMAP_brand</samp> (etc.) |
| * </ul> |
| * where the files in <samp>modules/docs/foo.jar!/some/pkg/foo/JavaHelpSearch/</samp> |
| * were generated by a regular invocation of this task and the files in |
| * <samp>modules/docs/locale/foo_brand.jar!/some/pkg/foo/JavaHelpSearch/</samp> |
| * were generated by the variant above. Then a help set reference using a URL such as |
| * <samp>nbdocs:/some/pkg/foo/helpset.xml</samp> will, when running with branding |
| * <samp>brand</samp>, not only display the expected variants of <samp>foo.html</samp> |
| * and <samp>baz.html</samp>, but be able to search for strings specifically in them |
| * (including correct offsets). |
| * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=31044">Issue #31044</a> |
| */ |
| public void addBrandedFileSet(BrandedFileSet s) { |
| brandings.add(s); |
| } |
| |
| /** @deprecated Use {@link #createClasspath} instead. */ |
| @Deprecated |
| public void setJhall(File f) { |
| log("The 'jhall' attribute to <jhindexer> is deprecated. Use a nested <classpath> instead.", Project.MSG_WARN); |
| createClasspath().setLocation(f); |
| } |
| |
| /** |
| * |
| */ |
| public void setClassPath(Path cp) { |
| classpath = cp; |
| } |
| |
| public @Override void execute() throws BuildException { |
| if (classpath == null) { |
| throw new BuildException ("Must specify the classpath attribute to find jhall.jar or jsearch.jar"); |
| } |
| if (db == null) { |
| throw new BuildException ("Must specify the db attribute"); |
| } |
| if (basedir == null) { |
| throw new BuildException ("Must specify the basedir attribute"); |
| } |
| FileScanner scanner = getDirectoryScanner (basedir); |
| scanner.scan (); |
| String[] files = scanner.getIncludedFiles (); |
| // First, an up-to-date check. ;-) |
| if (basedir.exists () && db.exists ()) { |
| long lastModified = Long.MIN_VALUE; |
| // First scan output dir for any files. |
| FileScanner output = new DirectoryScanner (); |
| output.setBasedir (db); |
| output.scan (); |
| String[] outfiles = output.getIncludedFiles (); |
| if (outfiles.length > 0) { |
| for (int i = 0; i < outfiles.length; i++) { |
| long mod = new File (db, outfiles[i]).lastModified (); |
| if (mod > lastModified) { |
| lastModified = mod; |
| } |
| } |
| // Now check to see if any source files are newer. |
| boolean ok = true; |
| for (int i = 0; i < files.length; i++) { |
| long mod = new File (basedir, files[i]).lastModified (); |
| if (mod > lastModified) { |
| ok = false; |
| break; |
| } |
| } |
| if (!brandings.isEmpty()) { |
| // Check these too! |
| for(FileSet fs: brandings) { |
| FileScanner scanner2 = fs.getDirectoryScanner(getProject()); |
| String[] files2 = scanner2.getIncludedFiles(); |
| for (int i = 0; i < files2.length; i++) { |
| long mod = new File (basedir, files2[i]).lastModified (); |
| if (mod > lastModified) { |
| ok = false; |
| break; |
| } |
| } |
| } |
| } |
| if (ok) { |
| // No need to rebuild. |
| return; |
| } |
| } |
| } |
| Delete delete = (Delete) getProject().createTask("delete"); |
| delete.setDir (db); |
| delete.init (); |
| delete.setLocation(getLocation()); |
| delete.execute (); |
| Mkdir mkdir = (Mkdir) getProject().createTask("mkdir"); |
| mkdir.setDir (db); |
| mkdir.init (); |
| mkdir.setLocation(getLocation()); |
| mkdir.execute (); |
| String maxbranding = null; |
| if (!brandings.isEmpty()) { |
| // Copy all files, overriding by branding, to a fresh dir somewhere. |
| // Does not suffice to simply use IndexRemove to strip off the basedirs |
| // of files in branded filesets, since their filenames will also include |
| // the branding token, and this will mess up the search database: it needs |
| // to store just the simple file name with no branding infix. |
| File tmp = new File(System.getProperty("java.io.tmpdir"), "jhindexer-branding-merge"); |
| delete = (Delete) getProject().createTask("delete"); |
| delete.setDir(tmp); |
| delete.init(); |
| delete.setLocation(getLocation()); |
| delete.execute(); |
| tmp.mkdir(); |
| // Start with the base files. |
| Copy copy = (Copy) getProject().createTask("copy"); |
| copy.setTodir(tmp); |
| copy.addFileset(fileset); |
| copy.init(); |
| copy.setLocation(getLocation()); |
| copy.execute(); |
| // Now branded filesets. Must be done in order of branding, so that |
| // more specific files override generic ones. |
| class BrandingLengthComparator implements Comparator<BrandedFileSet> { |
| public int compare(BrandedFileSet a, BrandedFileSet b) { |
| return a.branding.length() - b.branding.length(); |
| } |
| } |
| Collections.sort(brandings, new BrandingLengthComparator()); |
| for (BrandedFileSet s: brandings) { |
| if (maxbranding != null && !s.branding.startsWith(maxbranding + "_")) { |
| throw new BuildException("Illegal branding: " + s.branding, getLocation()); |
| } |
| maxbranding = s.branding; // only last one will be kept |
| String[] suffixes = { |
| ".html", |
| ".htm", |
| ".xhtml", |
| // XXX any others? unpleasant to hardcode but this is easiest, |
| // since glob mappers do not permit *_x* -> ** syntax. |
| }; |
| for (int i = 0; i < suffixes.length; i++) { |
| String suffix = suffixes[i]; |
| copy = (Copy) getProject().createTask("copy"); |
| copy.setTodir(tmp); |
| copy.setOverwrite(true); |
| copy.addFileset(s); |
| Mapper m = copy.createMapper(); |
| Mapper.MapperType mt = new Mapper.MapperType(); |
| mt.setValue("glob"); |
| m.setType(mt); |
| m.setFrom("*_" + s.branding + suffix); |
| m.setTo("*" + suffix); |
| copy.init(); |
| copy.setLocation(getLocation()); |
| copy.execute(); |
| if (locale != null) { |
| // Possibly have e.g. x_f4j_ja.html. |
| suffix = "_" + locale + suffix; |
| copy = (Copy) getProject().createTask("copy"); |
| copy.setTodir(tmp); |
| copy.setOverwrite(true); |
| copy.addFileset(s); |
| m = copy.createMapper(); |
| mt = new Mapper.MapperType(); |
| mt.setValue("glob"); |
| m.setType(mt); |
| m.setFrom("*_" + s.branding + suffix); |
| m.setTo("*" + suffix); |
| copy.init(); |
| copy.setLocation(getLocation()); |
| copy.execute(); |
| } |
| } |
| } |
| // Now replace basedir & files with this temp dir. |
| basedir = tmp; |
| FileSet tmpf = new FileSet(); |
| tmpf.setProject(getProject()); |
| tmpf.setDir(tmp); |
| files = tmpf.getDirectoryScanner(getProject()).getIncludedFiles(); |
| } |
| log ("Running JavaHelp search database indexer..."); |
| try { |
| File config = File.createTempFile ("jhindexer-config", ".txt"); |
| try { |
| try (OutputStream os = new FileOutputStream (config)) { |
| PrintWriter pw = new PrintWriter (os); |
| pw.println ("IndexRemove " + basedir + File.separator); |
| String message = "Files to be indexed:"; |
| for (int i = 0; i < files.length; i++) { |
| // [PENDING] JavaHelp docs say to use / as file sep for File directives; |
| // so what should the complete path be? Someone should test this on Windoze... |
| String path = basedir + File.separator + files[i]; |
| pw.println ("File " + path); |
| message += "\n\t" + path; |
| } |
| log (message, Project.MSG_VERBOSE); |
| pw.flush (); |
| } |
| AntClassLoader loader = new AntClassLoader(getProject(), classpath); |
| loader.addLoaderPackageRoot("com.sun.java.help.search"); |
| try { |
| Class<?> clazz = loader.loadClass("com.sun.java.help.search.Indexer"); |
| Method main = clazz.getMethod("main", String[].class); |
| List<String> args = Arrays.asList( |
| "-c", config.getAbsolutePath(), |
| "-db", db.getAbsolutePath() |
| ); |
| if (locale != null) { |
| args = new ArrayList<>(args); // #35244 |
| args.add("-locale"); |
| args.add(locale); |
| } |
| main.invoke(null, new Object[] {args.toArray(new String[args.size()])}); |
| } catch (InvocationTargetException ite) { |
| throw new BuildException(ite.getTargetException(), getLocation()); |
| } catch (Exception e) { // ClassNotFoundException, NoSuchMethodException, ... |
| throw new BuildException(e, getLocation()); |
| } |
| } finally { |
| config.delete (); |
| } |
| } catch (IOException ioe) { |
| throw new BuildException("Could not make temporary config file", ioe, getLocation()); |
| } |
| if (maxbranding != null) { |
| // Now rename search DB files to include branding suffix. |
| // Note that DOCS.TAB -> DOCS_brand.TAB to work with nbdocs: protocol. |
| String[] dbfiles = db.list(); |
| for (int i = 0; i < dbfiles.length; i++) { |
| String basename, ext; |
| int idx = dbfiles[i].lastIndexOf('.'); |
| if (idx != -1) { |
| basename = dbfiles[i].substring(0, idx); |
| ext = dbfiles[i].substring(idx); |
| } else { |
| basename = dbfiles[i]; |
| ext = ""; |
| } |
| File old = new File(db, dbfiles[i]); |
| File nue = new File(db, basename + "_" + maxbranding + ext); |
| log("Moving " + old + " to " + nue, Project.MSG_VERBOSE); |
| old.renameTo(nue); |
| } |
| } |
| } |
| |
| } |