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