Home | History | Annotate | Download | only in doclava
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.doclava;
     18 
     19 import com.google.clearsilver.jsilver.data.Data;
     20 
     21 import java.io.Reader;
     22 import java.io.IOException;
     23 import java.io.FileReader;
     24 import java.io.LineNumberReader;
     25 import java.util.regex.Pattern;
     26 import java.util.regex.Matcher;
     27 
     28 /*
     29  * SampleTagInfo copies text from a given file into the javadoc comment.
     30  *
     31  * The @include tag copies the text verbatim from the given file.
     32  *
     33  * The @sample tag copies the text from the given file, stripping leading and trailing whitespace,
     34  * and reducing the indent level of the text to the indent level of the first non-whitespace line.
     35  *
     36  * Both tags accept either a filename and an id or just a filename. If no id is provided, the entire
     37  * file is copied. If an id is provided, the lines in the given file between the first two lines
     38  * containing BEGIN_INCLUDE(id) and END_INCLUDE(id), for the given id, are copied. The id may be
     39  * only letters, numbers and underscore (_).
     40  *
     41  * Four examples: {@include samples/ApiDemos/src/com/google/app/Notification1.java} {@sample
     42  * samples/ApiDemos/src/com/google/app/Notification1.java} {@include
     43  * samples/ApiDemos/src/com/google/app/Notification1.java Bleh} {@sample
     44  * samples/ApiDemos/src/com/google/app/Notification1.java Bleh}
     45  */
     46 public class SampleTagInfo extends TagInfo {
     47   static final int STATE_BEGIN = 0;
     48   static final int STATE_MATCHING = 1;
     49 
     50   static final Pattern TEXT =
     51       Pattern.compile("[\r\n \t]*([^\r\n \t]*)[\r\n \t]*([0-9A-Za-z_]*)[\r\n \t]*", Pattern.DOTALL);
     52 
     53   private static final String BEGIN_INCLUDE = "BEGIN_INCLUDE";
     54   private static final String END_INCLUDE = "END_INCLUDE";
     55 
     56   private ContainerInfo mBase;
     57   private String mIncluded;
     58 
     59   public static String escapeHtml(String str) {
     60     return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
     61   }
     62 
     63   private static boolean isIncludeLine(String str) {
     64     return str.indexOf(BEGIN_INCLUDE) >= 0 || str.indexOf(END_INCLUDE) >= 0;
     65   }
     66 
     67   SampleTagInfo(String name, String kind, String text, ContainerInfo base,
     68       SourcePositionInfo position) {
     69     super(name, kind, text, position);
     70     mBase = base;
     71 
     72     Matcher m = TEXT.matcher(text);
     73     if (!m.matches()) {
     74       Errors.error(Errors.BAD_INCLUDE_TAG, position, "Bad @include tag: " + text);
     75       return;
     76     }
     77     String filename = m.group(1);
     78     String id = m.group(2);
     79     boolean trim = "@sample".equals(name);
     80 
     81     if (id == null || "".equals(id)) {
     82       mIncluded = readFile(position, filename, id, trim, true, false);
     83     } else {
     84       mIncluded = loadInclude(position, filename, id, trim);
     85     }
     86 
     87     if (mIncluded == null) {
     88       Errors.error(Errors.BAD_INCLUDE_TAG, position, "include tag '" + id + "' not found in file: "
     89           + filename);
     90     }
     91   }
     92 
     93   static String getTrimString(String line) {
     94     int i = 0;
     95     int len = line.length();
     96     for (; i < len; i++) {
     97       char c = line.charAt(i);
     98       if (c != ' ' && c != '\t') {
     99         break;
    100       }
    101     }
    102     if (i == len) {
    103       return null;
    104     } else {
    105       return line.substring(0, i);
    106     }
    107   }
    108 
    109   static String loadInclude(SourcePositionInfo pos, String filename, String id, boolean trim) {
    110     Reader input = null;
    111     StringBuilder result = new StringBuilder();
    112 
    113     String begin = BEGIN_INCLUDE + "(" + id + ")";
    114     String end = END_INCLUDE + "(" + id + ")";
    115 
    116     try {
    117       input = new FileReader(filename);
    118       LineNumberReader lines = new LineNumberReader(input);
    119 
    120       int state = STATE_BEGIN;
    121 
    122       int trimLength = -1;
    123       String trimString = null;
    124       int trailing = 0;
    125 
    126       while (true) {
    127         String line = lines.readLine();
    128         if (line == null) {
    129           return null;
    130         }
    131         switch (state) {
    132           case STATE_BEGIN:
    133             if (line.indexOf(begin) >= 0) {
    134               state = STATE_MATCHING;
    135             }
    136             break;
    137           case STATE_MATCHING:
    138             if (line.indexOf(end) >= 0) {
    139               return result.substring(0);
    140             } else {
    141               boolean empty = "".equals(line.trim());
    142               if (trim) {
    143                 if (isIncludeLine(line)) {
    144                   continue;
    145                 }
    146                 if (trimLength < 0 && !empty) {
    147                   trimString = getTrimString(line);
    148                   if (trimString != null) {
    149                     trimLength = trimString.length();
    150                   }
    151                 }
    152                 if (trimLength >= 0 && line.length() > trimLength) {
    153                   boolean trimThisLine = true;
    154                   for (int i = 0; i < trimLength; i++) {
    155                     if (line.charAt(i) != trimString.charAt(i)) {
    156                       trimThisLine = false;
    157                       break;
    158                     }
    159                   }
    160                   if (trimThisLine) {
    161                     line = line.substring(trimLength);
    162                   }
    163                 }
    164                 if (trimLength >= 0) {
    165                   if (!empty) {
    166                     for (int i = 0; i < trailing; i++) {
    167                       result.append('\n');
    168                     }
    169                     line = escapeHtml(line);
    170                     result.append(line);
    171                     trailing = 1; // add \n next time, maybe
    172                   } else {
    173                     trailing++;
    174                   }
    175                 }
    176               } else {
    177                 result.append(line);
    178                 result.append('\n');
    179               }
    180             }
    181             break;
    182         }
    183       }
    184     } catch (IOException e) {
    185       Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id
    186           + "\" " + filename);
    187     } finally {
    188       if (input != null) {
    189         try {
    190           input.close();
    191         } catch (IOException ex) {}
    192       }
    193     }
    194     Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Did not find " + end + " in file " + filename);
    195     return null;
    196   }
    197 
    198   static String readFile(SourcePositionInfo pos, String filename, String id, boolean trim,
    199       boolean escape, boolean errorOk) {
    200     Reader input = null;
    201     StringBuilder result = new StringBuilder();
    202     int trailing = 0;
    203     boolean started = false;
    204     try {
    205       input = new FileReader(filename);
    206       LineNumberReader lines = new LineNumberReader(input);
    207 
    208       while (true) {
    209         String line = lines.readLine();
    210         if (line == null) {
    211           break;
    212         }
    213         if (trim) {
    214           if (isIncludeLine(line)) {
    215             continue;
    216           }
    217           if (!"".equals(line.trim())) {
    218             if (started) {
    219               for (int i = 0; i < trailing; i++) {
    220                 result.append('\n');
    221               }
    222             }
    223             if (escape) {
    224               line = escapeHtml(line);
    225             }
    226             result.append(line);
    227             trailing = 1; // add \n next time, maybe
    228             started = true;
    229           } else {
    230             if (started) {
    231               trailing++;
    232             }
    233           }
    234         } else {
    235           result.append(line);
    236           result.append('\n');
    237         }
    238       }
    239     } catch (IOException e) {
    240       if (errorOk) {
    241         return null;
    242       } else {
    243         Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id
    244             + "\" " + filename);
    245       }
    246     } finally {
    247       if (input != null) {
    248         try {
    249           input.close();
    250         } catch (IOException ex) {}
    251       }
    252     }
    253     return result.substring(0);
    254   }
    255 
    256   @Override
    257   public void makeHDF(Data data, String base) {
    258     data.setValue(base + ".name", name());
    259     data.setValue(base + ".kind", kind());
    260     if (mIncluded != null) {
    261       data.setValue(base + ".text", mIncluded);
    262     } else {
    263       data.setValue(base + ".text", "INCLUDE_ERROR");
    264     }
    265   }
    266 }
    267