All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.ceylondoc.Util Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * 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("");
                }
            }
            
            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