[Mulgara-svn] r721 - trunk/src/jar/util/java/org/mulgara/util

pag at mulgara.org pag at mulgara.org
Fri Mar 28 21:32:31 UTC 2008


Author: pag
Date: 2008-03-28 14:32:30 -0700 (Fri, 28 Mar 2008)
New Revision: 721

Added:
   trunk/src/jar/util/java/org/mulgara/util/ClassDescriber.java
   trunk/src/jar/util/java/org/mulgara/util/ClassDescriberXML.java
Log:
An interface and one implementation for creating string descriptions of classes.

Added: trunk/src/jar/util/java/org/mulgara/util/ClassDescriber.java
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/ClassDescriber.java	                        (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/ClassDescriber.java	2008-03-28 21:32:30 UTC (rev 721)
@@ -0,0 +1,51 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+package org.mulgara.util;
+
+/**
+ * This class takes a {@link java.lang.Class} or an instance of a class, and describes it
+ * in a format that is serialized as a string.
+ *
+ * @created Mar 28, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2007 <a href="mailto:pgearon at users.sourceforge.net">Paul Gearon</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public interface ClassDescriber {
+
+  /**
+   * Gets a description of the current class. This is equivalent to calling getDescription(0)
+   * @return A string encoding the description.
+   */
+  public String getDescription();
+
+  /**
+   * Gets a description of the current class, increasing any indenting of the string format if
+   * an indented format is used.
+   * @param indentLevel The number of indents to add to the formatting of the string. Ignored
+   *        if the output string is not in an indented format.
+   * @return A string encoding the description, with indenting increased by indentLevel if relevant.
+   */
+  public String getDescription(int indentLevel);
+
+  /**
+   * Gets the class that is described by this object.
+   * @return The class that this object is describing.
+   */
+  public Class<?> getDescribedClass();
+
+  /**
+   * Sets the size of indenting in the output, if this is relevant to the describer.
+   * @param spaces The size of the indent, in number of spaces.
+   */
+  public void setSpacesPerIndent(int spaces);
+}
\ No newline at end of file

Added: trunk/src/jar/util/java/org/mulgara/util/ClassDescriberXML.java
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/ClassDescriberXML.java	                        (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/ClassDescriberXML.java	2008-03-28 21:32:30 UTC (rev 721)
@@ -0,0 +1,386 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.util;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Field;
+import java.lang.reflect.Constructor;
+
+/**
+ * This class takes a {@link java.lang.Class} or an instance of a class, and describes it
+ * in XML, with pretty-print indenting.
+ *
+ * @created Mar 28, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2007 <a href="mailto:pgearon at users.sourceforge.net">Paul Gearon</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class ClassDescriberXML implements ClassDescriber {
+
+  /** The default number of spaces for each new level of indenting. */
+  private static final int DEFAULT_INDENT_SPACES = 2;
+
+  /** The number of spaces to use for each new level of indenting. */
+  private int indentSpaces = DEFAULT_INDENT_SPACES;
+
+  /** The class to be described. */
+  private Class<?> cls;
+
+  /** A string of spaces to use for indenting. The number of spaces should be {@link #indentLevel}*{@link #indentSpaces}. */
+  private String indent;
+
+  /**
+   * Holds the level of indenting to use at a particular moment during XML generation.
+   * Each change only increments or decrements by 1.
+   */
+  private int indentLevel;
+
+  /** A string builder to accumulate the XML as it gets built. */
+  private StringBuilder buffer;
+
+  /**
+   * Creates a describer for the class of an object instance.
+   * @param obj The object instance to get the class description for.
+   */
+  public ClassDescriberXML(Object obj) {
+    this(obj.getClass());
+  }
+
+  /**
+   * Creates a describer for a given class.
+   * @param cls The class tobe described.
+   */
+  public ClassDescriberXML(Class<?> cls) {
+    this.cls = cls;
+  }
+
+  /** @see org.mulgara.util.ClassDescriber#getDescribedClass() */
+  public Class<?> getDescribedClass() {
+    return cls;
+  }
+
+  /**
+   * @see org.mulgara.util.ClassDescriber#getDescription()
+   * @return a String containing XML describing the provided class.
+   */
+  public String getDescription() {
+    setIndent(0);
+    buffer = new StringBuilder();
+    describeClass();
+    return buffer.toString();
+  }
+
+  /**
+   * @see org.mulgara.util.ClassDescriber#getDescription(int)
+   * @param indentLevel The level of indenting to start with.
+   * @return a String containing XML describing the provided class.
+   */
+  public String getDescription(int indentLevel) {
+    setIndent(indentLevel);
+    buffer = new StringBuilder();
+    describeClass();
+    return buffer.toString();
+  }
+
+  /** @see org.mulgara.util.ClassDescriber#setSpacesPerIndent(int) */
+  public void setSpacesPerIndent(int spaces) {
+    indentSpaces = spaces;
+  }
+
+  /**
+   * This method manages the work of describing a class. It calls the desciption methods for
+   * all the parts of a class in order, and wraps the result in a &lt;class&gt; element. The
+   * entire description is appended to {@link #buffer}.
+   * @return The {@link #buffer} containing a full description of the configured class.
+   */
+  private StringBuilder describeClass() {
+    buffer.append(indent).append("<class name=\"").append(cls.getName()).append(">\n");
+    pushIndent();
+    describeSuperClass();
+    describeInterfaces();
+    describeAnnotations();
+    describeFields();
+    describeConstructors();
+    describeMethods();
+    popIndent();
+    buffer.append(indent).append("</class>\n");
+    return buffer;
+  }
+
+  /**
+   * Appends the superclass description of the configured class to {@link #buffer}.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder describeSuperClass() {
+    Class<?> s = cls.getSuperclass();
+    if (s != null) {
+      buffer.append(indent).append("<superclass name=\"").append(s.getName()).append("\"/>\n");
+    }
+    return buffer;
+  }
+
+  /**
+   * Appends the interface descriptions of the current class to {@link #buffer}.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder describeInterfaces() {
+    Class<?>[] classes = cls.getInterfaces();
+    if (classes.length != 0) {
+      buffer.append(indent).append("<interfaces>\n");
+      for (Class<?> c: classes) {
+        buffer.append(indent).append("  <interface name=\"").append(c.getName()).append("\"/>\n");
+      }
+      buffer.append(indent).append("</interfaces>\n");
+    }
+    return buffer;
+  }
+
+  /**
+   * Appends the annotation descriptions of the current class to {@link #buffer}.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder describeAnnotations() {
+    Annotation[] annotations = cls.getAnnotations();
+    addAnnotations(annotations);
+    return buffer;
+  }
+
+  /**
+   * Appends the full field descriptions of the current class to {@link #buffer}.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder describeFields() {
+    Field[] fields = cls.getFields();
+    if (fields.length != 0) {
+      buffer.append(indent).append("<fields>\n");
+      pushIndent();
+      for (Field f: fields) {
+        buffer.append(indent).append("<field name=\"").append(f.getName());
+        buffer.append("\" type=\"").append(f.getType().getName());
+        Annotation[] annotations = f.getDeclaredAnnotations();
+        if (annotations.length == 0) buffer.append("\"/>\n");
+        else {
+          buffer.append("\">\n");
+          pushIndent();
+          addAnnotations(annotations);
+          popIndent();
+          buffer.append(indent).append("</field>\n");
+        }
+      }
+      popIndent();
+      buffer.append(indent).append("</fields>\n");
+    }
+    return buffer;
+  }
+
+  /**
+   * Appends the constructor descriptions of the current class to {@link #buffer}.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder describeConstructors() {
+    Constructor<?>[] constructors = cls.getConstructors();
+    if (constructors.length != 0) {
+      buffer.append(indent).append("<constructors>\n");
+      pushIndent();
+      for (Constructor<?> c: constructors) {
+        buffer.append(indent).append("<constructor");
+        if (c.isVarArgs()) buffer.append(" varargs=\"true\"");
+        if (c.isSynthetic()) buffer.append(" synthethic=\"true\"");
+
+        Class<?>[] exceptions = c.getExceptionTypes();
+        Class<?>[] params = c.getParameterTypes();
+        Annotation[] annotations = c.getDeclaredAnnotations();
+
+        if (params.length == 0 && exceptions.length == 0 && annotations.length == 0) buffer.append("/>\n");
+        else {
+          buffer.append(">\n");
+          pushIndent();
+          addExceptions(exceptions);
+          addAnnotations(annotations);
+          addParameters(params, c.getParameterAnnotations());
+          popIndent();
+        }
+        buffer.append(indent).append("</constructor>\n");
+      }
+      popIndent();
+      buffer.append(indent).append("</constructors>\n");
+    }
+    return buffer;
+  }
+
+  /**
+   * Appends the method descriptions of the current class to {@link #buffer}.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder describeMethods() {
+    Method[] methods = cls.getMethods();
+    if (methods.length != 0) {
+      buffer.append(indent).append("<methods>\n");
+      pushIndent();
+      for (Method m: methods) {
+        buffer.append(indent).append("<method name=\"").append(m.getName()).append("\"");
+        if (m.isVarArgs()) buffer.append(" varargs=\"true\"");
+        if (m.isSynthetic()) buffer.append(" synthethic=\"true\"");
+        if (m.isBridge()) buffer.append(" bridge=\"true\"");
+        buffer.append(">\n");
+        pushIndent();
+
+        Class<?>[] exceptions = m.getExceptionTypes();
+        Class<?>[] params = m.getParameterTypes();
+        Annotation[] annotations = m.getDeclaredAnnotations();
+
+        addExceptions(exceptions);
+        addAnnotations(annotations);
+        addParameters(params, m.getParameterAnnotations());
+        Class<?> ret = m.getReturnType();
+        buffer.append(indent).append("<return ");
+
+        if (ret == null) buffer.append("void=\"true\"/>\n");
+        else buffer.append("type=\"").append(ret.getName()).append("\"/>\n");
+
+        popIndent();
+        buffer.append(indent).append("</method>\n");
+      }
+      popIndent();
+      buffer.append(indent).append("</methods>\n");
+    }
+    return buffer;
+  }
+
+  /**
+   * Appends the annotation descriptions for a given set of annotations to {@link #buffer}.
+   * @param annotations The annotations to obtain descriptions for.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder addAnnotations(Annotation[] annotations) {
+    if (annotations.length == 0) return buffer;
+    buffer.append(indent).append("<annotations>\n");
+    pushIndent();
+    for (Annotation a: annotations) {
+      buffer.append(indent).append("<annotation type=\"").append(a.annotationType().getName()).append("\">");
+      buffer.append(xmlEscape(a.toString())).append("</annotation>\n");
+    }
+    popIndent();
+    buffer.append(indent).append("</annotations>\n");
+    return buffer;
+  }
+
+  /**
+   * Appends the exception descriptions for a given set of exceptions to {@link #buffer}.
+   * @param exceptions The exceptions to obtain descriptions for.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder addExceptions(Class<?>[] exceptions) {
+    if (exceptions.length == 0) return buffer;
+    buffer.append(indent).append("<throws>\n");
+    pushIndent();
+    for (int n = 0; n < exceptions.length; n++) {
+      Class<?> e = exceptions[n];
+      buffer.append(indent).append("<exception type=\"").append(e.getName()).append("\"/>\n");
+    }
+    popIndent();
+    buffer.append(indent).append("</throws>\n");
+    return buffer;
+  }
+
+  /**
+   * Appends the parameter descriptions for a given set of method parameters to {@link #buffer}.
+   * @param params The classes for each parameter.
+   * @param paramAnnotations An array of parameter annotations for each parameter.
+   * @return The updated {@link #buffer}.
+   */
+  private StringBuilder addParameters(Class<?>[] params, Annotation[][] paramAnnotations) {
+    if (params.length == 0) return buffer;
+    for (int nParam = 0; nParam < params.length; nParam++) {
+      Class<?> param = params[nParam];
+      Annotation[] pAnn = paramAnnotations[nParam];
+      buffer.append(indent).append("<parameter type=\"").append(param.getName());
+      if (pAnn.length == 0) buffer.append("\"/>\n");
+      else {
+        buffer.append("\">\n");
+        pushIndent();
+        addAnnotations(pAnn);
+        popIndent();
+        buffer.append(indent).append("</parameter>\n");
+      }
+    }
+    return buffer;
+  }
+
+  /**
+   * Changes the indent level to a new value. The new value is multiplied by the
+   * {@link #indentSpaces} value to get the number of spaces to indent each line by.
+   * @param level The new level to set the indent to. Should be +1 or -1 from the previous value.
+   */
+  private void setIndent(int level) {
+    indentLevel = level;
+    indent = indent(indentLevel);
+  }
+
+  /** Increment the current level of indenting. */
+  private void pushIndent() {
+    indent = indent(++indentLevel);
+  }
+
+  /** Decrement the current level of indenting. */
+  private void popIndent() {
+    indent = indent(--indentLevel);
+  }
+
+  /**
+   * Build a new string containing the number of spaces needed for the current level of indenting.
+   * @param i The indent level to calculate the new indent string for.
+   * @return A new string containing i * {@link #indentSpaces} spaces.
+   */
+  private String indent(int i) {
+    char[] arr = new char[indentSpaces * i];
+    Arrays.fill(arr, ' ');
+    return new String(arr);
+  }
+
+  /** An internal structure for mapping characters disallowed in XML to their escape codes. */
+  private static final Map<Character,String> escapes = new HashMap<Character,String>();
+  static {
+    escapes.put('&', "&amp;");
+    escapes.put('<', "&lt;");
+    escapes.put('\r', "&#13;");
+    escapes.put('>', "&gt;");
+    escapes.put('"', "&quot;");
+    escapes.put('\'', "&apos;");
+  }
+
+  /**
+   * Search for disallowed XML characters in a string, and replace them with their escape codes.
+   * @param s The string to escape.
+   * @return A new version of the input string, with XML escaping done.
+   */
+  private static String xmlEscape(String s) {
+    StringBuilder result = null;
+    for(int i = 0, max = s.length(), delta = 0; i < max; i++) {
+      char c = s.charAt(i);
+      String escCode = escapes.get(c);
+
+      if (escCode != null) {
+        if (result == null) result = new StringBuilder(s);
+        result.replace(i + delta, i + delta + 1, escCode);
+        delta += (escCode.length() - 1);
+      }
+    }
+    return (result == null) ? s : result.toString();
+  }
+
+}




More information about the Mulgara-svn mailing list