1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2004, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.jct.utils.mapmerge;
  28 
  29 import java.io.BufferedReader;
  30 import java.io.BufferedWriter;
  31 import java.io.File;
  32 import java.io.FileReader;
  33 import java.io.FileWriter;
  34 import java.io.IOException;
  35 import java.io.PrintStream;
  36 import java.io.PrintWriter;
  37 import java.io.Reader;
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.Iterator;
  41 import java.util.List;
  42 import java.util.Map;
  43 import java.util.TreeMap;
  44 import org.apache.tools.ant.BuildException;
  45 import org.apache.tools.ant.FileScanner;
  46 import org.apache.tools.ant.taskdefs.MatchingTask;
  47 import org.apache.tools.ant.types.FileSet;
  48 
  49 /**
  50  * A utility to merge JavaHelp map files.
  51  */
  52 public class Main
  53 {
  54     /**
  55      * An exception to report bad command line arguments.
  56      */
  57     public static class BadArgs extends Exception {
  58         BadArgs(String msg) {
  59             super(msg);
  60         }
  61     }
  62 
  63     /**
  64      * Command line entry point.<br>
  65      * @param args Command line arguments, per the usage as described.
  66      */
  67     public static void main(String[] args) {
  68         try {
  69             if (args.length == 0)
  70                 usage(System.err);
  71             else {
  72                 Main m = new Main(args);
  73                 m.run();
  74             }
  75         }
  76         catch (BadArgs e) {
  77             System.err.println(e);
  78             usage(System.err);
  79             System.exit(1);
  80         }
  81         catch (Throwable t) {
  82             t.printStackTrace();
  83             System.exit(2);
  84         }
  85     }
  86 
  87     /**
  88      * Write out short command line help.
  89      * @param out A stream to which to write the help.
  90      */
  91     private static void usage(PrintStream out) {
  92         String program = System.getProperty("program", "java " + Main.class.getName());
  93         out.println("Usage:");
  94         out.println("   " + program + " options files...");
  95         out.println("");
  96         out.println("Arguments:");
  97         out.println("-o file");
  98         out.println("        Output file.");
  99         out.println("files...");
 100         out.println("        Input files to be merged.");
 101     }
 102 
 103     public Main() { }
 104 
 105     /**
 106      * Create an object based on command line args.
 107      * It is an error if no input files or no output file is given.
 108      * @param args Command line args.
 109      * @see #main
 110      * @throws Main.BadArgs if problems are found in the given arguments.
 111      */
 112     public Main(String[] args) throws BadArgs {
 113         for (int i = 0; i < args.length; i++) {
 114             if (args[i].equals("-o") && i + 1 < args.length) {
 115                 outFile = new File(args[++i]);
 116             }
 117             else {
 118                 inFiles = new File[args.length - i];
 119                 for (int j = 0; j < inFiles.length; j++)
 120                     inFiles[j] = new File(args[i++]);
 121             }
 122 
 123         }
 124     }
 125 
 126     public static class Ant extends MatchingTask {
 127         private Main m = new Main();
 128         private List<FileSet> fileSets = new ArrayList<FileSet>();
 129 
 130         public void setOutFile(File file) {
 131             m.outFile = file;
 132         }
 133 
 134         public void addFileSet(FileSet fs) {
 135             fileSets.add(fs);
 136         }
 137 
 138         public void execute() {
 139             for (Iterator iter = fileSets.iterator(); iter.hasNext(); ) {
 140                 FileSet fs = (FileSet) iter.next();
 141                 FileScanner s = fs.getDirectoryScanner(getProject());
 142                 m.addFiles(s.getBasedir(), s.getIncludedFiles());
 143             }
 144             try {
 145                 m.run();
 146             } catch (BadArgs e) {
 147                 throw new BuildException(e.getMessage());
 148             } catch (IOException e) {
 149                 throw new BuildException(e);
 150             }
 151         }
 152     }
 153 
 154     public void addFiles(File baseDir, String[] paths) {
 155         if (paths == null)
 156             return;
 157         List<File> files = new ArrayList<File>();
 158         if (inFiles != null)
 159             files.addAll(Arrays.asList(inFiles));
 160         for (int i = 0; i < paths.length; i++)
 161             files.add(new File(baseDir, paths[i]));
 162         inFiles = files.toArray(new File[files.size()]);
 163     }
 164 
 165     private void run() throws BadArgs, IOException
 166     {
 167 
 168         if (inFiles == null || inFiles.length == 0)
 169             throw new BadArgs("no input files specified");
 170 
 171         if (outFile == null)
 172             throw new BadArgs("no output file specified");
 173 
 174         map = new TreeMap<>();
 175 
 176         for (int i = 0; i < inFiles.length; i++)
 177             read(inFiles[i]);
 178 
 179         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outFile)));
 180         out.println("<?xml version='1.0' encoding='ISO-8859-1' ?>");
 181         out.println("<!DOCTYPE map");
 182         out.println("  PUBLIC \"-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN\"");
 183         out.println("         \"http://java.sun.com/products/javahelp/map_1_0.dtd\">");
 184         out.println("<map version=\"1.0\">");
 185 
 186         int maxLen = 0;
 187         for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
 188             Map.Entry e = (Map.Entry) (iter.next());
 189             String target = (String) (e.getKey());
 190             String url = (String) (e.getValue());
 191             maxLen = Math.max(maxLen, target.length());
 192         }
 193 
 194         for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
 195             Map.Entry e = (Map.Entry) (iter.next());
 196             String target = (String) (e.getKey());
 197             String url = (String) (e.getValue());
 198             out.print("  <mapID target=\"" + target + "\"  ");
 199             for (int i = target.length(); i < maxLen; i++)
 200                 out.print(' ');
 201             out.println(" url=\"" + url + "\" />");
 202         }
 203 
 204         out.println("</map>");
 205         out.close();
 206     }
 207 
 208     private void read(File f) throws IOException {
 209         Reader in = new BufferedReader(new FileReader(f));
 210         currFile = f;
 211         read(in);
 212         in.close();
 213     }
 214 
 215     private void read(Reader in) throws IOException {
 216         this.in = in;
 217         line = 1;
 218         nextCh();
 219         while (c >= 0) {
 220             if (c == '<') {
 221                 nextCh();
 222                 skipSpace();
 223                 switch (c) {
 224                 case '!':
 225                     nextCh();
 226                     if (c == '-') {
 227                         nextCh();
 228                         if (c == '-') {
 229                             nextCh();
 230                             skipComment();
 231                         }
 232                     }
 233                     break;
 234 
 235                 case '?':
 236                     nextCh();
 237                     skipTag();
 238                     break;
 239 
 240                 case '/':
 241                     nextCh();
 242                     skipTag();
 243                     break;
 244 
 245                 default:
 246                     String startTag = scanIdentifier();
 247                     if (isMapID(startTag))
 248                         scanMapID(true);
 249                     else
 250                         skipTag();
 251                 }
 252             }
 253             else {
 254                 nextCh();
 255             }
 256         }
 257 
 258     }
 259 
 260     private boolean isMapID(String tag) {
 261         return tag.equals("mapID");
 262     }
 263 
 264     private void scanMapID(boolean start) throws IOException {
 265         String target = null;
 266         String url = null;
 267 
 268         skipSpace();
 269         while (c != '>') {
 270             if (c == '/') {
 271                 nextCh();
 272                 if (c == '>')
 273                     break;
 274                 else
 275                     throw new IOException("error parsing HTML input (" + currFile + ":" + line + ")");
 276             }
 277 
 278             String att = scanIdentifier();
 279             if (att.equals("target")) {
 280                 target = scanValue();
 281             }
 282             else if (att.equals("url")) {
 283                 url = scanValue();
 284             }
 285             else
 286                 scanValue();
 287             skipSpace();
 288         }
 289         map.put(target, url);
 290         nextCh();
 291     }
 292 
 293     /**
 294      * Read an identifier
 295      */
 296     private String scanIdentifier() throws IOException {
 297         StringBuffer buf = new StringBuffer();
 298         while (true) {
 299             if ((c >= 'a') && (c <= 'z')) {
 300                 buf.append((char)c);
 301                 nextCh();
 302             } else if ((c >= 'A') && (c <= 'Z')) {
 303                 buf.append((char)c);
 304                 nextCh();
 305             } else if ((c >= '0') && (c <= '9')) {
 306                 buf.append((char)c);
 307                 nextCh();
 308             } else if (c == '-') {  // needed for <META HTTP-EQUIV ....>
 309                 buf.append((char)c);
 310                 nextCh();
 311             } else
 312                 if (buf.length() == 0)
 313                     throw new IOException("Identifier expected (" + currFile + ":" + line + ")");
 314                 else
 315                     return buf.toString();
 316         }
 317     }
 318 
 319     /**
 320      * Read the value of an HTML attribute, which may be quoted.
 321      */
 322     private String scanValue() throws IOException {
 323         skipSpace();
 324         if (c != '=')
 325             return "";
 326 
 327         int quote = -1;
 328         nextCh();
 329         skipSpace();
 330         if ((c == '\'') || (c == '\"')) {
 331             quote = c;
 332             nextCh();
 333             skipSpace();
 334         }
 335         StringBuffer buf = new StringBuffer();
 336         while (((quote < 0) && (c != ' ') && (c != '\t') &&
 337                 (c != '\n') && (c != '\r') && (c != '>')) ||
 338                ((quote >= 0) && (c != quote))) {
 339             if (c == -1 || c == '\n' || c == '\r') {
 340                 throw new IOException("mismatched quotes (" + currFile + ":" + line + ")");
 341             }
 342             buf.append((char)c);
 343             nextCh();
 344         }
 345         if (c == quote)
 346             nextCh();
 347         skipSpace();
 348         return buf.toString();
 349     }
 350 
 351     /**
 352      * Skip an HTML comment  <!-- ... -->
 353      */
 354     private void skipComment() throws IOException {
 355         // a comment sequence is "<!--" ... "-->"
 356         // at the time this is called, "<!--" has been read;
 357         int numHyphens = 0;
 358         while (c != -1 && (numHyphens < 2 || c != '>')) {
 359             if (c == '-')
 360                 numHyphens++;
 361             else
 362                 numHyphens = 0;
 363             nextCh();
 364             //System.out.print((char)c);
 365         }
 366         nextCh();
 367     }
 368 
 369     /**
 370      * Skip whitespace.
 371      */
 372     private void skipSpace() throws IOException {
 373         while ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')) {
 374             nextCh();
 375         }
 376     }
 377 
 378 
 379     /**
 380      * Skip the contents of a tag i.e. <...>
 381      */
 382     private void skipTag() throws IOException {
 383         skipSpace();
 384         while (c != '>') {
 385             if (c == '/' || c == '?') {
 386                 nextCh();
 387                 if (c == '>')
 388                     break;
 389                 else
 390                     throw new IOException("error parsing HTML input (" + currFile + ":" + line + ")");
 391             }
 392 
 393             String att = scanIdentifier();
 394             if (att == "")
 395                 throw new IOException("error parsing HTML input (" + currFile + ":" + line + ")");
 396             String value = scanValue();
 397             skipSpace();
 398         }
 399         nextCh();
 400     }
 401 
 402 
 403     /**
 404      * Read the next character.
 405      */
 406     private void nextCh() throws IOException {
 407         c = in.read();
 408         if (c == '\n')
 409             line++;
 410     }
 411 
 412 
 413     private File[] inFiles;
 414     private File outFile;
 415     private Map<String, String> map;
 416 
 417     private Reader in;
 418     private int c;
 419     private File currFile;
 420     private int line;
 421 
 422 }