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("&", "&").replace("<", "<").replace(">", ">"); 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