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