com.redhat.ceylon.ceylondoc.Util Maven / Gradle / Ivy
/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.ceylondoc;
import java.io.IOException;
import java.io.StringReader;
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Pattern;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
import javax.swing.text.html.parser.DTD;
import javax.swing.text.html.parser.DocumentParser;
import javax.swing.text.html.parser.ParserDelegator;
import com.github.rjeschke.txtmark.BlockEmitter;
import com.github.rjeschke.txtmark.Configuration;
import com.github.rjeschke.txtmark.Processor;
import com.github.rjeschke.txtmark.SpanEmitter;
import com.redhat.ceylon.compiler.java.codegen.Decl;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.model.typechecker.model.Annotated;
import com.redhat.ceylon.model.typechecker.model.Annotation;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Import;
import com.redhat.ceylon.model.typechecker.model.ModelUtil;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.ModuleImport;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.Referenceable;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.Unit;
import com.redhat.ceylon.model.typechecker.model.Value;
public class Util {
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("(?: |\\u00A0|\\s|[\\s&&[^ ]])\\s*");
private static final Set ABBREVIATED_TYPES = new HashSet();
static {
ABBREVIATED_TYPES.add("ceylon.language::Empty");
ABBREVIATED_TYPES.add("ceylon.language::Entry");
ABBREVIATED_TYPES.add("ceylon.language::Sequence");
ABBREVIATED_TYPES.add("ceylon.language::Sequential");
ABBREVIATED_TYPES.add("ceylon.language::Iterable");
}
public static String normalizeSpaces(String str) {
if (str == null) {
return null;
}
return WHITESPACE_PATTERN.matcher(str).replaceAll(" ");
}
public static boolean isAbbreviatedType(Declaration decl) {
return ABBREVIATED_TYPES.contains(decl.getQualifiedNameString());
}
public static String join(String separator, List parts) {
StringBuilder stringBuilder = new StringBuilder();
Iterator iterator = parts.iterator();
while(iterator.hasNext()){
stringBuilder.append(iterator.next());
if(iterator.hasNext())
stringBuilder.append(separator);
}
return stringBuilder.toString();
}
private static final int FIRST_LINE_MAX_SIZE = 120;
public static String getDoc(Declaration decl, LinkRenderer linkRenderer) {
return wikiToHTML(getRawDoc(decl), linkRenderer.useScope(decl));
}
public static String getDoc(Module module, LinkRenderer linkRenderer) {
return wikiToHTML(getRawDoc(module.getUnit(), module.getAnnotations()), linkRenderer.useScope(module));
}
public static String getDoc(ModuleImport moduleImport, LinkRenderer linkRenderer) {
return wikiToHTML(getRawDoc(moduleImport.getModule().getUnit(), moduleImport.getAnnotations()), linkRenderer);
}
public static String getDoc(Package pkg, LinkRenderer linkRenderer) {
return wikiToHTML(getRawDoc(pkg.getUnit(), pkg.getAnnotations()), linkRenderer.useScope(pkg));
}
public static String getDocFirstLine(Declaration decl, LinkRenderer linkRenderer) {
return getDocFirstLine(getRawDoc(decl), linkRenderer.useScope(decl));
}
public static String getDocFirstLine(Package pkg, LinkRenderer linkRenderer) {
return getDocFirstLine(getRawDoc(pkg.getUnit(), pkg.getAnnotations()), linkRenderer.useScope(pkg));
}
public static String getDocFirstLine(Module module, LinkRenderer linkRenderer) {
return getDocFirstLine(getRawDoc(module.getUnit(), module.getAnnotations()), linkRenderer.useScope(module));
}
public static String getDocFirstLine(String text, LinkRenderer linkRenderer) {
String html = wikiToHTML(text, linkRenderer);
FirstLineParser parser = new FirstLineParser();
return parser.parseFirstLine(html);
}
private static class FirstLineParser extends DocumentParser {
private boolean done = false;
private boolean dots = false;
private int lastPosition = 0;
private List impliedTags = new ArrayList();
private List openedTags = new ArrayList();
private StringBuilder textBuilder = new StringBuilder();
private class FirstLineParserCallback extends ParserCallback {
@Override
public void handleStartTag(Tag t, MutableAttributeSet a, int pos) {
if( done ) {
return;
}
if (a.isDefined(IMPLIED)) {
impliedTags.add(t);
return;
}
if( t.isBlock() && textBuilder.length() > 0 ) {
done = true;
return;
}
openedTags.add(t);
lastPosition = getCurrentPos();
}
@Override
public void handleEndTag(Tag t, int pos) {
if( done ) {
return;
}
if( impliedTags.contains(t) ) {
impliedTags.remove(t);
return;
}
int lastIndexOf = openedTags.lastIndexOf(t);
if( lastIndexOf != -1 ) {
openedTags.remove(lastIndexOf);
}
lastPosition = getCurrentPos();
}
@Override
public void handleSimpleTag(Tag t, MutableAttributeSet a, int pos) {
if( done ) {
return;
}
lastPosition = getCurrentPos();
}
@Override
public void handleText(char[] data, int pos) {
if( done ) {
return;
}
textBuilder.append(data);
String text = textBuilder.toString().replaceAll("\\s*$", "");
String firstLine = trimFirstLine(text);
if( !text.equals(firstLine) ) {
done = true;
lastPosition += data.length - (text.length() - firstLine.length());
return;
}
lastPosition = getCurrentPos();
}
private String trimFirstLine(String text) {
// be lenient for Package and Module
if(text == null)
return "";
// First try to get the first sentence
BreakIterator breaker = BreakIterator.getSentenceInstance();
breaker.setText(text);
breaker.first();
int dot = breaker.next();
// First sentence is sufficiently short
if (dot != BreakIterator.DONE
&& dot <= FIRST_LINE_MAX_SIZE) {
return text.substring(0, dot).replaceAll("\\s*$", "");
}
if (text.length() <= FIRST_LINE_MAX_SIZE) {
return text;
}
// First sentence is really long, to try to break on a word
breaker = BreakIterator.getWordInstance();
breaker.setText(text);
int pos = breaker.first();
while (pos < FIRST_LINE_MAX_SIZE
&& pos != BreakIterator.DONE) {
pos = breaker.next();
}
if (pos != BreakIterator.DONE
&& breaker.previous() != BreakIterator.DONE) {
dots = true;
return text.substring(0, breaker.current()).replaceAll("\\s*$", "");
}
dots = true;
return text.substring(0, FIRST_LINE_MAX_SIZE-1);
}
};
private static DTD getDTD() {
try {
new ParserDelegator(); // initialize DTD
return DTD.getDTD("html32");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private FirstLineParser() {
super(getDTD());
}
private String parseFirstLine(String html) {
try {
parse(new StringReader(html), new FirstLineParserCallback(), true);
} catch (IOException e) {
throw new RuntimeException(e);
}
StringBuilder result = new StringBuilder();
result.append(html.substring(0, lastPosition).replaceAll("\\s*$", ""));
if( dots ) {
result.append("…");
}
if( !openedTags.isEmpty() ) {
Collections.reverse(openedTags);
for(Tag t : openedTags) {
result.append("").append(t).append(">");
}
}
return result.toString();
}
}
public static List getTags(T decl) {
List tags = new ArrayList();
Annotation tagged = Util.getAnnotation(decl.getUnit(), decl.getAnnotations(), "tagged");
if (tagged != null) {
tags.addAll(tagged.getPositionalArguments());
}
return tags;
}
public static String wikiToHTML(String text, LinkRenderer linkRenderer) {
if( text == null || text.length() == 0 ) {
return text;
}
Configuration config = Configuration.builder()
.forceExtentedProfile()
.setCodeBlockEmitter(CeylondocBlockEmitter.INSTANCE)
.setSpecialLinkEmitter(new CeylondocSpanEmitter(linkRenderer))
.build();
return Processor.process(text, config);
}
private static String getRawDoc(Declaration decl) {
Annotation a = findAnnotation(decl, "doc");
if (a != null) {
return a.getPositionalArguments().get(0);
}
return "";
}
public static String getRawDoc(Unit unit, List anns) {
Annotation a = getAnnotation(unit, anns, "doc");
if (a != null && a.getPositionalArguments() != null && !a.getPositionalArguments().isEmpty()) {
return a.getPositionalArguments().get(0);
}
return "";
}
public static Annotation getAnnotation(ModuleImport moduleImport, String name) {
return getAnnotation(moduleImport.getModule().getUnit(), moduleImport.getAnnotations(), name);
}
public static Annotation getAnnotation(Unit unit, List annotations, String name) {
String aliasedName = resolveAliasedName(unit, name);
// check that documentation annotation is not hidden by custom annotation
if( name.equals(aliasedName) && unit != null ) {
Declaration importedDeclaration = unit.getImportedDeclaration(name, null, false);
if( importedDeclaration != null && !importedDeclaration.getNameAsString().startsWith("ceylon.language::") ) {
return null;
}
}
if (annotations != null) {
for (Annotation a : annotations) {
if (a.getName().equals(aliasedName))
return a;
}
}
return null;
}
public static Annotation findAnnotation(Declaration decl, String name) {
Annotation a = getAnnotation(decl.getUnit(), decl.getAnnotations(), name);
if (a == null && decl.isActual() && decl.getRefinedDeclaration() != decl) {
// keep looking up
a = findAnnotation(decl.getRefinedDeclaration(), name);
}
return a;
}
private static String resolveAliasedName(Unit unit, String name) {
if (unit != null) {
for (Import i : unit.getImports()) {
if (!i.isAmbiguous() && i.getDeclaration().getQualifiedNameString().equals("ceylon.language::" + name)) {
return i.getAlias();
}
}
}
return name;
}
public static String capitalize(String text) {
char[] buffer = text.toCharArray();
boolean capitalizeNext = true;
for (int i = 0; i < buffer.length; i++) {
char ch = buffer[i];
if (Character.isWhitespace(ch)) {
capitalizeNext = true;
} else if (capitalizeNext) {
buffer[i] = Character.toTitleCase(ch);
capitalizeNext = false;
}
}
return new String(buffer);
}
public static String getModifiers(Declaration d) {
StringBuilder modifiers = new StringBuilder();
if (d.isShared()) {
modifiers.append("shared ");
}
if (d.isFormal()) {
modifiers.append("formal ");
} else {
if (d.isActual()) {
modifiers.append("actual ");
}
if (d.isDefault()) {
modifiers.append("default ");
}
}
if (Decl.isValue(d)) {
Value v = (Value) d;
if (v.isVariable()) {
modifiers.append("variable ");
}
} else if (d instanceof Class) {
Class c = (Class) d;
if (c.isAbstract()) {
modifiers.append("abstract ");
}
if (c.isFinal() && !c.isAnonymous()) {
modifiers.append("final ");
}
}
return modifiers.toString().trim();
}
public static List getAncestors(TypeDeclaration decl) {
List ancestors = new ArrayList();
Type ancestor = decl.getExtendedType();
while (ancestor != null) {
ancestors.add(ancestor.getDeclaration());
ancestor = ancestor.getExtendedType();
}
return ancestors;
}
public static List getSuperInterfaces(TypeDeclaration decl) {
Set superInterfaces = new HashSet();
for (Type satisfiedType : decl.getSatisfiedTypes()) {
superInterfaces.add(satisfiedType.getDeclaration());
superInterfaces.addAll(getSuperInterfaces(satisfiedType.getDeclaration()));
}
List list = new ArrayList();
list.addAll(superInterfaces);
removeDuplicates(list);
return list;
}
private static void removeDuplicates(List superInterfaces) {
OUTER: for (int i = 0; i < superInterfaces.size(); i++) {
TypeDeclaration decl1 = superInterfaces.get(i);
// compare it with each type after it
for (int j = i + 1; j < superInterfaces.size(); j++) {
TypeDeclaration decl2 = superInterfaces.get(j);
if (decl1.equals(decl2)) {
if (decl1.getType().isSubtypeOf(decl2.getType())) {
// we keep the first one because it is more specific
superInterfaces.remove(j);
} else {
// we keep the second one because it is more specific
superInterfaces.remove(i);
// since we removed the first type we need to stay at
// the same index
i--;
}
// go to next type
continue OUTER;
}
}
}
}
public static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
public static boolean isEmpty(Collection> c) {
return c == null || c.isEmpty();
}
public static boolean isThrowable(TypeDeclaration c) {
if (c instanceof Class) {
if ("ceylon.language::Throwable".equals(c.getQualifiedNameString())) {
return true;
} else if (c.getExtendedType()!=null) {
return isThrowable(c.getExtendedType().getDeclaration());
}
}
return false;
}
public static String getUnitPackageName(PhasedUnit unit) {
// WARNING: TypeChecker VFS alyways uses '/' chars and not platform-dependent ones
String path = unit.getPathRelativeToSrcDir();
String file = unit.getUnitFile().getName();
if(!path.endsWith(file)){
throw new RuntimeException("Unit relative path does not end with unit file name: "+path+" and "+file);
}
path = path.substring(0, path.length() - file.length());
if(path.endsWith("/"))
path = path.substring(0, path.length() - 1);
return path.replace('/', '.');
}
public static String getQuotedFQN(String pkgName, com.redhat.ceylon.compiler.typechecker.tree.Tree.Declaration decl) {
String name = decl.getIdentifier().getText();
// no need to quote the name itself as java keywords are lower-cased and we append a _ to every
// lower-case toplevel so they can never be java keywords
return pkgName.isEmpty() ? name : com.redhat.ceylon.compiler.java.util.Util.quoteJavaKeywords(pkgName) + "." + name;
}
public static Declaration findBottomMostRefinedDeclaration(TypedDeclaration d) {
if (d.getContainer() instanceof TypeDeclaration) {
Queue queue = new LinkedList();
queue.add((TypeDeclaration) d.getContainer());
return findBottomMostRefinedDeclaration(d, queue);
}
return null;
}
private static Declaration findBottomMostRefinedDeclaration(TypedDeclaration d, Queue queue) {
TypeDeclaration type = queue.poll();
if (type != null) {
if (type != d.getContainer()) {
Declaration member = type.getDirectMember(d.getName(), null, false);
if (member != null && member.isActual()) {
return member;
}
}
if (type.getExtendedType() != null) {
queue.add(type.getExtendedType().getDeclaration());
}
for (Type satisfiedType: type.getSatisfiedTypes()) {
queue.add(satisfiedType.getDeclaration());
}
return findBottomMostRefinedDeclaration(d, queue);
}
return null;
}
public static String getNameWithContainer(Declaration d) {
return "" +
((TypeDeclaration)d.getContainer()).getName() +
"." + d.getName() + "
";
}
private static class CeylondocBlockEmitter implements BlockEmitter {
private static final CeylondocBlockEmitter INSTANCE = new CeylondocBlockEmitter();
@Override
public void emitBlock(StringBuilder out, List lines, String meta) {
if (lines.isEmpty())
return;
if( meta == null || meta.length() == 0 ) {
out.append("");
}
else {
out.append("");
}
for (final String s : lines) {
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
switch (c) {
case '&':
out.append("&");
break;
case '<':
out.append("<");
break;
case '>':
out.append(">");
break;
default:
out.append(c);
break;
}
}
out.append('\n');
}
out.append("
\n");
}
}
private static class CeylondocSpanEmitter implements SpanEmitter {
private final LinkRenderer linkRenderer;
public CeylondocSpanEmitter(LinkRenderer linkRenderer) {
this.linkRenderer = linkRenderer;
}
@Override
public void emitSpan(StringBuilder out, String content) {
int pipeIndex = content.indexOf("|");
String customText = pipeIndex != -1 ? content.substring(0, pipeIndex) : null;
String link = new LinkRenderer(linkRenderer).
to(content).
withinText(true).
useCustomText(customText).
printTypeParameters(false).
printWikiStyleLinks(true).
getLink();
out.append(link);
}
}
public static class ReferenceableComparatorByName implements Comparator {
public static final ReferenceableComparatorByName INSTANCE = new ReferenceableComparatorByName();
@Override
public int compare(Referenceable a, Referenceable b) {
return nullSafeCompare(a.getNameAsString(), b.getNameAsString());
}
};
public static class TypeComparatorByName implements Comparator {
public static final TypeComparatorByName INSTANCE = new TypeComparatorByName();
@Override
public int compare(Type a, Type b) {
return nullSafeCompare(a.getDeclaration().getName(), b.getDeclaration().getName());
}
};
public static class ModuleImportComparatorByName implements Comparator {
public static final ModuleImportComparatorByName INSTANCE = new ModuleImportComparatorByName();
@Override
public int compare(ModuleImport a, ModuleImport b) {
return nullSafeCompare(a.getModule().getNameAsString(), b.getModule().getNameAsString());
}
}
static int nullSafeCompare(final String s1, final String s2) {
if (s1 == s2) {
return 0;
} else if (s1 == null) {
return -1;
} else if (s2 == null) {
return 1;
}
return s1.compareTo(s2);
}
public static boolean isEnumerated(TypeDeclaration klass) {
return klass.getCaseTypes() != null && !klass.getCaseTypes().isEmpty();
}
public static String getDeclarationName(Declaration decl) {
String name = decl.getName();
if( ModelUtil.isConstructor(decl) && name == null ) {
name = ((TypeDeclaration)decl.getContainer()).getName();
}
return name;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy