Package com.redhat.ceylon.compiler.typechecker.analyzer

Source Code of com.redhat.ceylon.compiler.typechecker.analyzer.TypeHierarchyVisitor

package com.redhat.ceylon.compiler.typechecker.analyzer;

import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.message;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isAbstraction;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isOverloadedVersion;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isResolvable;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isTypeUnknown;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Functional;
import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ParameterList;
import com.redhat.ceylon.compiler.typechecker.model.ProducedReference;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.Value;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;

/**
* Checks associated to the traversal of the hierarchy:
* - detect circularity in inheritance
* - All formal must be implemented as actual by concrete classes
* - may not inherit two declarations with the same name that do not share a common supertype
* - may not inherit two declarations with the same name unless redefined in subclass
*
* @author Emmanuel Bernard <emmanuel@hibernate.org>
*/
public class TypeHierarchyVisitor extends Visitor {

    private final Map<TypeDeclaration,Type> types = new HashMap<TypeDeclaration, Type>();

    private static final class Type {
        public Map<String,Members> membersByName = new HashMap<String, Members>();
        public TypeDeclaration declaration;
        public static final class Members {
            public String name;
            public Set<Declaration> formals = new LinkedHashSet<Declaration>();
            //public Set<Declaration> concretesOnInterfaces = new LinkedHashSet<Declaration>();
            public Set<Declaration> actuals = new HashSet<Declaration>();
            public Set<Declaration> actualsNonFormals = new HashSet<Declaration>();
            public Set<Declaration> defaults = new HashSet<Declaration>();
            public Set<Declaration> nonFormalsNonDefaults = new HashSet<Declaration>();
      public Set<Declaration> shared = new HashSet<Declaration>();
        }

        @Override
        public String toString() {
            return declaration.getName();
        }
    }
   
    @Override
    public void visit(Tree.ObjectDefinition that) {
        Value value = that.getDeclarationModel();
        Class anonymousClass =
                (Class) value.getType().getDeclaration();
        validateMemberRefinement(that, anonymousClass);
        super.visit(that);
        //an object definition is always concrete
        List<Type> orderedTypes =
                sortDAGAndBuildMetadata(value.getTypeDeclaration(), that);
        checkForFormalsNotImplemented(that,
                orderedTypes, anonymousClass);
        checkForDoubleMemberInheritanceNotOverridden(that,
                orderedTypes, anonymousClass);
        checkForDoubleMemberInheritanceWoCommonAncestor(that,
                orderedTypes, anonymousClass);
    }

    @Override
    public void visit(Tree.ObjectArgument that) {
        Value value = that.getDeclarationModel();
        Class anonymousClass =
                (Class) value.getType().getDeclaration();
        validateMemberRefinement(that, anonymousClass);
        super.visit(that);
        //an object definition is always concrete
        List<Type> orderedTypes =
                sortDAGAndBuildMetadata(value.getTypeDeclaration(), that);
        checkForFormalsNotImplemented(that,
                orderedTypes, anonymousClass);
        checkForDoubleMemberInheritanceNotOverridden(that,
                orderedTypes, anonymousClass);
        checkForDoubleMemberInheritanceWoCommonAncestor(that,
                orderedTypes, anonymousClass);
    }

    @Override
    public void visit(Tree.ClassOrInterface that) {
        ClassOrInterface classOrInterface = that.getDeclarationModel();
        validateMemberRefinement(that, classOrInterface);
        super.visit(that);
        if (!classOrInterface.isAlias()) {
            boolean concrete =
                    !classOrInterface.isAbstract() &&
                    !classOrInterface.isFormal();
            List<Type> orderedTypes =
                    sortDAGAndBuildMetadata(classOrInterface, that);
            if (concrete) {
                checkForFormalsNotImplemented(that,
                        orderedTypes, (Class) classOrInterface);
            }
            checkForDoubleMemberInheritanceNotOverridden(that,
                    orderedTypes, classOrInterface);
            checkForDoubleMemberInheritanceWoCommonAncestor(that,
                    orderedTypes, classOrInterface);
        }
    }

    @Override
    public void visit(Tree.TypeConstraint that) {
        super.visit(that);
        sortDAGAndBuildMetadata(that.getDeclarationModel(), that);
    }
   
    @Override
    public void visit(Tree.Declaration that) {
        super.visit(that);
        Declaration dec = that.getDeclarationModel();
        if (dec.isClassOrInterfaceMember() && dec.isActual()) {
            checkForShortcutRefinement(that, dec);
        }
    }

    @Override
    public void visit(Tree.BaseMemberExpression that) {
        super.visit(that);
        Declaration dec = that.getDeclaration();
        if (dec instanceof MethodOrValue &&
                ((MethodOrValue) dec).isShortcutRefinement()) {
            checkForShortcutRefinement(that, dec);
        }
    }

    private static void checkForShortcutRefinement(Node that, Declaration dec) {
        ClassOrInterface classOrInterface =
                (ClassOrInterface) dec.getContainer();
        for (Declaration im:
                classOrInterface.getInheritedMembers(dec.getName())) {
            if (im instanceof MethodOrValue &&
                    ((MethodOrValue) im).isShortcutRefinement()) {
                that.addError("refines a non-formal, non-default member: " +
                        message(im));
            }
        }
    }

    private void checkForDoubleMemberInheritanceWoCommonAncestor(Tree.StatementOrArgument that,
            List<Type> orderedTypes, ClassOrInterface classOrInterface) {
        Type aggregateType = new Type();
        for(Type currentType: orderedTypes) {
            for (Type.Members currentTypeMembers:
                    currentType.membersByName.values()) {
                String name = currentTypeMembers.name;
                Type.Members aggregateMembers =
                        aggregateType.membersByName.get(name);
                if (aggregateMembers==null) {
                    //not accumulated yet, no need to check
                    aggregateMembers = new Type.Members();
                    aggregateMembers.name = name;
                    aggregateType.membersByName.put(name,aggregateMembers);
                }
                else {
                    boolean superMemberIsShared =
                            !aggregateMembers.shared.isEmpty();
                    boolean currentMemberIsShared =
                            !currentTypeMembers.shared.isEmpty();
                    if (superMemberIsShared && currentMemberIsShared) {
                        boolean isMemberNameOnAncestor =
                                isMemberNameOnAncestor(currentType, name);
                        if (!isMemberNameOnAncestor) {
                            TypeDeclaration otherType =
                                    getTypeDeclarationFor(aggregateMembers);
                          if (!mixedInBySupertype(currentType.declaration,
                                  otherType, classOrInterface)) {
                            StringBuilder sb = new StringBuilder("may not inherit two declarations with the same name that do not share a common supertype: '");
                                sb.append(name)
                                  .append("' is defined by supertypes '")
                              .append(currentType.declaration.getName())
                              .append("' and '")
                              .append(otherType.getName())
                              .append("'");
                            that.addError(sb.toString());
                          }
                        }
                    }
                }
                pourCurrentTypeInfoIntoAggregatedType(currentTypeMembers,
                        aggregateMembers);
            }
        }
    }

    private void checkForDoubleMemberInheritanceNotOverridden(Tree.StatementOrArgument that,
            List<Type> orderedTypes, ClassOrInterface classOrInterface) {
        Type aggregateType = new Type();
        int size = orderedTypes.size();
        for(int index = size-1; index>=0; index--) {
            Type currentType = orderedTypes.get(index);
            for (Type.Members currentTypeMembers:
                    currentType.membersByName.values()) {
                String name = currentTypeMembers.name;
                Type.Members aggregateMembers =
                        aggregateType.membersByName.get(name);
                if (aggregateMembers==null) {
                    //not accumulated yet, no need to check
                    aggregateMembers = new Type.Members();
                    aggregateMembers.name = name;
                    aggregateType.membersByName.put(name,aggregateMembers);
                }
                else {
                    boolean subtypeMemberIsShared =
                            !aggregateMembers.shared.isEmpty();
                    boolean currentMemberIsShared =
                            !currentTypeMembers.shared.isEmpty();
                    if (subtypeMemberIsShared && currentMemberIsShared) {
                        boolean isMemberRefined =
                                isMemberRefined(orderedTypes,index,name,currentTypeMembers);
                        boolean isMemberNameOnAncestor =
                                isMemberNameOnAncestor(currentType, name);
                        if (!isMemberRefined && isMemberNameOnAncestor) {
                            TypeDeclaration otherType =
                                    getTypeDeclarationFor(aggregateMembers);
                          if (!mixedInBySupertype(currentType.declaration,
                                  otherType, classOrInterface)) {
                            StringBuilder sb = new StringBuilder("may not inherit two declarations with the same name unless redefined in subclass: '");
                                sb.append(name)
                                  .append("' is defined by supertypes '")
                              .append(currentType.declaration.getName())
                              .append("' and '")
                              .append(otherType.getName())
                              .append("'");
                            that.addError(sb.toString());
                          }
                        }
                    }
                }
                pourCurrentTypeInfoIntoAggregatedType(currentTypeMembers,
                        aggregateMembers);
            }
        }
    }

    private void pourCurrentTypeInfoIntoAggregatedType(Type.Members currentTypeMembers,
            Type.Members aggregateMembers) {
        aggregateMembers.nonFormalsNonDefaults.addAll(currentTypeMembers.nonFormalsNonDefaults);
        aggregateMembers.actuals.addAll(currentTypeMembers.actuals);
        aggregateMembers.formals.addAll(currentTypeMembers.formals);
        //aggregateMembers.concretesOnInterfaces.addAll(currentTypeMembers.concretesOnInterfaces);
        aggregateMembers.actualsNonFormals.addAll(currentTypeMembers.actualsNonFormals);
        aggregateMembers.defaults.addAll(currentTypeMembers.defaults);
        aggregateMembers.shared.addAll(currentTypeMembers.shared);
    }

    private boolean isMemberNameOnAncestor(Type currentType, String name) {
        //retrieve inherited members (shared)
        return !currentType.declaration.getInheritedMembers(name).isEmpty();
    }

    private boolean mixedInBySupertype(TypeDeclaration currentType,
            TypeDeclaration otherType, ClassOrInterface classOrInterface) {
        TypeDeclaration et = classOrInterface.getExtendedTypeDeclaration();
        if (et!=null &&
                et.inherits(currentType) &&
                et.inherits(otherType)) {
            return true;
        }
        for (TypeDeclaration st:
                classOrInterface.getSatisfiedTypeDeclarations()) {
            if (st!=null &&
                    st.inherits(currentType) &&
                    st.inherits(otherType)) {
                return true;
            }
        }
        return false;
    }

    private boolean isMemberRefined(List<Type> orderedTypes, int index,
            String name, Type.Members currentTypeMembers) {
        int size = orderedTypes.size();
        Declaration declarationOfSupertypeMember =
                getMemberDeclaration(currentTypeMembers);
        for (int subIndex = size-1 ; subIndex>index;subIndex--) {
            Type type = orderedTypes.get(subIndex);
            //has a direct member and supertype as inherited members
            Declaration directMember =
                    type.declaration.getDirectMember(name, null, false);
            boolean isMemberRefined =
                    directMember!=null && directMember.isShared(); //&& !(directMember instanceof Parameter);
            isMemberRefined = isMemberRefined &&
                    type.declaration.getInheritedMembers(name)
                        .contains(declarationOfSupertypeMember);
            if (isMemberRefined) {
                return true;
            }
        }
        return false;
    }

    private TypeDeclaration getTypeDeclarationFor(Type.Members aggregateMembers) {
        Declaration memberDeclaration =
                getMemberDeclaration(aggregateMembers);
        return memberDeclaration == null ? null :
            memberDeclaration.getDeclaringType(memberDeclaration).getDeclaration();
    }

    private Declaration getMemberDeclaration(Type.Members aggregateMembers) {
        if (!aggregateMembers.formals.isEmpty()) {
            return aggregateMembers.formals.iterator().next();
        }
        if (!aggregateMembers.defaults.isEmpty()) {
            return aggregateMembers.defaults.iterator().next();
        }
        if (!aggregateMembers.actuals.isEmpty()) {
            return aggregateMembers.actuals.iterator().next();
        }
        if (!aggregateMembers.nonFormalsNonDefaults.isEmpty()) {
            return aggregateMembers.nonFormalsNonDefaults.iterator().next();
        }
        return null;
    }

    private void checkForFormalsNotImplemented(Tree.StatementOrArgument that,
            List<Type> orderedTypes, Class clazz) {
        Type aggregation = buildAggregatedType(orderedTypes);
        for (Type.Members members: aggregation.membersByName.values()) {
            if (!members.formals.isEmpty()) {
                if (members.actualsNonFormals.isEmpty()) {
                    Declaration example = members.formals.iterator().next();
                    Declaration declaringType = (Declaration) example.getContainer();
                    if (!clazz.equals(declaringType)) {
                        addUnimplementedFormal(clazz, example);
                        that.addError("formal member '" + example.getName() +
                                "' of '" + declaringType.getName() +
                                "' not implemented in class hierarchy", 300);
                        continue;
                    }
                }
                for (Declaration f: members.formals) {
                    if (isOverloadedVersion(f)) {
                        boolean found = false;
                        for (Declaration a: members.actualsNonFormals) {
                            if (a.getRefinedDeclaration().equals(f.getRefinedDeclaration())) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            StringBuilder paramTypes = new StringBuilder();
                            List<ParameterList> parameterLists =
                                    ((Functional)f).getParameterLists();
                            if (!parameterLists.isEmpty()) {
                                for (Parameter p: parameterLists.get(0).getParameters()) {
                                    if (paramTypes.length()>0) {
                                        paramTypes.append(", ");
                                    }
                                    if (!isTypeUnknown(p.getType())) {
                                        paramTypes.append(p.getType().getProducedTypeName());
                                    }
                                }
                            }
                            Declaration declaringType = (Declaration) f.getContainer();
                            addUnimplementedFormal(clazz, f);
                            that.addError("overloaded formal member '" + f.getName() +
                                    "(" + paramTypes + ")' of '" + declaringType.getName() +
                                    "' not implemented in class hierarchy"/*, 300*/);
                        }
                    }
                }
            }
            /*if (!members.concretesOnInterfaces.isEmpty() && members.actualsNonFormals.isEmpty()) {
                Declaration declaringType = (Declaration) members.concretesOnInterfaces.iterator().next().getContainer();
                that.addWarning("interface member " + members.name +
                    " of " + declaringType.getName() +
                        " not implemented in class hierarchy (concrete interface members not yet supported)");
            }*/
        }
    }

    private void addUnimplementedFormal(Class clazz, Declaration member) {
        ProducedReference unimplemented =
                member.getProducedReference(clazz.getType(),
                        Collections.<ProducedType>emptyList());
        List<ProducedReference> list =
                clazz.getUnimplementedFormals();
        if (list.isEmpty()) {
            list = new ArrayList<ProducedReference>();
            clazz.setUnimplementedFormals(list);
        }
        list.add(unimplemented);
    }

    //accumulate all members of a type hierarchy
    private Type buildAggregatedType(List<Type> orderedTypes) {
        int size = orderedTypes.size();
        Type aggregation = new Type();
        for (int index = size-1; index>=0; index--) {
            Type current = orderedTypes.get(index);
            for (Type.Members currentMembers:
                    current.membersByName.values()) {
                Type.Members aggregateMembers =
                        aggregation.membersByName.get(currentMembers.name);
                if (aggregateMembers==null) {
                    aggregateMembers = new Type.Members();
                    aggregateMembers.name = currentMembers.name;
                    aggregation.membersByName.put(currentMembers.name,aggregateMembers);
                }
                pourCurrentTypeInfoIntoAggregatedType(currentMembers, aggregateMembers);
                //if an actual implementation high in the hierarchy is overridden as formal => do not add
                //remember we go from most specific to most generic type
                for (Declaration actualNonFormal:
                        currentMembers.actualsNonFormals) {
                    for (Declaration formal: aggregateMembers.formals) {
                        if (formal.getName().equals(actualNonFormal.getName())) {
                            if (!formal.getUnit().getPackage().getModule().isJava() ||
                                    !formal.isInterfaceMember()) {
                                aggregateMembers.actualsNonFormals.remove(actualNonFormal);
                                break;
                            }
                        }
                    }
                }
            }
        }
        return aggregation;
    }

    //sort type hierarchy from most abstract to most concrete
    private List<Type> sortDAGAndBuildMetadata(TypeDeclaration declaration,
            Node errorReporter) {
        //Apply a partial sort on the class hierarchy which is a Directed Acyclic Graph (DAG)
        // with subclasses pointing to superclasses or interfaces
        //use depth-first plus a stack fo processed nodes to detect non DAG
        //http://en.wikipedia.org/wiki/Topological_sorting
        List<Type> sortedDag = new ArrayList<Type>();
        List<TypeDeclaration> visitedDeclarationPerBranch =
                new ArrayList<TypeDeclaration>();
        Set<TypeDeclaration> visited = new HashSet<TypeDeclaration>();
        visitDAGNode(declaration, sortedDag, visited,
                visitedDeclarationPerBranch, errorReporter);
        return sortedDag;
    }

    private void visitDAGNode(TypeDeclaration declaration,
            List<Type> sortedDag, Set<TypeDeclaration> visited,
            List<TypeDeclaration> stackOfProcessedType,
            Node errorReporter) {
        if (declaration == null) {
            return;
        }
        if (checkCyclicInheritance(declaration,
                stackOfProcessedType, errorReporter)) {
            return; //stop the cycle here but try and process the rest
        }

        if ( visited.contains(declaration) ) {
            return;
        }
        visited.add(declaration);
        Type type = getOrBuildType(declaration);

        stackOfProcessedType.add(declaration);
        visitDAGNode(declaration.getExtendedTypeDeclaration(),
                sortedDag, visited, stackOfProcessedType,
                errorReporter);
        for (TypeDeclaration superSatisfiedType:
                declaration.getSatisfiedTypeDeclarations()) {
            visitDAGNode(superSatisfiedType, sortedDag, visited,
                    stackOfProcessedType, errorReporter);
        }
        for (ProducedType superSatisfiedType:
                declaration.getBrokenSupertypes()) {
            visitDAGNode(superSatisfiedType.getDeclaration(),
                    sortedDag, visited, stackOfProcessedType,
                    errorReporter);
        }
        stackOfProcessedType.remove(stackOfProcessedType.size()-1);
        sortedDag.add(type);
    }

    private boolean checkCyclicInheritance(TypeDeclaration declaration,
            List<TypeDeclaration> stackOfProcessedType, Node errorReporter) {
        final int matchingIndex = stackOfProcessedType.indexOf(declaration);
        if (matchingIndex!=-1) {
            /*StringBuilder sb = new StringBuilder("cyclical inheritance in ");
            sb.append(declaration.getName());
            sb.append(" (involving ");
            for (int index = stackOfProcessedType.size()-1;index>matchingIndex;index--) {
                sb.append(stackOfProcessedType.get(index).getName()).append(", ");
            }
            removeTrailing(", ", sb);
            sb.append(")");
            errorReporter.addError(sb.toString());
            return true;*/
        }
        return false;
    }

    /*private void removeTrailing(String trailingString, StringBuilder sb) {
        final int length = sb.length();
        sb.delete(length-trailingString.length(), length);
    }*/

    private Type getOrBuildType(TypeDeclaration declaration) {
        Type type = types.get(declaration);
        if (type == null) {
            type = new Type();
            type.declaration = declaration;
            for (Declaration member: declaration.getMembers()) {
                if (!(member instanceof MethodOrValue ||
                      member instanceof Class) ||
                        member.isStaticallyImportable() ||
                        isAbstraction(member)) {
                    continue;
                }
                final String name = member.getName();
                Type.Members members = type.membersByName.get(name);
                if (members==null) {
                    members = new Type.Members();
                    members.name = name;
                    type.membersByName.put(name,members);
                }
                if (member.isActual()) {
                    members.actuals.add(member);
                    if (!member.isFormal()) {
                        members.actualsNonFormals.add(member);
                    }
                }
                if (member.isFormal()) {
                    members.formals.add(member);
                }
                /*if (!member.isFormal() && member.isInterfaceMember()) {
                  members.concretesOnInterfaces.add(member);
                }*/
                if (member.isDefault()) {
                    members.defaults.add(member);
                }
                if (!member.isFormal() && !member.isDefault()) {
                    members.nonFormalsNonDefaults.add(member);
                }
                if (member.isShared()) {
                    members.shared.add(member);
                }
            }
            types.put(declaration,type);
        }
        return type;
    }
   
    private void validateMemberRefinement(Tree.StatementOrArgument that,
            TypeDeclaration td) {
        if (!td.isInconsistentType()) {
            Set<String> errors = new HashSet<String>();
            for (TypeDeclaration std: td.getSupertypeDeclarations()) {
                if (td instanceof ClassOrInterface &&
                        !((ClassOrInterface) td).isAbstract() &&
                        !((ClassOrInterface) td).isAlias()) {
                    for (Declaration d: std.getMembers()) {
                        if (d.isShared() &&
                                !isOverloadedVersion(d) &&
                                isResolvable(d) &&
                                !errors.contains(d.getName())) {
                            Declaration r = td.getMember(d.getName(), null, false);
                            if (r==null || !r.refines(d) &&
                                    //squash bogus error when there is a dupe declaration
                                    !r.getContainer().equals(td)) {
                                //TODO: This seems to dupe some checks that are already
                                //      done in TypeHierarchyVisitor, resulting in
                                //      multiple errors
                                //TODO: figure out which other declaration causes the
                                //      problem and display it to the user!
                                if (r==null) {
                                    that.addError("member '" + d.getName() +
                                            "' is inherited ambiguously by '" + td.getName() +
                                            "' from '" + std.getName()
                                            "' and another unrelated supertype");
                                }
                                else {
                                    //TODO: I'm not really certain that the following
                                    //      condition is correct, we really should
                                    //      check that the other declaration is a Java
                                    //      interface member (see the TODO above)
                                    if (!(d.getUnit().getPackage().getModule().isJava() &&
                                            r.getUnit().getPackage().getModule().isJava() &&
                                            r.isInterfaceMember() &&
                                            d.isClassMember())) {
                                        that.addError("member '" + d.getName() +
                                                "' is inherited ambiguously by '" + td.getName() +
                                                "' from '" + std.getName()
                                                "' and another subtype of '" +
                                                ((TypeDeclaration) r.getContainer()).getName() +
                                                "' and so must be refined by '" + td.getName() + "'",
                                                350);
                                    }
                                }
                                errors.add(d.getName());
                            }
                            /*else if (!r.getContainer().equals(td)) { //the case where the member is actually declared by the current type is handled by checkRefinedTypeAndParameterTypes()
                                //TODO: I think this case never occurs, because getMember() always
                                //      returns null in the case of an ambiguity
                                List<ProducedType> typeArgs = new ArrayList<ProducedType>();
                                if (d instanceof Generic) {
                                    for (TypeParameter refinedTypeParam: ((Generic) d).getTypeParameters()) {
                                        typeArgs.add(refinedTypeParam.getType());
                                    }
                                }
                                ProducedType t = td.getType().getTypedReference(r, typeArgs).getType();
                                ProducedType it = st.getTypedReference(d, typeArgs).getType();
                                checkAssignable(t, it, that, "type of member " + d.getName() +
                                        " must be assignable to all types inherited from instantiations of " +
                                        st.getDeclaration().getName());
                            }*/
                        }
                    }
                }
            }
        }
    }

}
TOP

Related Classes of com.redhat.ceylon.compiler.typechecker.analyzer.TypeHierarchyVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.