blob: 9b9e40ad5cba4080f8d21ee18a08dd8d50465290 [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)) {
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}