blob: 4e14b2b5d873080f678aa7c2dc312497cec7f113 [file] [log] [blame]
Ben Dodson920dbbb2010-08-04 15:21:06 -07001/*
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
17package com.google.doclava;
18
19import com.google.clearsilver.jsilver.data.Data;
20
21import java.io.Reader;
22import java.io.IOException;
23import java.io.FileReader;
24import java.io.LineNumberReader;
25import java.util.regex.Pattern;
26import 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 */
46public 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)) {
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -070082 mIncluded = readFile(position, filename, id, trim, true, false, false);
Ben Dodson920dbbb2010-08-04 15:21:06 -070083 } 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
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700109 static String addLineNumber(String line, String num) {
110 StringBuilder numberedLine = new StringBuilder();
111 numberedLine.append("<li id=\"" + num + "\">");
112 numberedLine.append(line);
113 numberedLine.append("</li>");
114 return numberedLine.substring(0);
115 }
116
Ben Dodson920dbbb2010-08-04 15:21:06 -0700117 static String loadInclude(SourcePositionInfo pos, String filename, String id, boolean trim) {
118 Reader input = null;
119 StringBuilder result = new StringBuilder();
120
121 String begin = BEGIN_INCLUDE + "(" + id + ")";
122 String end = END_INCLUDE + "(" + id + ")";
123
124 try {
125 input = new FileReader(filename);
126 LineNumberReader lines = new LineNumberReader(input);
127
128 int state = STATE_BEGIN;
129
130 int trimLength = -1;
131 String trimString = null;
132 int trailing = 0;
133
134 while (true) {
135 String line = lines.readLine();
136 if (line == null) {
137 return null;
138 }
139 switch (state) {
140 case STATE_BEGIN:
141 if (line.indexOf(begin) >= 0) {
142 state = STATE_MATCHING;
143 }
144 break;
145 case STATE_MATCHING:
146 if (line.indexOf(end) >= 0) {
147 return result.substring(0);
148 } else {
149 boolean empty = "".equals(line.trim());
150 if (trim) {
151 if (isIncludeLine(line)) {
152 continue;
153 }
154 if (trimLength < 0 && !empty) {
155 trimString = getTrimString(line);
156 if (trimString != null) {
157 trimLength = trimString.length();
158 }
159 }
160 if (trimLength >= 0 && line.length() > trimLength) {
161 boolean trimThisLine = true;
162 for (int i = 0; i < trimLength; i++) {
163 if (line.charAt(i) != trimString.charAt(i)) {
164 trimThisLine = false;
165 break;
166 }
167 }
168 if (trimThisLine) {
169 line = line.substring(trimLength);
170 }
171 }
172 if (trimLength >= 0) {
173 if (!empty) {
174 for (int i = 0; i < trailing; i++) {
175 result.append('\n');
176 }
177 line = escapeHtml(line);
178 result.append(line);
179 trailing = 1; // add \n next time, maybe
180 } else {
181 trailing++;
182 }
183 }
184 } else {
185 result.append(line);
186 result.append('\n');
187 }
188 }
189 break;
190 }
191 }
192 } catch (IOException e) {
193 Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id
194 + "\" " + filename);
195 } finally {
196 if (input != null) {
197 try {
198 input.close();
199 } catch (IOException ex) {}
200 }
201 }
202 Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Did not find " + end + " in file " + filename);
203 return null;
204 }
205
206 static String readFile(SourcePositionInfo pos, String filename, String id, boolean trim,
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700207 boolean escape, boolean numberedLines, boolean errorOk) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700208 Reader input = null;
209 StringBuilder result = new StringBuilder();
210 int trailing = 0;
211 boolean started = false;
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700212
Ben Dodson920dbbb2010-08-04 15:21:06 -0700213 try {
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700214
Ben Dodson920dbbb2010-08-04 15:21:06 -0700215 input = new FileReader(filename);
216 LineNumberReader lines = new LineNumberReader(input);
217
218 while (true) {
219 String line = lines.readLine();
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700220 String lineNum = Integer.toString(lines.getLineNumber());
221
Ben Dodson920dbbb2010-08-04 15:21:06 -0700222 if (line == null) {
223 break;
224 }
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700225
Ben Dodson920dbbb2010-08-04 15:21:06 -0700226 if (trim) {
227 if (isIncludeLine(line)) {
228 continue;
229 }
230 if (!"".equals(line.trim())) {
231 if (started) {
232 for (int i = 0; i < trailing; i++) {
233 result.append('\n');
234 }
235 }
236 if (escape) {
237 line = escapeHtml(line);
238 }
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700239 if (numberedLines) {
240 line = addLineNumber(line, lineNum);
241 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700242 result.append(line);
243 trailing = 1; // add \n next time, maybe
244 started = true;
245 } else {
246 if (started) {
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700247 if (numberedLines) {
248 result.append('\n');
249 line = line + " ";
250 line = addLineNumber(line, lineNum);
251 result.append(line);
252 } else {
253 trailing++;
254 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700255 }
256 }
257 } else {
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -0700258 if (numberedLines) {
259 line = addLineNumber(line, lineNum);
260 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700261 result.append(line);
262 result.append('\n');
263 }
264 }
265 } catch (IOException e) {
266 if (errorOk) {
267 return null;
268 } else {
269 Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id
270 + "\" " + filename);
271 }
272 } finally {
273 if (input != null) {
274 try {
275 input.close();
276 } catch (IOException ex) {}
277 }
278 }
279 return result.substring(0);
280 }
281
282 @Override
283 public void makeHDF(Data data, String base) {
284 data.setValue(base + ".name", name());
285 data.setValue(base + ".kind", kind());
286 if (mIncluded != null) {
287 data.setValue(base + ".text", mIncluded);
288 } else {
289 data.setValue(base + ".text", "INCLUDE_ERROR");
290 }
291 }
292}