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, 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 addLineNumber(String line, String num) {
    110     StringBuilder numberedLine = new StringBuilder();
    111     numberedLine.append("<a class=\"number\"" + "href=\"#l" + num + "\">" + num + "\n</a>");
    112     numberedLine.append("<span class=\"code-line\" id=\"l" + num + "\">" + line + "</span>");
    113     return numberedLine.substring(0);
    114   }
    115 
    116   static String loadInclude(SourcePositionInfo pos, String filename, String id, boolean trim) {
    117     Reader input = null;
    118     StringBuilder result = new StringBuilder();
    119 
    120     String begin = BEGIN_INCLUDE + "(" + id + ")";
    121     String end = END_INCLUDE + "(" + id + ")";
    122 
    123     try {
    124       input = new FileReader(filename);
    125       LineNumberReader lines = new LineNumberReader(input);
    126 
    127       int state = STATE_BEGIN;
    128 
    129       int trimLength = -1;
    130       String trimString = null;
    131       int trailing = 0;
    132 
    133       while (true) {
    134         String line = lines.readLine();
    135         if (line == null) {
    136           return null;
    137         }
    138         switch (state) {
    139           case STATE_BEGIN:
    140             if (line.indexOf(begin) >= 0) {
    141               state = STATE_MATCHING;
    142             }
    143             break;
    144           case STATE_MATCHING:
    145             if (line.indexOf(end) >= 0) {
    146               return result.substring(0);
    147             } else {
    148               boolean empty = "".equals(line.trim());
    149               if (trim) {
    150                 if (isIncludeLine(line)) {
    151                   continue;
    152                 }
    153                 if (trimLength < 0 && !empty) {
    154                   trimString = getTrimString(line);
    155                   if (trimString != null) {
    156                     trimLength = trimString.length();
    157                   }
    158                 }
    159                 if (trimLength >= 0 && line.length() > trimLength) {
    160                   boolean trimThisLine = true;
    161                   for (int i = 0; i < trimLength; i++) {
    162                     if (line.charAt(i) != trimString.charAt(i)) {
    163                       trimThisLine = false;
    164                       break;
    165                     }
    166                   }
    167                   if (trimThisLine) {
    168                     line = line.substring(trimLength);
    169                   }
    170                 }
    171                 if (trimLength >= 0) {
    172                   if (!empty) {
    173                     for (int i = 0; i < trailing; i++) {
    174                       result.append('\n');
    175                     }
    176                     line = escapeHtml(line);
    177                     result.append(line);
    178                     trailing = 1; // add \n next time, maybe
    179                   } else {
    180                     trailing++;
    181                   }
    182                 }
    183               } else {
    184                 result.append(line);
    185                 result.append('\n');
    186               }
    187             }
    188             break;
    189         }
    190       }
    191     } catch (IOException e) {
    192       Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id
    193           + "\" " + filename);
    194     } finally {
    195       if (input != null) {
    196         try {
    197           input.close();
    198         } catch (IOException ex) {}
    199       }
    200     }
    201     Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Did not find " + end + " in file " + filename);
    202     return null;
    203   }
    204 
    205   static String readFile(SourcePositionInfo pos, String filename, String id, boolean trim,
    206       boolean escape, boolean numberedLines, boolean errorOk) {
    207     Reader input = null;
    208     StringBuilder result = new StringBuilder();
    209     int trailing = 0;
    210     boolean started = false;
    211 
    212     try {
    213 
    214       input = new FileReader(filename);
    215       LineNumberReader lines = new LineNumberReader(input);
    216 
    217       while (true) {
    218         String line = lines.readLine();
    219         String lineNum = Integer.toString(lines.getLineNumber());
    220 
    221         if (line == null) {
    222           break;
    223         }
    224 
    225         if (trim) {
    226           if (isIncludeLine(line)) {
    227             continue;
    228           }
    229           if (!"".equals(line.trim())) {
    230             if (started) {
    231               for (int i = 0; i < trailing; i++) {
    232                 result.append('\n');
    233               }
    234             }
    235             if (escape) {
    236               line = escapeHtml(line);
    237             }
    238             if (numberedLines) {
    239               line = addLineNumber(line, lineNum);
    240             }
    241             result.append(line);
    242             trailing = 1; // add \n next time, maybe
    243             started = true;
    244           } else {
    245             if (started) {
    246               if (numberedLines) {
    247                 result.append('\n');
    248                 line = line + " ";
    249                 line = addLineNumber(line, lineNum);
    250                 result.append(line);
    251               } else {
    252                 trailing++;
    253               }
    254             }
    255           }
    256         } else {
    257             if (numberedLines) {
    258               line = addLineNumber(line, lineNum);
    259             }
    260           result.append(line);
    261           result.append('\n');
    262         }
    263       }
    264     } catch (IOException e) {
    265       if (errorOk) {
    266         return null;
    267       } else {
    268         Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id
    269             + "\" " + filename);
    270       }
    271     } finally {
    272       if (input != null) {
    273         try {
    274           input.close();
    275         } catch (IOException ex) {}
    276       }
    277     }
    278     return result.substring(0);
    279   }
    280 
    281   @Override
    282   public void makeHDF(Data data, String base) {
    283     data.setValue(base + ".name", name());
    284     data.setValue(base + ".kind", kind());
    285     if (mIncluded != null) {
    286       data.setValue(base + ".text", mIncluded);
    287     } else {
    288       data.setValue(base + ".text", "INCLUDE_ERROR");
    289     }
    290   }
    291 }
    292