diff --git a/Doc/course.xml b/Doc/course.xml index 91d8cfb6836028fdec9bb64323b700734a29efee..da9f1f15f98aa348119cce0963e8ac6d5a32ab88 100644 --- a/Doc/course.xml +++ b/Doc/course.xml @@ -5996,7 +5996,7 @@ public interface Node { ... }</programlisting> - <para>We take <methodname>getChildNodes()</methodname> as an + <para>We take <methodname>org.w3c.dom.Node.getChildNodes()</methodname> as an example:</para> <figure xml:id="domRetrieveChildren"> @@ -6527,7 +6527,7 @@ public class HtmlTree { <callout arearefs="programlisting_catalog2html_insertproduct" xml:id="programlisting_catalog2html_insertproduct_co"> <para>Calling - <methodname>appendIteprogramlisting_catalog2html_insertproduct_com(...)</methodname> + <methodname>solve.dom.HtmlTree.appendItem(String,String)</methodname> once per product completes the creation of our HTML DOM tree:</para> @@ -7251,7 +7251,7 @@ INSERT INTO Product VALUES('w-124', 110.40);</programlisting> <answer> <para>The additional functionality on top of <xref linkend="xml2xml"/> is represented by a method - <methodname>addPrices</methodname>. This method modifies the + <methodname>dom.xsl.XmlRdbms2Html.addPrices()</methodname>. This method modifies the <acronym xlink:href="http://www.w3.org/DOM">DOM</acronym> input tree prior to applying the XSL. Prices are being inserting based on data received from an RDBMS via @@ -11177,8 +11177,7 @@ PersistenceHandler.username=foo</pre> <para>Notice also the <classname>java.awt.event.WindowAdapter</classname> implementation being executed when closing the application's - main window. The <methodname>windowClosing(WindowEvent - e)</methodname> method disconnects any existing database + main window. The <methodname>java.awt.event.WindowAdapter.windowClosing(java.awt.event.WindowEvent)</methodname> method disconnects any existing database connection thus freeing resources.</para> <programlisting language="java">package sda.jdbc.intro.v1; @@ -12713,8 +12712,8 @@ public class HashProvider { ...}</programlisting> <para>We may test the two class methods - <methodname>getSaltedHash</methodname>(...) and - <methodname>check(...)</methodname> by a separate driver + <methodname>sda.jdbc.intro.auth.HashProvider.getSaltedHash(char[])</methodname>(...) and + <methodname>sda.jdbc.intro.auth.HashProvider.check(char[],String)</methodname> by a separate driver class. Notice the <quote>$</quote> sign <coref linkend="saltPwhashSeparator"/> separating salt and password hash:</para> @@ -16253,7 +16252,7 @@ Exception in thread "main" org.hibernate.ObjectNotFoundException: <co <classname>org.hibernate.ObjectNotFoundException</classname>.</para> <para>The documentation also tells us to use the - corresponding <methodname>get()</methodname> method which + corresponding <methodname>org.hibernate.Session.get(Class,Serializable)</methodname> method which actually returns <code>null</code> in case a primary key value does not exist:</para> @@ -16351,7 +16350,7 @@ uid=wings, Fred Wings</programlisting> xml:id="hqlWithSelect"/>); final Object queryResult <co xml:id="queryResultFromSelect"/>= searchUsers.list();</programlisting> - <para>Use the <methodname>getSimpleName()</methodname> + <para>Use the <methodname>Class.getSimpleName()</methodname> reflection method to iteratively analyze the <code>queryResult</code> <coref linkend="queryResultFromSelect"/> instance's structure. This @@ -16428,7 +16427,7 @@ Found user 'Fred Wings'</programlisting> </section> <section xml:id="mappingSingleClasses"> - <title>Mapping single classes and database tables</title> + <title>Mapping single entities and database tables</title> <section xml:id="transientProperties"> <title>Transient properties</title> @@ -16789,8 +16788,7 @@ public class Project { <qandadiv> <qandaentry> <question> - <para>The setter void <methodname>setId(int - id)</methodname>in + <para>The setter void <methodname annotations="nojavadoc">setId(int)</methodname>in <classname>hibintro.v6.Project</classname> has protected access. Explain this choice.</para> </question> @@ -17754,12 +17752,67 @@ CREATE TABLE BankAccount ( <section xml:id="inheritTablePerConcrete"> <title>Table per concrete class</title> - <para/> + <para>Not covered here.</para> </section> </section> - <section xml:id="mappingRelatedClasses"> - <title>Mapping related classes</title> + <section xml:id="mappingRelatedEntities"> + <title>Mapping related entities</title> + + <section xml:id="primaryKeyRevisit"> + <title>Primary keys revisited</title> + + <para>Following <xref linkend="Bauer05"/> (p.88) we list important + properties of primary keys with respect to <quote>best + practices</quote> on top of their relational counterparts:</para> + + <itemizedlist> + <listitem> + <para>A primary key's values never change </para> + </listitem> + + <listitem> + <para>Primary key values should not have a business + meaning</para> + </listitem> + + <listitem> + <para>Primary keys should be chosen to have proper indexing + support with respect to the database product in question.</para> + </listitem> + </itemizedlist> + + <para>Regarding persistence we have three different concepts + regarding an object's identity:</para> + + <glosslist> + <glossentry> + <glossterm>Java Object identity</glossterm> + + <glossdef> + <para>The operator == checks whether two identifiers point to + the same memory address.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>Java Object equality</glossterm> + + <glossdef> + <para>The + <methodname>Object.equals(Object)</methodname>.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm/> + + <glossdef> + <para/> + </glossdef> + </glossentry> + </glosslist> + </section> <section xml:id="sect_MappingEmbeddedClass"> <title>Mapping a single embedded class</title> diff --git a/ws/Docbook/Dbtools/Testdata/method.xml b/ws/Docbook/Dbtools/Testdata/method.xml new file mode 100644 index 0000000000000000000000000000000000000000..6d65e50d2febe2145c679794c7da2aba3daab5be --- /dev/null +++ b/ws/Docbook/Dbtools/Testdata/method.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<book version="5.0" xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:db="http://docbook.org/ns/docbook"> + <info> + <title/> + + <author> + <personname><firstname/><surname/></personname> + + <affiliation> + <orgname/> + </affiliation> + </author> + + <pubdate/> + </info> + + <chapter xml:id="chap"> + <title>Methods</title> + + <para>Lets assume a static method + <methodname>java.lang.String.equals(java.lang.Object)</methodname> an a + non-static method <methodname>String.indexOf(String,int)</methodname>, + <methodname>String.hashCode()</methodname>.</para> + + <para> <methodname>String.getChars(int,int,char[],int)</methodname> + </para> + </chapter> +</book> diff --git a/ws/Docbook/Dbtools/src/main/java/de/hdm_stuttgart/mi/codeformat/Docbook2docbook.java b/ws/Docbook/Dbtools/src/main/java/de/hdm_stuttgart/mi/codeformat/Docbook2docbook.java index 910a1dd570ae055f1e9a5fccb6af6f1c9951730d..d7b1ae55c01fdb209373d5f2f7d9ebe2ab127087 100644 --- a/ws/Docbook/Dbtools/src/main/java/de/hdm_stuttgart/mi/codeformat/Docbook2docbook.java +++ b/ws/Docbook/Dbtools/src/main/java/de/hdm_stuttgart/mi/codeformat/Docbook2docbook.java @@ -7,6 +7,11 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,331 +42,466 @@ import org.w3c.dom.ls.LSSerializer; * */ public class Docbook2docbook { - + private static final Logger logger = Logger.getLogger(Docbook2docbook.class); - private final static String srcPrefixDir = "Ref/api", - docbookNamespace = "http://docbook.org/ns/docbook"; - - private final XPathFactory xpf = XPathFactory.newInstance(); - - /** Searching a document of type docbook for programlistings with java classes. If the class can be retrieved - * a url reference is being constructed. - * @param args - * @throws ClassCastException - * @throws FileNotFoundException - * @throws ClassNotFoundException - * @throws InstantiationException - * @throws IllegalAccessException - * @throws XPathExpressionException - */ - public static void main(String[] args) throws - ClassCastException, FileNotFoundException, ClassNotFoundException, - InstantiationException, IllegalAccessException, XPathExpressionException { - if (1 != args.length) { - logger.fatal("Usage: Docbook2docbook <xmlfileBasename>"); - } else { - Docbook2docbook d2d = new Docbook2docbook(args[0]); - d2d.process(); - logger.info("Successfully finished conversion"); - } - } - private - final String docbookBasename; - /** - * @param basename the XML document's basename e.g. for a XML instance "somdirectory/mydoc.xml" - * this will be "somdirectory/mydoc" - */ - public Docbook2docbook (final String basename){ - this.docbookBasename = basename; - } - private void process() throws ClassCastException, ClassNotFoundException, - InstantiationException, IllegalAccessException, - XPathExpressionException { - final DOMImplementationRegistry registry = - DOMImplementationRegistry.newInstance(); - - DOMImplementationLS impl = (DOMImplementationLS)registry.getDOMImplementation("LS"); - - LSParser db = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); - final DOMConfiguration config = db.getDomConfig(); - config.setParameter("error-handler", new DomErrorHandler(System.err)); - final String xmlInfileName = docbookBasename + ".xml"; - final Document dbInput = db.parseURI(xmlInfileName); - addClassReferences(dbInput); - - final LSSerializer serial = impl.createLSSerializer(); - serial.getDomConfig().setParameter("format-pretty-print", false); - final LSOutput lsOut = impl.createLSOutput(); - PrintWriter out; - try { - out = new PrintWriter(docbookBasename + ".convert.xml"); - lsOut.setCharacterStream(out); - serial.write(dbInput, lsOut); - out.flush(); - } catch (FileNotFoundException e) { - logger.fatal("Unable to parse xml file " + xmlInfileName); - } - } - - private void addClassReferences(final Document top) throws XPathExpressionException{ - - final XPath searchBlockObjects = xpf.newXPath(); - searchBlockObjects.setNamespaceContext(new MyNamespaceContexts()); - - final NodeList classnames = - (NodeList) searchBlockObjects.evaluate( - "//db:classname[not(contains(@annotations, 'nojavadoc'))]", - top, XPathConstants.NODESET); - for (int i = 0; i < classnames.getLength(); i++){ - processClassName((Element) classnames.item(i)); - } - - final NodeList listings = - (NodeList) searchBlockObjects.evaluate( - "//db:programlisting[not(contains(@annotations, 'nojavadoc'))]", - top, XPathConstants.NODESET); - for (int i = 0; i < listings.getLength(); i++){ - processListing((Element) listings.item(i)); - } - // Not yet ready! -// final NodeList methodNames = -// (NodeList) searchProgramlisting.evaluate( -// "//db:methodname", -// top, XPathConstants.NODESET); -// for (int i = 0; i < methodNames.getLength(); i++){ -// processMethodName((Element) methodNames.item(i)); -// } - } - - private void processClassName(final Element classnameElement) { - final Node firstClassnameNode = classnameElement.getFirstChild(); - if (firstClassnameNode instanceof Text) { - final Text firstClassnameTextNode = (Text) firstClassnameNode; - final String fullyQualifiedClassname = firstClassnameTextNode.getData().trim(); - - final Class<?> cl = testClassAvailability(fullyQualifiedClassname); - if (null == cl) { - logger.warn("Unable to find prefix for '" + fullyQualifiedClassname); - } else { - final String urlPrefix, implementationVendor; - if (fullyQualifiedClassname.startsWith("javax.")) { - urlPrefix="http://docs.oracle.com/javase/7/docs/api"; - implementationVendor = "oracle corporation (fixed by javax.*)"; - } else { - implementationVendor = cl.getPackage().getImplementationVendor(); - if (null == implementationVendor) { - urlPrefix = srcPrefixDir; - } else { - switch(implementationVendor.toLowerCase()) { - case "oracle corporation": - urlPrefix="http://docs.oracle.com/javase/7/docs/api"; - break; - case "jdom.org": - urlPrefix="http://www.jdom.org/docs/apidocs"; - break; - case "hibernate.org": - urlPrefix = "http://docs.jboss.org/hibernate/orm/4.1/javadocs"; - break; - default: - urlPrefix="unknown vendor '" + implementationVendor + "'"; - logger.warn("No vendor found for " + fullyQualifiedClassname); - break; - } - } - final Element link = createLink(classnameElement, cl, cl.getSimpleName()); - link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - urlPrefix + "/" + package2path(cl.getCanonicalName()) + ".html"); - classnameElement.replaceChild(link, firstClassnameTextNode); - } - logger.info("Mapping '" + fullyQualifiedClassname + "' to base URL " + - urlPrefix + ", vendor = " + implementationVendor); - } - } - } - - private void processMethodName(final Element methodNameElement) { - final Node firstMethodNameNode = methodNameElement.getFirstChild(); - if (firstMethodNameNode instanceof Text) { - final Text firstMethodNameTextNode = (Text) firstMethodNameNode; - final String fullyQualifiedMethodName = firstMethodNameTextNode.getData().trim(); -// final Class<?> cl = testClassAvailability(fullyQualifiedClassname); -// if (null != cl) { -// final String urlPrefix; -// final String implementationVendor = cl.getPackage().getImplementationVendor(); -// if (null != implementationVendor && implementationVendor.equals("Oracle Corporation")){ -// urlPrefix="http://docs.oracle.com/javase/7/docs/api"; -// } else { -// urlPrefix = srcPrefixDir; -// } -// final Element link = createLink(classnameElement, cl, cl.getSimpleName()); -// link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", -// urlPrefix + "/" + package2path(cl.getCanonicalName()) + ".html"); -// classnameElement.replaceChild(link, firstClassnameTextNode); -// } - } - } - - private void processListing(final Element plisting) throws XPathExpressionException{ - final Text[] textNodes = findTextNodes(plisting); - - final Pattern searchPackage = Pattern.compile( - "(?s).*package[ \\t]+" + - "([\\w]|\\w[\\w|.]*[\\w])"+ - "[ \t]*;.*"); - - String packageName = null; - for (final Text tNode : textNodes){ - Matcher matchPackage = searchPackage.matcher(tNode.getData()); - if (matchPackage.find()){ - packageName = matchPackage.group(1); - break; - } - } - if (null != packageName){ - final Pattern searchClass = Pattern.compile( - "(?s)(.*[ \\t]+class[ \\t]+)" + - "(\\w+)" + - "(.*)"); - // May use the following regexp excluding non-public classes with group(4): - // "(?s)(.*public)([ \\t]+\\w+)*[ \\t]+(class[ \\t]+)" + - // "(\\w+)" + - // "(.*)"); - for (final Text tNode : textNodes){ - Matcher matchClass = searchClass.matcher(tNode.getData()); - if (matchClass.find()){ - final String className = matchClass.group(2); - - final Class<?> cl = testClassAvailability(packageName + "." + className); - if (null != cl){ - final Text beforeText = createTextNode(plisting, matchClass.group(1)), - afterText = createTextNode(plisting, matchClass.group(3)); - plisting.replaceChild(afterText, tNode); - final Element link = createLink(plisting, cl, className); - link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - srcPrefixDir + "/" + package2path(cl.getCanonicalName()) + ".html"); - plisting.insertBefore(link, afterText); - plisting.insertBefore(beforeText, link); - } - break; - } - } - } - } - private Element createLink(final Element root, final Class<?> cl, final String className) { - final Element link = createElementNode( - root, - "uri", docbookNamespace, - className); - link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - srcPrefixDir + "/" + package2path(cl.getCanonicalName()) + ".html"); - return link; - } - private Class<?> testClassAvailability(final String fullyQualifiedClassName){ - try { - return Class.forName(fullyQualifiedClassName); - } catch (ClassNotFoundException e) { - if (fullyQualifiedClassName.contains(".")) { - logger.warn("Warning: Unable to access class " + fullyQualifiedClassName); - } - return null; - } - } - //Helper functions - - /** - * @param current - * @return - */ - public String findExtraClasspath(final Element current) { - - logger.info("line:" + current.getUserData("lineNumber")); -// final String annotations = current.getAttributeNS(docbookNamespace, "annotations"); - final String annotations = current.getAttribute("annotations"); - if (null != annotations) { - final String ret = annotations.trim(); - if (!ret.equals("")) { - return ret; - } - } - final Node parentNode = current.getParentNode(); - - if (parentNode instanceof Document) { - logger.error("Document root being reached while scanning for classpath related annotation"); - System.exit(1); - } - final Element parent = (Element) parentNode; - return findExtraClasspath(parent); - } - - /** - * @param path - */ - public static void addURL(String path) { - File filePath = new File(path); - try { - addURL(filePath.toURI().toURL()); - } catch (MalformedURLException e) { - e.printStackTrace(); - logger.fatal("Error, could not create URL from '" + path + "'"); - System.exit(1); - } - } - - /** - * @param u - */ - public static void addURL(URL u) { - - URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); - Class<URLClassLoader> sysclass = URLClassLoader.class; - final Class[] parameters = new Class[]{URL.class}; - - try { - Method method = sysclass.getDeclaredMethod("addURL", parameters); - method.setAccessible(true); - method.invoke(sysloader, new Object[]{u}); - } catch (Throwable t) { - t.printStackTrace(); - logger.error("Error, could not add URL to system classloader"); - System.exit(1); - } - } - - private final String package2path(final String packageName){ - String searchDot = "\\."; - Pattern pattern = Pattern.compile(searchDot); - Matcher matcher = pattern.matcher(packageName); - return matcher.replaceAll("/"); - } - private Text createTextNode(final Node node, final String data){ - if (node instanceof Document){ - return ((Document) node).createTextNode(data); - } else{ - return node.getOwnerDocument().createTextNode(data); - } - } - private Element createElementNode(final Node node, - final String type, final String ns, final String data){ - final Element ret; - if (node instanceof Document){ - ret = ((Document) node).createElementNS(ns, type); - } else{ - ret = node.getOwnerDocument().createElementNS(ns, type); - } - final Text t = createTextNode(node, data); - ret.appendChild(t); - return ret; - } - private Text[] findTextNodes(final Element parent) throws XPathExpressionException{ - final XPath searchTextNodes = xpf.newXPath(); - final NodeList textNodes = - (NodeList) searchTextNodes.evaluate( - "text()", - parent, XPathConstants.NODESET); - final Text [] t = new Text[textNodes.getLength()]; - for (int i = 0; i < textNodes.getLength(); i++){ - t[i] = (Text) textNodes.item(i); - } - return t; - } + private final static String srcPrefixDir = "Ref/api", + docbookNamespace = "http://docbook.org/ns/docbook"; + private final static Package[] knownPackages; + private final static Map<String, Package> package2name; + private static final Map<String, Class<?>> BUILT_IN_MAP = + new ConcurrentHashMap<String, Class<?>>(); + + + private final XPathFactory xpf = XPathFactory.newInstance(); + + /** Searching a document of type docbook for programlistings with java classes. If the class can be retrieved + * a url reference is being constructed. + * @param args + * @throws ClassCastException + * @throws FileNotFoundException + * @throws ClassNotFoundException + * @throws InstantiationException + * @throws IllegalAccessException + * @throws XPathExpressionException + */ + + static { + knownPackages = Package.getPackages(); + package2name = new HashMap<String, Package>(); + for (final Package p: Package.getPackages()) { + package2name.put(p.getName(), p); + } + for (Class<?> c : new Class[]{void.class, + boolean.class, byte.class, char.class, short.class, int.class, + boolean[].class, byte[].class, char[].class, short[].class, int[].class, + + float.class, double.class, long.class, + float[].class, double[].class, long[].class + }) + BUILT_IN_MAP.put(c.getCanonicalName(), c); + } + public static Class<?> forName(String name) throws ClassNotFoundException { + Class<?> c = BUILT_IN_MAP.get(name); + if (c == null) { + return Class.forName(name); + } else { + return c; + } + } + public static void main(String[] args) throws + ClassCastException, FileNotFoundException, ClassNotFoundException, + InstantiationException, IllegalAccessException, XPathExpressionException { + if (1 != args.length) { + logger.fatal("Usage: Docbook2docbook <xmlfileBasename>"); + } else { + Docbook2docbook d2d = new Docbook2docbook(args[0]); + d2d.process(); + logger.info("Successfully finished conversion"); + } + } + private + final String docbookBasename; + /** + * @param basename the XML document's basename e.g. for a XML instance "somdirectory/mydoc.xml" + * this will be "somdirectory/mydoc" + */ + public Docbook2docbook (final String basename){ + this.docbookBasename = basename; + } + private void process() throws ClassCastException, ClassNotFoundException, + InstantiationException, IllegalAccessException, + XPathExpressionException { + final DOMImplementationRegistry registry = + DOMImplementationRegistry.newInstance(); + + DOMImplementationLS impl = (DOMImplementationLS)registry.getDOMImplementation("LS"); + + LSParser db = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); + final DOMConfiguration config = db.getDomConfig(); + config.setParameter("error-handler", new DomErrorHandler(System.err)); + final String xmlInfileName = docbookBasename + ".xml"; + final Document dbInput = db.parseURI(xmlInfileName); + addClassReferences(dbInput); + + final LSSerializer serial = impl.createLSSerializer(); + serial.getDomConfig().setParameter("format-pretty-print", false); + final LSOutput lsOut = impl.createLSOutput(); + PrintWriter out; + try { + out = new PrintWriter(docbookBasename + ".convert.xml"); + lsOut.setCharacterStream(out); + serial.write(dbInput, lsOut); + out.flush(); + } catch (FileNotFoundException e) { + logger.fatal("Unable to parse xml file " + xmlInfileName); + } + } + + private void addClassReferences(final Document top) throws XPathExpressionException{ + + final XPath searchBlockObjects = xpf.newXPath(); + searchBlockObjects.setNamespaceContext(new MyNamespaceContexts()); + + final NodeList classnames = + (NodeList) searchBlockObjects.evaluate( + "//db:classname[not(contains(@annotations, 'nojavadoc'))]", + top, XPathConstants.NODESET); + for (int i = 0; i < classnames.getLength(); i++){ + processClassName((Element) classnames.item(i)); + } + + final NodeList listings = + (NodeList) searchBlockObjects.evaluate( + "//db:programlisting[not(contains(@annotations, 'nojavadoc'))]", + top, XPathConstants.NODESET); + for (int i = 0; i < listings.getLength(); i++){ + processListing((Element) listings.item(i)); + } + final NodeList methodNames = + (NodeList) searchBlockObjects.evaluate( + "//db:methodname[not(contains(@annotations, 'nojavadoc'))]", + top, XPathConstants.NODESET); + for (int i = 0; i < methodNames.getLength(); i++){ + processMethodName((Element) methodNames.item(i)); + } + } + + private void processClassName(final Element classnameElement) { + final Node firstClassnameNode = classnameElement.getFirstChild(); + if (firstClassnameNode instanceof Text) { + final Text firstClassnameTextNode = (Text) firstClassnameNode; + final String fullyQualifiedClassname = firstClassnameTextNode.getData().trim(); + + final Class<?> cl = findClass(fullyQualifiedClassname); + if (null == cl) { + logger.warn("Unable to find prefix for '" + fullyQualifiedClassname); + } else { + + final Element link = createLink(classnameElement, cl.getSimpleName(), urlPrefix (cl)); + classnameElement.replaceChild(link, firstClassnameTextNode); + } + } + } + + final String urlPrefix (final Method method) { + final Class<?> cl = method.getDeclaringClass(); + final StringBuffer buff = new StringBuffer(); + buff.append(urlPrefix(cl)); + buff.append('#'); + buff.append(method.getName()); + buff.append('('); + for (int i = 0; i < method.getParameterTypes().length; i++) { + final Class<?> type = method.getParameterTypes()[i]; + buff.append(type.getCanonicalName()); + if (i < method.getParameterTypes().length - 1) { + buff.append(", "); + } + } + buff.append(')'); + return buff.toString(); + } + + final String urlPrefix (final Class<?> cl) { + final String urlPrefix, implementationVendor; + final String fullyQualifiedClassname = cl.getCanonicalName(); + if (fullyQualifiedClassname.startsWith("javax.")) { + urlPrefix="http://docs.oracle.com/javase/7/docs/api"; + implementationVendor = "oracle corporation (fixed by javax.*)"; + } else { + implementationVendor = cl.getPackage().getImplementationVendor(); + if (null == implementationVendor) { + urlPrefix = srcPrefixDir; + } else { + switch(implementationVendor.toLowerCase()) { + case "oracle corporation": + urlPrefix="http://docs.oracle.com/javase/7/docs/api"; + break; + case "jdom.org": + urlPrefix="http://www.jdom.org/docs/apidocs"; + break; + case "hibernate.org": + urlPrefix = "http://docs.jboss.org/hibernate/orm/4.1/javadocs"; + break; + default: + urlPrefix="unknown vendor '" + implementationVendor + "'"; + logger.warn("No vendor found for " + fullyQualifiedClassname); + break; + } + } + } + return urlPrefix + "/" + package2path(cl.getCanonicalName()) + ".html"; + } + + private static String join(final String[] components, final String seperator, int start, int length) { + final StringBuffer b = new StringBuffer(); + for (int i = start; i < start + length; i++) { + b.append(components[i]); + if (i < start + length - 1) { + b.append(seperator); + } + } + return b.toString(); + } + + private static Package package2name(final String[] components, int startIndex, int length) { + for (String c: components) { + logger.info("component:" + c); + } + + final String packageName = join(components, ".", startIndex, length); + logger.info("searching for package '" + packageName + "'"); + return Package.getPackage(packageName); + } + private final Class<?> findUniqueClass(final String classname) { + + try { + return forName(classname); //primitive type? + } catch (ClassNotFoundException ex) {} + + + int packageCount = 0; + Class<?> lastFound = null; + for (final Package p: knownPackages) { + final String fullyQualifiedClassName = p.getName().concat(".").concat(classname); + final Class<?> candidate = findClass(fullyQualifiedClassName); + if (null != candidate) { + lastFound = candidate; + packageCount++; + logger.info("found class '" + lastFound.getCanonicalName()); + } + } + if (0 == packageCount) { + logger.warn("Class '" + classname + "' not found in any package"); + } else if (1 < packageCount) { + logger.warn("Class '" + classname + "' found in " + packageCount + " packages"); + } + return lastFound; + } + private final Class<?> findClass(final String className) { + if (className.contains(".")) { + try { + //logger.info("Looking for class '" + className + "'"); + return forName(className) ; + } catch (ClassNotFoundException ex) { + //logger.info("No such class '" + fullyQualifiedClassName + "'"); + return null; + } + } else { + return findUniqueClass(className); + } + } + private void processMethodName(final Element methodNameElement) { + final Node firstMethodNameNode = methodNameElement.getFirstChild(); + if (firstMethodNameNode instanceof Text) { + final Text firstMethodNameTextNode = (Text) firstMethodNameNode; + final String fullyQualifiedMethodName = firstMethodNameTextNode.getData().trim().replaceAll("[ \t\n]+", " "); + final String mainSignatureComponents[] = fullyQualifiedMethodName.split("[()]"); + logger.info("----------------------------------------------------------"); + final String returnQualifiedMethodname = mainSignatureComponents[0]; + //argumentList = mainSignatureComponents[1]; + logger.info("return plus methodname:'" + returnQualifiedMethodname + "'"); + final String[] methodComponents = returnQualifiedMethodname.split("\\."); + + final Class<?> uniqueClass; + final String errMsg; + final String methodName; + if (2 == methodComponents.length) { //Unqualified Class, e.g. String.clone() + final String unqualifiedClassname = methodComponents [0]; + methodName = methodComponents [1]; + uniqueClass = findUniqueClass(unqualifiedClassname); + errMsg = "No unique lookup of class '" + unqualifiedClassname + "'"; + } else { + final Package pack = package2name(methodComponents, 0, methodComponents.length - 2); + methodName = methodComponents [methodComponents.length - 1]; + final String fullyQualifiedClassName = pack.getName().concat(".").concat(methodComponents[methodComponents.length - 2]); + + uniqueClass = findClass(fullyQualifiedClassName); + errMsg = "Unable to find class '" + fullyQualifiedClassName + "'"; + } + if (null == uniqueClass) { + logger.fatal(errMsg); + System.exit(1); + } + logger.info("found class '" + uniqueClass.getCanonicalName() + "'"); + + final List<Class<?>> parameterTypes = new ArrayList<Class<?>>(); + if (1 < mainSignatureComponents.length) { // Argument list may be void + final String[] argumentStrings = mainSignatureComponents[1].split("[,]"); + for (final String a: argumentStrings) { + final Class<?> argType = findClass(a); + if (null == argType) { + logger.fatal("No such type '" + a + "' in method '" + + methodName + "'"); + System.exit(1); + } + parameterTypes.add(argType); + } + } + Method method = null; + try { + method = uniqueClass.getDeclaredMethod(methodName, parameterTypes.toArray(new Class<?>[0])); + } catch (NoSuchMethodException ex) { + logger.error("Unable to find method '" + uniqueClass.getCanonicalName() + "." + + methodName + "'"); + System.exit(1); + } + logger.info("Adding Link '" + urlPrefix(method) + "'"); + + if (method.isAccessible()) { + final Element link = createElementNode(methodNameElement, "link", docbookNamespace, method.getName()); + link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", urlPrefix(method)); + methodNameElement.replaceChild(link, firstMethodNameTextNode); + } + } + } + + private void processListing(final Element plisting) throws XPathExpressionException{ + final Text[] textNodes = findTextNodes(plisting); + + final Pattern searchPackage = Pattern.compile( + "(?s).*package[ \\t]+" + + "([\\w]|\\w[\\w|.]*[\\w])"+ + "[ \t]*;.*"); + + String packageName = null; + for (final Text tNode : textNodes){ + Matcher matchPackage = searchPackage.matcher(tNode.getData()); + if (matchPackage.find()){ + packageName = matchPackage.group(1); + break; + } + } + if (null != packageName){ + final Pattern searchClass = Pattern.compile( + "(?s)(.*[ \\t]+class[ \\t]+)" + + "(\\w+)" + + "(.*)"); + // May use the following regexp excluding non-public classes with group(4): + // "(?s)(.*public)([ \\t]+\\w+)*[ \\t]+(class[ \\t]+)" + + // "(\\w+)" + + // "(.*)"); + for (final Text tNode : textNodes){ + Matcher matchClass = searchClass.matcher(tNode.getData()); + if (matchClass.find()){ + final String className = matchClass.group(2); + + final Class<?> cl = findClass(packageName + "." + className); + if (null != cl){ + final Text beforeText = createTextNode(plisting, matchClass.group(1)), + afterText = createTextNode(plisting, matchClass.group(3)); + plisting.replaceChild(afterText, tNode); + final Element link = createLink(plisting, className, srcPrefixDir + "/" + package2path(cl.getCanonicalName()) + ".html"); + plisting.insertBefore(link, afterText); + plisting.insertBefore(beforeText, link); + } + break; + } + } + } + } + + private Element createLink(final Element root, final String content, final String href) { + final Element link = createElementNode(root, "link", docbookNamespace, content); + link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", href); + return link; + } + + //Helper functions + + /** + * @param current + * @return + */ + public String findExtraClasspath(final Element current) { + + logger.info("line:" + current.getUserData("lineNumber")); + // final String annotations = current.getAttributeNS(docbookNamespace, "annotations"); + final String annotations = current.getAttribute("annotations"); + if (null != annotations) { + final String ret = annotations.trim(); + if (!ret.equals("")) { + return ret; + } + } + final Node parentNode = current.getParentNode(); + + if (parentNode instanceof Document) { + logger.error("Document root being reached while scanning for classpath related annotation"); + System.exit(1); + } + final Element parent = (Element) parentNode; + return findExtraClasspath(parent); + } + + /** + * @param path + */ + public static void addURL(String path) { + File filePath = new File(path); + try { + addURL(filePath.toURI().toURL()); + } catch (MalformedURLException e) { + e.printStackTrace(); + logger.fatal("Error, could not create URL from '" + path + "'"); + System.exit(1); + } + } + + /** + * @param u + */ + public static void addURL(URL u) { + + URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class<URLClassLoader> sysclass = URLClassLoader.class; + final Class[] parameters = new Class[]{URL.class}; + + try { + Method method = sysclass.getDeclaredMethod("addURL", parameters); + method.setAccessible(true); + method.invoke(sysloader, new Object[]{u}); + } catch (Throwable t) { + t.printStackTrace(); + logger.error("Error, could not add URL to system classloader"); + System.exit(1); + } + } + + private final String package2path(final String packageName){ + String searchDot = "\\."; + Pattern pattern = Pattern.compile(searchDot); + Matcher matcher = pattern.matcher(packageName); + return matcher.replaceAll("/"); + } + private Text createTextNode(final Node node, final String data){ + if (node instanceof Document){ + return ((Document) node).createTextNode(data); + } else{ + return node.getOwnerDocument().createTextNode(data); + } + } + private Element createElementNode(final Node node, + final String type, final String ns, final String data){ + final Element ret; + if (node instanceof Document){ + ret = ((Document) node).createElementNS(ns, type); + } else{ + ret = node.getOwnerDocument().createElementNS(ns, type); + } + final Text t = createTextNode(node, data); + ret.appendChild(t); + return ret; + } + private Text[] findTextNodes(final Element parent) throws XPathExpressionException{ + final XPath searchTextNodes = xpf.newXPath(); + final NodeList textNodes = + (NodeList) searchTextNodes.evaluate( + "text()", + parent, XPathConstants.NODESET); + final Text [] t = new Text[textNodes.getLength()]; + for (int i = 0; i < textNodes.getLength(); i++){ + t[i] = (Text) textNodes.item(i); + } + return t; + } }