JFlexXref.java revision 1370
69N/A/*
69N/A * CDDL HEADER START
69N/A *
69N/A * The contents of this file are subject to the terms of the
69N/A * Common Development and Distribution License (the "License").
69N/A * You may not use this file except in compliance with the License.
69N/A *
69N/A * See LICENSE.txt included in this distribution for the specific
69N/A * language governing permissions and limitations under the License.
69N/A *
69N/A * When distributing Covered Code, include this CDDL HEADER in each
69N/A * file and include the License file at LICENSE.txt.
69N/A * If applicable, add the following below this CDDL HEADER, with the
69N/A * fields enclosed by brackets "[]" replaced with your own identifying
69N/A * information: Portions Copyright [yyyy] [name of copyright owner]
69N/A *
69N/A * CDDL HEADER END
69N/A */
69N/A
69N/A/*
69N/A * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
69N/A * Portions Copyright 2011, 2012 Jens Elkner.
69N/A */
69N/A
69N/Apackage org.opensolaris.opengrok.analysis;
69N/A
69N/Aimport java.io.CharArrayReader;
69N/Aimport java.io.IOException;
69N/Aimport java.io.Reader;
69N/Aimport java.io.Writer;
69N/Aimport java.lang.reflect.Field;
69N/Aimport java.util.ArrayList;
228N/Aimport java.util.Comparator;
69N/Aimport java.util.HashMap;
69N/Aimport java.util.Map;
69N/Aimport java.util.Set;
69N/Aimport java.util.SortedSet;
69N/Aimport java.util.TreeSet;
69N/Aimport java.util.logging.Logger;
69N/A
69N/Aimport org.opensolaris.opengrok.analysis.Definitions.Tag;
69N/Aimport org.opensolaris.opengrok.configuration.Project;
69N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
69N/Aimport org.opensolaris.opengrok.history.Annotation;
69N/Aimport org.opensolaris.opengrok.web.Util;
69N/A
69N/A/**
69N/A * Base class for Xref lexers.
69N/A *
69N/A * @author Lubos Kosco
69N/A */
69N/Apublic abstract class JFlexXref {
69N/A private static final Logger logger = Logger.getLogger(JFlexXref.class.getName());
69N/A public Writer out;
69N/A public String urlPrefix = RuntimeEnvironment.getInstance().getUrlPrefix();
69N/A public Annotation annotation;
69N/A public Project project;
69N/A protected Definitions defs;
69N/A /**
69N/A * A stack of <span ...> CSS class names currently opened.
69N/A * It is used to close all open spans for a line and re-open them at the
69N/A * start of the next line to produce wellformed XML. So if a
69N/A * <span ...> is opened and do not closed on the same line, the class
69N/A * name used needs to be pushed onto this stack. If the <span ...>
69N/A * gets closed on a different line, it needs to be popped from this stack.
69N/A * If properly done, the stack should be empty when the last line has been
69N/A * written.
69N/A */
69N/A @SuppressWarnings("serial")
228N/A protected class SpanStack extends ArrayList<String> {
228N/A /**
228N/A * Create a new empty instance with the default capacity.
228N/A */
228N/A public SpanStack() {
228N/A super();
228N/A }
228N/A /**
228N/A * The preferred method to add a new span class name to this instance.
228N/A * @param s class name to add.
228N/A * @see #pop()
228N/A * @throws IllegalArgumentException if the given argument is {@code null}.
69N/A */
69N/A public void push(String s) {
69N/A if (s == null) {
69N/A throw new IllegalArgumentException("null is not allowed");
69N/A }
69N/A super.add(s);
69N/A }
72N/A
69N/A /**
69N/A * The preferred method to remove the last element from this stack.
69N/A * @return {@code null} if the stack is empty, the removed element
69N/A * otherwise.
69N/A * @see #push(String)
69N/A */
69N/A public String pop() {
69N/A return isEmpty() ? null : remove(size() - 1);
69N/A }
69N/A }
69N/A
69N/A /** the {@link SpanStack} for this and only this instance */
69N/A protected final SpanStack spans;
69N/A /** EOF value returned by yylex(). */
69N/A private final int yyeof;
69N/A /** See {@link RuntimeEnvironment#getUserPage()}. Per default initialized
115N/A * in the constructor and here to be consistent and avoid lot of
115N/A * unnecessary lookups.
115N/A * @see #startNewLine() */
115N/A protected String userPageLink;
69N/A /** See {@link RuntimeEnvironment#getUserPageSuffix()}. Per default
69N/A * initialized in the constructor and here to be consistent and avoid lot of
69N/A * unnecessary lookups.
69N/A * @see #startNewLine() */
69N/A protected String userPageSuffix;
69N/A
69N/A /**
69N/A * Description of the style to use for a type of definitions.
69N/A */
69N/A private static class Style {
69N/A /** Name of the style definition as given by CTags. */
115N/A final String name;
69N/A
69N/A /** Class name used by the style sheets when rendering the xref. */
69N/A final String ssClass;
69N/A
69N/A /**
69N/A * The title of the section to which this type belongs, or {@code null}
69N/A * if this type should not be listed in the navigation panel.
69N/A */
69N/A final String title;
69N/A
69N/A /** Construct a style description. */
69N/A Style(String name, String ssClass, String title) {
69N/A this.name = name;
69N/A this.ssClass = ssClass;
69N/A this.title = title;
69N/A }
72N/A }
72N/A
69N/A /**
69N/A * Description of styles to use for different types of definitions.
69N/A */
69N/A private static final Style[] DEFINITION_STYLES = {
69N/A new Style("macro", "xm", "Macro"),
69N/A new Style("argument", "xa", null),
69N/A new Style("local", "xl", null),
69N/A new Style("variable", "xv", "Variable"),
new Style("class", "xc", "Class"),
new Style("package", "xp", "Package"),
new Style("interface", "xi", "Interface"),
new Style("namespace", "xn", "Namespace"),
new Style("enumerator", "xer", null),
new Style("enum", "xe", "Enum"),
new Style("struct", "xs", "Struct"),
new Style("typedefs", "xts", null),
new Style("typedef", "xt", "Typedef"),
new Style("union", "xu", null),
new Style("field", "xfld", null),
new Style("member", "xmb", null),
new Style("function", "xf", "Function"),
new Style("method", "xmt", "Method"),
new Style("subroutine", "xsr", "Subroutine"),
};
/**
* Create a new lexer instance. Initializes {@link #userPageLink},
* {@link #userPageSuffix} using the runtime environment and creates a new
* empty {@link SpanStack}.
* @see RuntimeEnvironment#getUserPage()
* @see RuntimeEnvironment#getUserPageSuffix()
*/
protected JFlexXref() {
try {
// TODO when bug #16053 is fixed, we should add a getter to a file
// that's included from all the Xref classes so that we avoid the
// reflection.
Field f = getClass().getField("YYEOF");
yyeof = f.getInt(null);
userPageLink = RuntimeEnvironment.getInstance().getUserPage();
if (userPageLink != null && userPageLink.length() == 0) {
userPageLink = null;
}
userPageSuffix = RuntimeEnvironment.getInstance().getUserPageSuffix();
if (userPageSuffix != null && userPageSuffix.length() == 0) {
userPageSuffix = null;
}
} catch (Exception e) {
// The auto-generated constructors for the Xref classes don't
// expect a checked exception, so wrap it in an AssertionError.
// This should never happen, since all the Xref classes will get
// a public static YYEOF field from JFlex.
AssertionError ae = new AssertionError("Couldn't initialize yyeof");
ae.initCause(e);
throw ae; // NOPMD (stack trace is preserved by initCause(), but
// PMD thinks it's lost)
}
spans = new SpanStack();
}
/**
* Reinitialize the xref with new contents.
*
* @param contents a char buffer with text to analyze
* @param length the number of characters to use from the char buffer
*/
public void reInit(char[] contents, int length) {
yyreset(new CharArrayReader(contents, 0, length));
annotation = null;
}
public void setDefs(Definitions defs) {
this.defs = defs;
}
protected void appendProject() throws IOException {
if (project != null) {
out.write("&amp;project=");
out.write(project.getDescription());
}
}
protected String getProjectPostfix() {
return project == null ? "" : ("&amp;project=" + project.getDescription());
}
/** Get the next token from the scanner. */
public abstract int yylex() throws IOException;
/** Reset the scanner. */
public abstract void yyreset(Reader reader);
/** Get the value of {@code yyline}. */
protected abstract int getLineNumber();
/** Set the value of {@code yyline}. */
protected abstract void setLineNumber(int x);
/**
* Write xref to the specified {@code Writer}.
*
* @param out xref destination
* @throws IOException on error when writing the xref
*/
public void write(Writer out) throws IOException {
this.out = out;
writeSymbolTable();
setLineNumber(0);
out.write("<div id='lines'\n>");
startNewLine();
while (yylex() != yyeof) { // NOPMD while statement intentionally empty
// nothing to do here, yylex() will do the work
}
finishLine();
out.write("</div\n>");
if (spans.size() != 0) {
logger.info("The SpanStack named spans is not empty! May be "
+ "the Xref lexer is not perfect yet!");
spans.clear();
}
}
/**
* Write a JavaScript function that returns an array with the definitions
* to list in the navigation panel. Each element of the array is itself an
* array containing the name of the definition type, the CSS class name for
* the type, and an array of (symbol, line) pairs for the definitions of
* that type.
*/
private void writeSymbolTable() throws IOException {
if (defs == null) {
// No definitions, no symbol table to write
return;
}
// We want the symbol table to be sorted
Comparator<Tag> cmp = new Comparator<Tag>() {
@Override
public int compare(Tag tag1, Tag tag2) {
// Order by symbol name, and then by line number if multiple
// definitions use the same symbol name
int ret = tag1.symbol.compareTo(tag2.symbol);
if (ret == 0) {
ret = tag1.line - tag2.line;
}
return ret;
}
};
Map<String, SortedSet<Tag>> symbols =
new HashMap<String, SortedSet<Tag>>();
for (Tag tag : defs.getTags()) {
Style style = getStyle(tag.type);
if (style != null && style.title != null) {
SortedSet<Tag> tags = symbols.get(style.name);
if (tags == null) {
tags = new TreeSet<Tag>(cmp);
symbols.put(style.name, tags);
}
tags.add(tag);
}
}
out.append("<script type=\"text/javascript\">/* <![CDATA[ */\n");
out.append("O.symlist = [");
boolean first = true;
for (Style style : DEFINITION_STYLES) {
SortedSet<Tag> tags = symbols.get(style.name);
if (tags != null) {
if (!first) {
out.append(',');
}
out.append("[\"");
out.append(style.title);
out.append("\",\"");
out.append(style.ssClass);
out.append("\",[");
boolean firstTag = true;
for (Tag tag : tags) {
if (!firstTag) {
out.append(',');
}
out.append('[');
out.append(Util.jsStringLiteral(tag.symbol));
out.append(',');
out.append(Integer.toString(tag.line));
out.append(']');
firstTag = false;
}
out.append("]]");
first = false;
}
}
/* no LF intentionally - xml is whitespace aware ... */
out.append("]; /* ]]> */</script>");
}
/**
* Get the style description for a definition type.
*
* @param type the definition type
* @return the style of a definition type, or {@code null} if no style is
* defined for the type
* @see #DEFINITION_STYLES
*/
private static Style getStyle(String type) {
for (Style style : DEFINITION_STYLES) {
if (type.startsWith(style.name)) {
return style;
}
}
return null;
}
/**
* Write out annotation infos for the given line.
*
* @param num linenumber to print
* @throws IOException depends on the destination (<var>out</var>).
*/
private final void writeAnnotationInfos(int num) throws IOException {
String r = annotation.getRevision(num);
boolean enabled = annotation.isEnabled(num);
out.write("<span class='blame'>");
if (enabled) {
out.write("<a class='r' href='");
out.write(Util.URIEncodePath(annotation.getFilename()));
out.write("?a=true&amp;r=");
out.write(Util.URIEncode(r));
String msg = annotation.getDesc(r);
if (msg != null) {
out.write("' title=\"");
out.write(msg);
}
out.write("\">");
}
StringBuilder buf = new StringBuilder();
Util.htmlize(r, buf);
out.write(buf.toString());
buf.setLength(0);
if (enabled) {
out.write("</a>");
}
String a = annotation.getAuthor(num);
if (userPageLink == null) {
out.write("<span class='a'>");
Util.htmlize(a, buf);
out.write(buf.toString());
out.write("</span>");
buf.setLength(0);
} else {
out.write("<a class='a' href='");
out.write(userPageLink);
out.write(Util.URIEncode(a));
if (userPageSuffix != null) {
out.write(userPageSuffix);
}
out.write("'>");
Util.htmlize(a, buf);
out.write(buf.toString());
buf.setLength(0);
out.write("</a>");
}
out.write("</span>");
}
private final void finishLine() throws IOException {
if (spans.size() != 0) {
for (int i=spans.size()-1; i >= 0; i--) {
out.write("</span>");
}
}
out.write("</div\n>");
}
/**
* Terminate the current line and insert preamble for the next line. The
* line count will be incremented.
*
* @throws IOException on error when writing the xref
*/
protected final void startNewLine() throws IOException {
int line = getLineNumber();
if (line != 0) {
finishLine();
}
setLineNumber(++line);
String lineStr = String.valueOf(line);
out.write("<div id='");
out.write(lineStr);
out.write("'><b>");
out.write(lineStr);
out.write("</b>");
if (annotation != null) {
writeAnnotationInfos(line);
}
if (spans.size() != 0) {
for (String cname : spans) {
if (cname.isEmpty()) {
out.write("<span>");
} else {
out.write("<span class='");
out.write(cname);
out.write("'>");
}
}
}
}
/**
* Write a symbol and generate links as appropriate.
*
* @param symbol the symbol to write
* @param keywords a set of keywords recognized by this analyzer (no links
* will be generated if the symbol is a keyword)
* @param line the line number on which the symbol appears
* @throws IOException if an error occurs while writing to the stream
*/
protected void writeSymbol(String symbol, Set<String> keywords, int line)
throws IOException {
String[] strs = new String[1];
strs[0] = "";
if (keywords != null && keywords.contains(symbol)) {
// This is a keyword, so we don't create a link.
out.append("<b>").append(symbol).append("</b>");
} else if (defs != null && defs.hasDefinitionAt(symbol, line, strs)) {
// This is the definition of the symbol.
String type = strs[0];
String style_class = "d";
Style style = getStyle(type);
if (style != null) {
style_class = style.ssClass;
}
// 1) Create an anchor for direct links. (Perhaps we should only
// do this when there's exactly one definition of the symbol in
// this file? Otherwise, we may end up with multiple anchors with
// the same name.)
out.append("<a class=\"");
out.append(style_class);
out.append("\" name=\"");
out.append(symbol);
out.append("\"/>");
// 2) Create a link that searches for all references to this symbol.
out.append("<a href=\"");
out.append(urlPrefix);
out.append("refs=");
out.append(symbol);
appendProject();
out.append("\" class=\"");
out.append(style_class);
out.append("\">");
out.append(symbol);
out.append("</a>");
} else if (defs != null && defs.occurrences(symbol) == 1) {
// This is a reference to a symbol defined exactly once in this file.
String style_class = "d";
// Generate a direct link to the symbol definition.
out.append("<a class=\"");
out.append(style_class);
out.append("\" href=\"#");
out.append(symbol);
out.append("\">");
out.append(symbol);
out.append("</a>");
} else {
// This is a symbol that is not defined in this file, or a symbol
// that is defined more than once in this file. In either case, we
// can't generate a direct link to the definition, so generate a
// link to search for all definitions of that symbol instead.
out.append("<a href=\"");
out.append(urlPrefix);
out.append("defs=");
out.append(symbol);
appendProject();
out.append("\">");
out.append(symbol);
out.append("</a>");
}
}
/**
* Write HTML escape sequence for the specified Unicode character, unless
* it's an ISO control character, in which case it is ignored.
*
* @param c the character to write
* @throws IOException if an error occurs while writing to the stream
*/
protected void writeUnicodeChar(char c) throws IOException {
if (!Character.isISOControl(c)) {
out.append("&#").append(Integer.toString(c)).append(';');
}
}
/**
* Write an e-mail address. The address will be obfuscated if
* {@link RuntimeEnvironment#isObfuscatingEMailAddresses()} returns
* {@code true}.
*
* @param address the address to write
* @throws IOException if an error occurs while writing to the stream
*/
protected void writeEMailAddress(String address) throws IOException {
if (RuntimeEnvironment.getInstance().isObfuscatingEMailAddresses()) {
out.write(address.replace("@", " (at) "));
} else {
out.write(address);
}
}
}