/* *******************************************************************
* Copyright (c) 2003 Contributors.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* Contributors:
* Wes Isberg initial implementation
* ******************************************************************/
/*
* A quickie hack to extract sample code from testable sources.
* This could reuse a lot of code from elsewhere,
* but currently doesn't,
* to keep it in the build module which avoids dependencies.
* (Too bad we can't use scripting languages...)
*/
package org.aspectj.internal.tools.build;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
/**
* This gathers sample code delimited with [START..END]-SAMPLE
* from source files under a base directory,
* along with any @author
info.
*
// START-SAMPLE {anchorName} {anchorText} * ... sample code ... * // END-SAMPLE {anchorName} ** where {anchorName} need not be unique and might be * hierarchical wrt "-", e.g., "genus-species-individual". */ public class SampleGatherer { /** EOL String for gathered lines */ public static final String EOL = "\n"; // XXX static final String START = "START-SAMPLE"; static final String END = "END-SAMPLE"; static final String AUTHOR = "@author"; static final String FLAG = "XXX"; // private static void test(String[] args){ // String[] from = new String[] { "
", "" }; // String[] to = new String[] { "<pre>", "</pre>" }; // String source = "in this
day andage of
"; // System.err.println("from " + source); // System.err.println(" to " + SampleUtil.replace(source, from, to)); // source = "day and"; // System.err.println("from " + source); // System.err.println(" to " + SampleUtil.replace(source, from, to)); // source = "0) && (start < line.length())) { line = line.substring(start); } line = line.trim(); if (line.endsWith("*/")) { line = line.substring(0, line.length()-2).trim(); } else if (line.endsWith("-->")) { line = line.substring(0, line.length()-3).trim(); } return line; } private static void doGather(File source, Samples sink) throws IOException { if (source.isFile()) { if (isSource(source)) { gatherFromFile(source, sink); } } else if (source.isDirectory() && source.canRead()) { File[] files = source.listFiles(); for (File file : files) { doGather(file, sink); } } } private static boolean isSource(File file) { if ((null == file) || !file.isFile() || !file.canRead()) { return false; } String path = file.getName().toLowerCase(); String[] suffixes = Sample.Kind.SOURCE_SUFFIXES; for (String suffix : suffixes) { if (path.endsWith(suffix)) { return true; } } return false; } private static void gatherFromFile(final File source, final Samples sink) throws IOException { Reader reader = null; try { String author = null; StringBuilder sampleCode = new StringBuilder(); String anchorName = null; String anchorTitle = null; ArrayListflags = new ArrayList<>(); int startLine = -1; // seeking int endLine = Integer.MAX_VALUE; // not seeking reader = new FileReader(source); LineNumberReader lineReader = new LineNumberReader(reader); String line; while (null != (line = lineReader.readLine())) { // XXX naive // found start? int loc = line.indexOf(START); if (-1 != loc) { int lineNumber = lineReader.getLineNumber(); if (-1 != startLine) { abort("unexpected " + START, source, line, lineNumber); } startLine = lineNumber; endLine = -1; anchorName = trimCommentEnd(line, loc + START.length()); loc = anchorName.indexOf(" "); if (-1 == loc) { anchorTitle = null; } else { anchorTitle = anchorName.substring(1+loc).trim(); anchorName = anchorName.substring(0, loc); } continue; } // found end? loc = line.indexOf(END); if (-1 != loc) { int lineNumber = lineReader.getLineNumber(); if (Integer.MAX_VALUE == endLine) { abort("unexpected " + END, source, line, lineNumber); } String newtag = trimCommentEnd(line, loc + END.length()); if ((newtag.length() > 0) && !newtag.equals(anchorName)) { String m = "expected " + anchorName + " got " + newtag; abort(m, source, line, lineNumber); } endLine = lineNumber; Sample sample = new Sample(anchorName, anchorTitle, author, sampleCode.toString(), source, startLine, endLine, flags.toArray(new String[0])); sink.addSample(sample); // back to seeking start sampleCode.setLength(0); startLine = -1; endLine = Integer.MAX_VALUE; continue; } // found author? loc = line.indexOf(AUTHOR); if (-1 != loc) { author = trimCommentEnd(line, loc + AUTHOR.length()); } // found flag comment? loc = line.indexOf(FLAG); if (-1 != loc) { flags.add(trimCommentEnd(line, loc + FLAG.length())); } // reading? if ((-1 != startLine) && (-1 == endLine)) { sampleCode.append(line); sampleCode.append(EOL); } } if (-1 == endLine) { abort("incomplete sample", source, "", lineReader.getLineNumber()); } } finally { if (null != reader) { reader.close(); } } } private static void abort(String why, File file, String line, int lineNumber) throws Abort { throw new Abort(why + " at " + file + ":" + lineNumber + ": " + line); } // private static void delay(Object toDelay) { // synchronized (toDelay) { // XXX sleep instead? // toDelay.notifyAll(); // } // } static class Abort extends IOException { private static final long serialVersionUID = -1l; Abort(String s) { super(s); } } } /** * Data associated with sample code - struct class. */ class Sample { public static final String ASPECTJ_TEAM = "The AspectJ Team"; /** sort by anchorName, file path, and start/end location */ static Comparator NAME_SOURCE_COMPARER = new Comparator () { public int compare(Sample left, Sample right) { if (null == left) { return (null == right ? 0 : -1); } if (null == right) { return 1; } int result = left.anchorName.compareTo(right.anchorName); if (0 != result) { return result; } result = left.sourcePath.compareTo(right.sourcePath); if (0 != result) { return result; } result = right.startLine - left.startLine; if (0 != result) { return result; } return right.endLine - left.endLine; } }; /** sort by author, then NAME_SOURCE_COMPARER */ static Comparator AUTHOR_NAME_SOURCE_COMPARER = new Comparator () { public int compare(Sample left, Sample right) { if (null == left) { return (null == right ? 0 : -1); } if (null == right) { return 1; } int result = left.author.compareTo(right.author); if (0 != result) { return result; } return NAME_SOURCE_COMPARER.compare(left,right); } }; final String anchorName; final String anchorTitle; final String author; final String sampleCode; final File sourcePath; final int startLine; final int endLine; final Kind kind; /** List of String flags found in the sample */ final List flags; public Sample( String anchorName, String anchorTitle, String author, String sampleCode, File sourcePath, int startLine, int endLine, String[] flags) { this.anchorName = anchorName; this.anchorTitle = anchorTitle; this.author = (null != author ? author : ASPECTJ_TEAM); this.sampleCode = sampleCode; this.sourcePath = sourcePath; this.startLine = startLine; this.endLine = endLine; this.kind = Kind.getKind(sourcePath); // List theFlags; if ((null == flags) || (0 == flags.length)) { this.flags = Collections.emptyList(); } else { this.flags = Collections.unmodifiableList(Arrays.asList(flags)); } } public String toString() { return sampleCode; } public static class Kind { /** lowercase source suffixes identify files to gather samples from */ public static final String[] SOURCE_SUFFIXES = new String[] { ".java", ".aj", ".sh", ".ksh", ".txt", ".text", ".html", ".htm", ".xml" }; static final Kind XML = new Kind(); static final Kind HTML = new Kind(); static final Kind PROGRAM = new Kind(); static final Kind SCRIPT = new Kind(); static final Kind TEXT = new Kind(); static final Kind OTHER = new Kind(); public static Kind getKind(File file) { if (null == file) { return OTHER; } String name = file.getName().toLowerCase(); if ((name.endsWith(".java") || name.endsWith(".aj"))) { return PROGRAM; } if ((name.endsWith(".html") || name.endsWith(".htm"))) { return HTML; } if ((name.endsWith(".sh") || name.endsWith(".ksh"))) { return SCRIPT; } if ((name.endsWith(".txt") || name.endsWith(".text"))) { return TEXT; } if (name.endsWith(".xml")) { return XML; } return OTHER; } private Kind() { } } } /** * type-safe Collection of samples. */ class Samples { private ArrayList samples = new ArrayList<>(); int size() { return samples.size(); } void addSample(Sample sample) { samples.add(sample); } /** * @return List copy, sorted by Sample.NAME_SOURCE_COMPARER */ List getSortedSamples() { return getSortedSamples(Sample.NAME_SOURCE_COMPARER); } List getSortedSamples(Comparator comparer) { List result = new ArrayList<>(samples); result.sort(comparer); return result; } } /** * Render samples by using method visitors. */ class SamplesRenderer { public static SamplesRenderer ME = new SamplesRenderer(); protected SamplesRenderer() { } public static final String EOL = "\n"; // XXX public static final String INFO = " This contains contributions from the AspectJ community of " + "
" + "
- sample code for AspectJ programs,
" + "- sample code for extensions to AspectJ tools using the public API's,
" + "- sample scripts for invoking AspectJ tools, and
" + "- documentation trails showing how to do given tasks" + " using AspectJ, AJDT, or various IDE or deployment" + " environments.
Find complete source files in the AspectJ CVS repository at " + "
"; public static final String COPYRIGHT = "org.aspectj/modules/docs/sandbox
. " + "For instructions on downloading code from the CVS repository, " + "see the FAQ entry " + "\"buildingsource\".Copyright 2003 Contributors. All Rights Reserved. " + "This sample code is made available under the Eclipse Public " + "License v 2.0 available at " + "" + "https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt. " + "Contributors are listed in this document as authors. " + "Permission to republish portions of this sample code " + "is hereby granted if the publication acknowledges " + "the author by name and " + "the source by reference to the AspectJ project home page " + "at http://eclipse.org/aspectj.
" + EOL; /** template algorithm to render */ public final StringBuffer render(Samples samples, StringBuffer sink) { if (null == sink) { sink = new StringBuffer(); } if ((null == samples) || (0 == samples.size())) { return sink; } startList(samples, sink); Listlist = samples.getSortedSamples(); String anchorName = null; for (Sample sample : list) { String newAnchorName = sample.anchorName; if ((null == anchorName) || (!anchorName.equals(newAnchorName))) { endAnchorName(anchorName, sink); startAnchorName(newAnchorName, sample.anchorTitle, sink); anchorName = newAnchorName; } render(sample, sink); } endAnchorName(anchorName, sink); endList(samples, sink); return sink; } protected void startList(Samples samples, StringBuffer sink) { sink.append("Printing " + samples.size() + " samples"); sink.append(EOL); } protected void startAnchorName(String name, String title, StringBuffer sink) { sink.append("anchor " + name); sink.append(EOL); } protected void render(Sample sample, StringBuffer sink) { SampleUtil.render(sample, "=", ", ",sink); sink.setLength(sink.length()-2); sink.append(EOL); } /** * @param name the String name being ended - ignore if null * @param sink */ protected void endAnchorName(String name, StringBuffer sink) { if (null == name) { return; } } protected void endList(Samples samples, StringBuffer sink) { sink.append("Printed " + samples.size() + " samples"); sink.append(EOL); } } // XXX need DocBookSamplesRenderer /** * Output the samples as a single HTML file, with a table of contents * and sorting the samples by their anchor tags. */ class HTMLSamplesRenderer extends SamplesRenderer { public static SamplesRenderer ME = new HTMLSamplesRenderer(); // XXX move these public static boolean doHierarchical = true; public static boolean doFlags = false; final StringBuffer tableOfContents; final StringBuffer sampleSection; String[] lastAnchor = new String[0]; String currentAnchor; String currentAuthor; protected HTMLSamplesRenderer() { sampleSection = new StringBuffer(); tableOfContents = new StringBuffer(); } protected void startAnchorName(String name, String title, StringBuffer sink) { if (doHierarchical) { doContentTree(name); } // ---- now do anchor tableOfContents.append(" " + title + " "); tableOfContents.append(EOL); currentAnchor = name; } protected void startList(Samples samples, StringBuffer sink) { } protected void render(Sample sample, StringBuffer sink) { if (null != currentAnchor) { if (!currentAnchor.equals(sample.anchorName)) { String m = "expected " + currentAnchor + " got " + sample.anchorName; throw new Error(m); } currentAnchor = null; } // do heading then code renderHeading(sample.anchorName, sample.anchorTitle, sampleSection); if (sample.kind == Sample.Kind.HTML) { renderHTML(sample); } else if (sample.kind == Sample.Kind.XML) { renderXML(sample); } else { renderPre(sample); } } protected boolean doRenderAuthor(Sample sample) { return (null != sample.author); // && !sample.author.equals(currentAuthor) } protected void renderStandardHeader(Sample sample) { // XXX starting same as pre if (doRenderAuthor(sample)) { currentAuthor = sample.author; sampleSection.append("| " + currentAuthor); sampleSection.append(EOL); } sampleSection.append(" | "); sampleSection.append(SampleUtil.renderCodePath(sample.sourcePath)); sampleSection.append(":" + sample.startLine); sampleSection.append(" |"); sampleSection.append(EOL); sampleSection.append("
"); sampleSection.append(EOL); if (doFlags) { boolean flagHeaderDone = false; for (String flag : sample.flags) { if (!flagHeaderDone) { sampleSection.append("
Comments flagged:
"); sampleSection.append(EOL); flagHeaderDone = true; } sampleSection.append("
"); sampleSection.append(EOL); } } } protected void renderXML(Sample sample) { renderStandardHeader(sample); sampleSection.append("- "); sampleSection.append(flag); sampleSection.append("
"); } if (flagHeaderDone) { sampleSection.append(""); sampleSection.append(EOL); sampleSection.append(prepareXMLSample(sample.sampleCode)); sampleSection.append(EOL); sampleSection.append(""); sampleSection.append(EOL); } protected void renderHTML(Sample sample) { renderStandardHeader(sample); sampleSection.append(EOL); sampleSection.append(prepareHTMLSample(sample.sampleCode)); sampleSection.append(EOL); } protected void renderPre(Sample sample) { renderStandardHeader(sample); sampleSection.append(""); sampleSection.append(EOL); sampleSection.append(prepareCodeSample(sample.sampleCode)); sampleSection.append(""); sampleSection.append(EOL); } protected void endAnchorName(String name, StringBuffer sink) { if (null == name) { return; } currentAnchor = null; currentAuthor = null; // authors don't span anchors } protected void endList(Samples samples, StringBuffer sink) { sink.append(""); sink.append(EOL); sink.append("AspectJ sample code "); sink.append(EOL); sink.append(""); sink.append(EOL); sink.append(" "); sink.append(EOL); sink.append("AspectJ sample code
"); sink.append(INFO); sink.append(EOL); sink.append(COPYRIGHT); sink.append(EOL); sink.append("Generated on "); sink.append(DateFormat.getDateInstance().format(new Date())); sink.append(" by SamplesGatherer"); sink.append(EOL); sink.append("
Contents
"); sink.append(EOL); sink.append(""); sink.append(EOL); sink.append(tableOfContents.toString()); // unwind to common prefix, if necessary for (int i = 0; i < lastAnchor.length ; i++) { sink.append("
"); } sink.append("Author Index "); sink.append(" "); sink.append("Listings
"); sink.append(EOL); sink.append(sampleSection.toString()); renderAuthorIndex(samples, sink); sink.append(""); sink.append(EOL); } protected String prepareXMLSample(String sampleCode) { String[] from = new String[] {"\t", "<"}; String[] to = new String[] {" ", "<"}; return (SampleUtil.replace(sampleCode, from, to)); } protected String prepareHTMLSample(String sampleCode) { String[] from = new String[20]; String[] to = new String[20]; for (int i = 0; i < to.length; i++) { String h = "h" + i + ">"; from[i] = "<" + h; to[i] = ""; from[++i] = "" + h; to[i] = "
"; } return (SampleUtil.replace(sampleCode, from, to)); } protected String prepareCodeSample(String sampleCode) { String[] from = new String[] { "
", "" }; String[] to = new String[] { "<pre>", "</pre>" }; return (SampleUtil.replace(sampleCode, from, to)); } protected void renderHeading(String anchor, String title, StringBuffer sink) { sink.append(" "); sink.append(EOL); if ((null == title) || (0 == title.length())) { title = anchor; } sink.append("" + title + "
"); sink.append(EOL); sink.append("back to top"); sink.append(EOL); } /** * Manage headings in both table of contents and listings. * @param name the String anchor */ protected void doContentTree(String name) { if (name.equals(lastAnchor)) { return; } // ---- handle trees String[] parts = SampleUtil.splitAnchorName(name); //String[] lastAnchor = (String[]) lastAnchors.peek(); int firstDiff = SampleUtil.commonPrefix(parts, lastAnchor); // unwind to common prefix, if necessary if (firstDiff+1 < lastAnchor.length) { for (int i = 1; i < lastAnchor.length-firstDiff ; i++) { tableOfContents.append(" "); tableOfContents.append(EOL); } } // build up prefix StringBuilder branchAnchor = new StringBuilder(); for (int i = 0; i < firstDiff;) { branchAnchor.append(parts[i]); i++; branchAnchor.append("-"); } // emit leading headers, but not anchor itself for (int i = firstDiff; i < (parts.length-1); i++) { branchAnchor.append(parts[i]); String prefixName = branchAnchor.toString(); branchAnchor.append("-"); tableOfContents.append("" + prefixName + " "); tableOfContents.append(EOL); tableOfContents.append(""); tableOfContents.append(EOL); renderHeading(prefixName, prefixName, sampleSection); } lastAnchor = parts; } protected void renderAuthorIndex(Samples samples, StringBuffer sink) { sink.append("
"); } sink.append("Author Index
"); Listlist = samples.getSortedSamples(Sample.AUTHOR_NAME_SOURCE_COMPARER); String lastAuthor = null; for (Sample sample : list) { String author = sample.author; if (!author.equals(lastAuthor)) { if (null != lastAuthor) { sink.append(" "); sink.append(author); sink.append(EOL); sink.append(" "); sink.append(EOL); lastAuthor = author; } sink.append("
- "); if (null == sample.anchorTitle) { sink.append(sample.anchorName); } else { sink.append(sample.anchorTitle); } sink.append("
"); } } } class SampleUtil { public static final String SAMPLE_BASE_DIR_NAME = "sandbox"; public static void simpleRender(Samples result, StringBuffer sink) { List sortedSamples = result.getSortedSamples(); int i = 0; for (Object sortedSample : sortedSamples) { Sample sample = (Sample) sortedSample; sink.append(i++ + ": " + sample); } } /** result struct for getPackagePath */ static class JavaFile { /** input File possibly signifying a java file */ final File path; /** String java path suffix in form "com/company/Bar.java" * null if this is not a java file */ final String javaPath; /** any prefix before java path suffix in the original path */ final String prefix; /** error handling */ final Throwable thrown; JavaFile(File path, String javaPath, String prefix, Throwable thrown) { this.path = path; this.javaPath = javaPath; this.prefix = prefix; this.thrown = thrown; } } /** * Read any package statement in the file to determine * the package path of the file * @param path the File to seek the package in * @return the JavaFile with the components of the path */ public static JavaFile getJavaFile(File path) { if (null == path) { throw new IllegalArgumentException("null path"); } String result = path.getPath().replace('\\', '/'); String packag = ""; String javaPath = null; String prefix = null; Throwable thrown = null; if (result.endsWith(".java") || result.endsWith(".aj")) { FileReader reader = null; try { reader = new FileReader(path); BufferedReader br = new BufferedReader(reader); String line; while (null != (line = br.readLine())) { int loc = line.indexOf("package"); if (-1 != loc) { int end = line.indexOf(";"); if (-1 == loc) { String m = "unterminated package statement \""; throw new Error(m + line + "\" in " + path); } packag = (line.substring(loc + 7, end) + ".") .trim() .replace('.', '/'); break; } loc = line.indexOf("import"); if (-1 != loc) { break; } } } catch (IOException e) { thrown = e; } finally { if (null != reader) { try { reader.close(); } catch (IOException e1) { // ignore } } } if (null == thrown) { javaPath = packag + path.getName(); int loc = result.indexOf(javaPath); if (-1 == loc) { String m = "expected suffix " + javaPath + " in "; throw new Error(m + result); } prefix = result.substring(0, loc); } } return new JavaFile(path, javaPath, prefix, thrown); } /** * Extract file path relative to base of package directory * and directory in SAMPLE_BASE_DIR_NAME for this file. * @param path the File to render from SAMPLE_BASE_DIR_NAME * @return String "baseDir {path}" */ public static String renderCodePath(File path) { JavaFile javaFile = getJavaFile(path); if (javaFile.thrown != null) { throw new Error(javaFile.thrown.getClass() + ": " + javaFile.thrown.getMessage()); } String file = javaFile.javaPath; // can be null... String prefix = javaFile.prefix; if (prefix == null) { prefix = path.getPath().replace('\\', '/'); } int loc = prefix.lastIndexOf(SAMPLE_BASE_DIR_NAME); if (-1 == loc) { String m = "not after " + SAMPLE_BASE_DIR_NAME; throw new IllegalArgumentException(m + "?: " + path); } prefix = prefix.substring(loc + 1 + SAMPLE_BASE_DIR_NAME.length()); if (file == null) { int slash = prefix.lastIndexOf('/'); if (-1 == slash) { file = prefix; prefix = ""; } else { file = prefix.substring(slash+1); prefix = prefix.substring(0, slash); } } if (prefix.endsWith("/")) { prefix = prefix.substring(0, prefix.length()-1); } return (prefix + " " + file).trim(); } public static int commonPrefix(String[] lhs, String[] rhs) { final int max = smallerSize(lhs, rhs); int firstDiff = 0; while (firstDiff < max) { if (!lhs[firstDiff].equals(rhs[firstDiff])) { break; } firstDiff++; } return firstDiff; } private static int smallerSize(Object[] one, Object[] two) { if ((null == one) || (null == two)) { return 0; } return (one.length > two.length ? two.length : one.length); } public static String[] splitAnchorName(Sample sample) { return splitAnchorName(sample.anchorName); } public static String[] splitAnchorName(String anchorName) { ArrayListresult = new ArrayList<>(); int start = 0; int loc = anchorName.indexOf("-", start); String next; while (loc != -1) { next = anchorName.substring(start, loc); result.add(next); start = loc+1; loc = anchorName.indexOf("-", start); } next = anchorName.substring(start); result.add(next); return result.toArray(new String[0]); } /** * Replace literals with literals in source string * @param source the String to modify * @param from the String[] of literals to replace * @param to the String[] of literals to use when replacing * @return the String source as modified by the replaces */ public static String replace(String source, String[] from, String[] to) { if ((null == source) || (0 == source.length())) { return source; } if (from.length != to.length) { throw new IllegalArgumentException("unmatched from/to"); } StringBuilder result = new StringBuilder(); int LEN = source.length(); int start = 0; for (int i = 0; i < LEN; i++) { String suffix = source.substring(i); for (int j = 0; j < from.length; j++) { if (suffix.startsWith(from[j])) { result.append(source.substring(start, i)); result.append(to[j]); start = i + from[j].length(); i = start-1; break; } } } if (start < source.length()) { result.append(source.substring(start)); } return result.toString(); } public static void render( Sample sample, String fieldDelim, String valueDelim, StringBuffer sink) { if ((null == sink) || (null == sample)) { return; } if (null == fieldDelim) { fieldDelim = ""; } if (null == valueDelim) { valueDelim = ""; } sink.append("anchorName"); sink.append(valueDelim); sink.append(sample.anchorName); sink.append(fieldDelim); sink.append("author"); sink.append(valueDelim); sink.append(sample.author); sink.append(fieldDelim); sink.append("sourcePath"); sink.append(valueDelim); sink.append(sample.sourcePath.toString()); sink.append(fieldDelim); sink.append("startLine"); sink.append(valueDelim); sink.append(sample.startLine); sink.append(fieldDelim); sink.append("endLine"); sink.append(valueDelim); sink.append(sample.endLine); sink.append(fieldDelim); sink.append("sampleCode"); sink.append(valueDelim); sink.append(sample.sampleCode.toString()); sink.append(fieldDelim); } private SampleUtil(){} }