/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.doclava; import com.google.clearsilver.jsilver.data.Data; import java.io.Reader; import java.io.IOException; import java.io.FileReader; import java.io.LineNumberReader; import java.util.regex.Pattern; import java.util.regex.Matcher; /* * SampleTagInfo copies text from a given file into the javadoc comment. * * The @include tag copies the text verbatim from the given file. * * The @sample tag copies the text from the given file, stripping leading and trailing whitespace, * and reducing the indent level of the text to the indent level of the first non-whitespace line. * * Both tags accept either a filename and an id or just a filename. If no id is provided, the entire * file is copied. If an id is provided, the lines in the given file between the first two lines * containing BEGIN_INCLUDE(id) and END_INCLUDE(id), for the given id, are copied. The id may be * only letters, numbers and underscore (_). * * Four examples: {@include samples/ApiDemos/src/com/google/app/Notification1.java} {@sample * samples/ApiDemos/src/com/google/app/Notification1.java} {@include * samples/ApiDemos/src/com/google/app/Notification1.java Bleh} {@sample * samples/ApiDemos/src/com/google/app/Notification1.java Bleh} */ public class SampleTagInfo extends TagInfo { public static final SampleTagInfo[] EMPTY_ARRAY = new SampleTagInfo[0]; public static SampleTagInfo[] getArray(int size) { return size == 0 ? EMPTY_ARRAY : new SampleTagInfo[size]; } static final int STATE_BEGIN = 0; static final int STATE_MATCHING = 1; static final Pattern TEXT = Pattern.compile("[\r\n \t]*([^\r\n \t]*)[\r\n \t]*([0-9A-Za-z_]*)[\r\n \t]*", Pattern.DOTALL); private static final String BEGIN_INCLUDE = "BEGIN_INCLUDE"; private static final String END_INCLUDE = "END_INCLUDE"; private ContainerInfo mBase; private String mIncluded; public static String escapeHtml(String str) { return str.replace("&", "&").replace("<", "<").replace(">", ">"); } private static boolean isIncludeLine(String str) { return str.indexOf(BEGIN_INCLUDE) >= 0 || str.indexOf(END_INCLUDE) >= 0; } SampleTagInfo(String name, String kind, String text, ContainerInfo base, SourcePositionInfo position) { super(name, kind, text, position); mBase = base; Matcher m = TEXT.matcher(text); if (!m.matches()) { Errors.error(Errors.BAD_INCLUDE_TAG, position, "Bad @include tag: " + text); return; } String filename = m.group(1); String id = m.group(2); boolean trim = "@sample".equals(name); if (id == null || "".equals(id)) { mIncluded = readFile(position, filename, id, trim, true, false, false); } else { mIncluded = loadInclude(position, filename, id, trim); } if (mIncluded == null) { Errors.error(Errors.BAD_INCLUDE_TAG, position, "include tag '" + id + "' not found in file: " + filename); } } static String getTrimString(String line) { int i = 0; int len = line.length(); for (; i < len; i++) { char c = line.charAt(i); if (c != ' ' && c != '\t') { break; } } if (i == len) { return null; } else { return line.substring(0, i); } } static String addLineNumber(String line, String num) { StringBuilder numberedLine = new StringBuilder(); numberedLine.append("" + num + "\n"); numberedLine.append("" + line + ""); return numberedLine.substring(0); } static String loadInclude(SourcePositionInfo pos, String filename, String id, boolean trim) { Reader input = null; StringBuilder result = new StringBuilder(); String begin = BEGIN_INCLUDE + "(" + id + ")"; String end = END_INCLUDE + "(" + id + ")"; try { input = new FileReader(filename); LineNumberReader lines = new LineNumberReader(input); int state = STATE_BEGIN; int trimLength = -1; String trimString = null; int trailing = 0; while (true) { String line = lines.readLine(); if (line == null) { return null; } switch (state) { case STATE_BEGIN: if (line.indexOf(begin) >= 0) { state = STATE_MATCHING; } break; case STATE_MATCHING: if (line.indexOf(end) >= 0) { return result.substring(0); } else { boolean empty = "".equals(line.trim()); if (trim) { if (isIncludeLine(line)) { continue; } if (trimLength < 0 && !empty) { trimString = getTrimString(line); if (trimString != null) { trimLength = trimString.length(); } } if (trimLength >= 0 && line.length() > trimLength) { boolean trimThisLine = true; for (int i = 0; i < trimLength; i++) { if (line.charAt(i) != trimString.charAt(i)) { trimThisLine = false; break; } } if (trimThisLine) { line = line.substring(trimLength); } } if (trimLength >= 0) { if (!empty) { for (int i = 0; i < trailing; i++) { result.append('\n'); } line = escapeHtml(line); result.append(line); trailing = 1; // add \n next time, maybe } else { trailing++; } } } else { result.append(line); result.append('\n'); } } break; } } } catch (IOException e) { Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id + "\" " + filename); } finally { if (input != null) { try { input.close(); } catch (IOException ex) {} } } Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Did not find " + end + " in file " + filename); return null; } static String readFile(SourcePositionInfo pos, String filename, String id, boolean trim, boolean escape, boolean numberedLines, boolean errorOk) { Reader input = null; StringBuilder result = new StringBuilder(); int trailing = 0; boolean started = false; try { input = new FileReader(filename); LineNumberReader lines = new LineNumberReader(input); while (true) { String line = lines.readLine(); String lineNum = Integer.toString(lines.getLineNumber()); if (line == null) { break; } if (trim) { if (isIncludeLine(line)) { continue; } if (!"".equals(line.trim())) { if (started) { for (int i = 0; i < trailing; i++) { result.append('\n'); } } if (escape) { line = escapeHtml(line); } if (numberedLines) { line = addLineNumber(line, lineNum); } result.append(line); trailing = 1; // add \n next time, maybe started = true; } else { if (started) { if (numberedLines) { result.append('\n'); line = line + " "; line = addLineNumber(line, lineNum); result.append(line); } else { trailing++; } } } } else { if (numberedLines) { line = addLineNumber(line, lineNum); } result.append(line); result.append('\n'); } } } catch (IOException e) { if (errorOk) { return null; } else { Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + " include \"" + id + "\" " + filename); } } finally { if (input != null) { try { input.close(); } catch (IOException ex) {} } } return result.substring(0); } @Override public void makeHDF(Data data, String base) { data.setValue(base + ".name", name()); data.setValue(base + ".kind", kind()); if (mIncluded != null) { data.setValue(base + ".text", mIncluded); } else { data.setValue(base + ".text", "INCLUDE_ERROR"); } } }