/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.javadoc; import gw.lang.javadoc.IClassDocNode; import gw.lang.javadoc.IConstructorNode; import gw.lang.javadoc.IMethodNode; import gw.lang.javadoc.IVarNode; import gw.lang.javadoc.IParamNode; import gw.lang.PublishInGosu; import gw.lang.reflect.java.IJavaClassInfo; import gw.lang.reflect.java.IJavaClassConstructor; import gw.lang.reflect.java.IJavaMethodDescriptor; import gw.lang.reflect.java.IJavaClassType; import gw.lang.reflect.java.IJavaClassTypeVariable; import gw.lang.reflect.java.IJavaClassGenericArrayType; import gw.lang.reflect.java.IJavaClassWildcardType; import gw.lang.reflect.java.IJavaClassParameterizedType; import gw.util.concurrent.LocklessLazyVar; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.XMLConstants; import java.io.InputStream; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.LinkedList; import java.util.ArrayList; import java.util.Collections; /** */ class ClassDocNode extends BaseFeatureNode implements IClassDocNode, IDocNodeWithDescription { private static final String DOC_EXTENSION = ".gti"; private static final QName QNAME_CLASS = new QName( XMLConstants.NULL_NS_URI, "class" ); private static final QName QNAME_DESC = new QName( XMLConstants.NULL_NS_URI, "desc" ); private static final QName QNAME_METHOD = new QName( XMLConstants.NULL_NS_URI, "method" ); private static final QName QNAME_VAR = new QName( XMLConstants.NULL_NS_URI, "var" ); private static final QName QNAME_RETURN = new QName( XMLConstants.NULL_NS_URI, "return" ); private static final QName QNAME_PARAM = new QName( XMLConstants.NULL_NS_URI, "param" ); private static final QName QNAME_EX = new QName( XMLConstants.NULL_NS_URI, "ex" ); private static final QName QNAME_CONS = new QName( XMLConstants.NULL_NS_URI, "cons" ); private static final QName QNAME_DEPRECATED = new QName( XMLConstants.NULL_NS_URI, "deprecated" ); private static final Map<QName,NodeHandler> _nodeHandlers = new HashMap<QName, NodeHandler>(); private List<IMethodNode> _methods = Collections.emptyList(); private List<IConstructorNode> _constructors = Collections.emptyList(); private List<IVarNode> _vars = Collections.emptyList(); private static final LocklessLazyVar<XMLInputFactory> XML_INPUT_FACTORY_FACTORY = new LocklessLazyVar<XMLInputFactory>() { @Override protected XMLInputFactory init() { return XMLInputFactory.newInstance(); } }; static { _nodeHandlers.put( QNAME_CLASS, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) { if ( parent != null ) { throw new RuntimeException( "Expected null parent" ); } return new ClassDocNode(); } } ); _nodeHandlers.put( QNAME_DESC, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { ( (IDocNodeWithDescription) parent ).setDescription( parser.getElementText() ); return null; // shouldn't have any children } } ); _nodeHandlers.put( QNAME_METHOD, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { MethodNode methodNode = new MethodNode(); final String methodName = parser.getAttributeValue( null, "name" ); if ( methodName == null ) { throw new RuntimeException( "method's name is null" ); } methodNode.setName( methodName ); ( (ClassDocNode) parent ).addMethod( methodNode ); return methodNode; } } ); _nodeHandlers.put( QNAME_VAR, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { VarNode varNode = new VarNode(); final String varName = parser.getAttributeValue( null, "name" ); if ( varName == null ) { throw new RuntimeException( "var's name is null" ); } varNode.setName( varName ); ( (ClassDocNode) parent ).addVar( varNode ); return varNode; } } ); _nodeHandlers.put( QNAME_CONS, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { ConstructorNode constructorNode = new ConstructorNode(); ( (ClassDocNode) parent ).addConstructor( constructorNode ); return constructorNode; } } ); _nodeHandlers.put( QNAME_RETURN, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { ( (MethodNode) parent ).setReturnDescription( parser.getElementText() ); return null; // shouldn't have any children } } ); _nodeHandlers.put( QNAME_PARAM, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { ParamNode paramNode = new ParamNode(); paramNode.setName( parser.getAttributeValue( null, "name" ) ); paramNode.setType( parser.getAttributeValue( null, "type" ) ); paramNode.setDescription( parser.getElementText() ); ( (IDocNodeWithParams) parent ).addParam( paramNode ); return null; // shouldn't have any children } } ); _nodeHandlers.put( QNAME_EX, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { ExceptionNode exNode = new ExceptionNode(); exNode.setType( parser.getAttributeValue( null, "type" ) ); exNode.setDescription( parser.getElementText() ); ( (IDocNodeWithParams) parent ).addException( exNode ); return null; // shouldn't have any children } } ); _nodeHandlers.put( QNAME_DEPRECATED, new NodeHandler() { @Override public Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException { ( (BaseFeatureNode) parent ).setDeprecated( parser.getElementText() ); return null; // shouldn't have any children } } ); } @Override public List<IMethodNode> getMethods() { return _methods; } @Override public void addMethod( IMethodNode method ) { if ( _methods == Collections.EMPTY_LIST ) { _methods = new ArrayList<IMethodNode>( 1 ); } _methods.add( method ); } @Override public IMethodNode getMethod(IJavaMethodDescriptor name) { List<IMethodNode> methods = getMethods(); for (IMethodNode method : methods) { if (method.getName().equals(name.getName()) && compareParams(name.getMethod().getGenericParameterTypes(), method.getParams())) { return method; } } return null; } @Override public IMethodNode getMethod(String name, Type[] parameterTypes) { List<IMethodNode> methods = getMethods(); for (IMethodNode method : methods) { if (method.getName().equals(name) && compareParams(parameterTypes, method.getParams())) { return method; } } return null; } @Override public List<IConstructorNode> getConstructors() { return _constructors; } @Override public void addConstructor( IConstructorNode constructor ) { if ( _constructors == Collections.EMPTY_LIST ) { _constructors = new ArrayList<IConstructorNode>( 1 ); } _constructors.add( constructor ); } @Override public List<IVarNode> getVars() { return _vars; } @Override public void addVar( IVarNode var ) { if ( _vars == Collections.EMPTY_LIST ) { _vars = new ArrayList<IVarNode>( 1 ); } _vars.add( var ); } @Override public IVarNode getVar( String name ) { List<IVarNode> vars = getVars(); for ( IVarNode var : vars ) { final String varName = var.getName(); // extract variable to debug TH-only NPE if ( varName.equals( name ) ) { return var; } } return null; } public static ClassDocNode get(Class<?> type) { if( !type.getName().startsWith( "gw.lang" ) && !type.isAnnotationPresent( PublishInGosu.class ) ) { return null; } String resourceName = type.getName().replaceAll("\\.", "/") + DOC_EXTENSION; if (type.getClassLoader() == null) { return null; } InputStream stream = type.getClassLoader().getResourceAsStream( resourceName ); if ( stream == null ) { return null; } try { XMLStreamReader parser = XML_INPUT_FACTORY_FACTORY.get().createXMLStreamReader( stream ); Object classNode = null; // will end up being last END_ELEMENT we receive LinkedList<Object> nodeStack = new LinkedList<Object>(); Object parent = null; while ( parser.hasNext() ) { int event = parser.next(); switch ( event ) { case XMLStreamConstants.START_ELEMENT: { QName elementName = parser.getName(); NodeHandler nodeHandler = _nodeHandlers.get( elementName ); Object newParent = nodeHandler.handle( parent, parser ); if ( newParent != null ) { nodeStack.add( parent ); parent = newParent; } break; } case XMLStreamConstants.END_ELEMENT: { classNode = parent; parent = nodeStack.removeLast(); break; } } } parser.close(); return (ClassDocNode) classNode; } catch ( Exception ex ) { throw new RuntimeException( "Could not parse " + resourceName, ex ); } } @Override public IConstructorNode getConstructor(IJavaClassConstructor ctor) { for (IConstructorNode constructorNode : getConstructors()) { if (compareParams(ctor.getParameterTypes(), constructorNode.getParams())) { return constructorNode; } } return null; } private boolean compareParams(Type[] parameterTypes, List<IParamNode> params) { if (parameterTypes.length != params.size()) { return false; } for (int i = 0; i < parameterTypes.length; i++) { Type parameterType = parameterTypes[i]; String parameterTypeName = getParameterTypeName(parameterType); if (!areTypeNamesEqual(parameterTypeName, params.get(i).getType())) { return false; } } return true; } private boolean compareParams(IJavaClassType[] parameterTypes, List<IParamNode> params) { if (parameterTypes.length != params.size()) { return false; } for (int i = 0; i < parameterTypes.length; i++) { IJavaClassType parameterType = parameterTypes[i]; String parameterTypeName = getParameterTypeName(parameterType); if (!areTypeNamesEqual(parameterTypeName, params.get(i).getType())) { return false; } } return true; } // Some versions of qdox use $ for inner class names, some use . instead. I'd // rather we just didn't care about the difference private boolean areTypeNamesEqual(String name1, String name2) { return name1.replace('$', '.').equals(name2.replace('$', '.')); } private String getParameterTypeName(Type parameterType) { String parameterTypeName; if (parameterType instanceof Class) { Class parameterT = (Class) parameterType; parameterTypeName = parameterT.getName(); } else if (parameterType instanceof TypeVariable) { TypeVariable parameterT = (TypeVariable) parameterType; parameterTypeName = parameterT.getName(); } else if (parameterType instanceof ParameterizedType) { ParameterizedType parameterT = (ParameterizedType) parameterType; parameterTypeName = getParameterTypeName(parameterT.getRawType()); } else if (parameterType instanceof WildcardType) { assert false : "Do not expect WilcardType as a parameter type"; parameterTypeName = null; } else if (parameterType instanceof GenericArrayType) { GenericArrayType parameterT = (GenericArrayType) parameterType; parameterTypeName = getParameterTypeName(parameterT.getGenericComponentType()); } else { throw new RuntimeException("Don't know the type of " + parameterType); } return parameterTypeName; } private String getParameterTypeName(IJavaClassType parameterType) { String parameterTypeName; if (parameterType instanceof IJavaClassInfo) { parameterTypeName = parameterType.getName(); } else if (parameterType instanceof IJavaClassTypeVariable) { parameterTypeName = parameterType.getName(); } else if (parameterType instanceof IJavaClassParameterizedType) { IJavaClassParameterizedType parameterT = (IJavaClassParameterizedType) parameterType; parameterTypeName = getParameterTypeName(parameterT.getConcreteType()); } else if (parameterType instanceof IJavaClassWildcardType) { assert false : "Do not expect WilcardType as a parameter type"; parameterTypeName = null; } else if (parameterType instanceof IJavaClassGenericArrayType) { IJavaClassGenericArrayType parameterT = (IJavaClassGenericArrayType) parameterType; parameterTypeName = getParameterTypeName(parameterT.getGenericComponentType()); } else { throw new RuntimeException("Don't know the type of " + parameterType); } return parameterTypeName; } private interface NodeHandler { Object handle( Object parent, XMLStreamReader parser ) throws XMLStreamException; } }