package ecologylab.serialization;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringBufferInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import ecologylab.collections.Scope;
import ecologylab.generic.Debug;
import ecologylab.generic.HashMapArrayList;
import ecologylab.generic.StringInputStream;
import ecologylab.net.ParsedURL;
import ecologylab.serialization.annotations.Hint;
import ecologylab.serialization.annotations.bibtex_key;
import ecologylab.serialization.annotations.bibtex_tag;
import ecologylab.serialization.annotations.simpl_collection;
import ecologylab.serialization.annotations.simpl_composite;
import ecologylab.serialization.annotations.simpl_composite_as_scalar;
import ecologylab.serialization.annotations.simpl_format;
import ecologylab.serialization.annotations.simpl_hints;
import ecologylab.serialization.annotations.simpl_inherit_parent_tag;
import ecologylab.serialization.annotations.simpl_map;
import ecologylab.serialization.annotations.simpl_scalar;
import ecologylab.serialization.annotations.simpl_tag;
import ecologylab.serialization.types.CrossLanguageTypeConstants;
import ecologylab.serialization.types.ScalarType;
import ecologylab.serialization.types.TypeRegistry;
/**
* Static helper methods that are used during the translation of java objects to XML and back. The
* XML files can also be compressed by using the compression variable. For compression to work, the
* developer should provide a abbreviation table in the format listed in the code.
*
* @author Andruid Kerne
* @author Madhur Khandelwal
* @version 0.5
*/
public class XMLTools extends Debug implements CharacterConstants, SpecialCharacterEntities
{
private static final int DEFAULT_TAG_LENGTH = 15;
static HashMap<String, String> entityTable = new HashMap<String, String>();
static final String SPECIAL_SPELLINGS[] =
{ "nbsp", "iexcl", "cent", "pound", "curren",
"yen", "brvbar", "sect", "uml", "copy", "ordf", "laquo", "not", "shy", "reg", "macr", "deg",
"plusmn", "sup2", "sup3", "acute", "micro", "para", "middot", "ccedil", "sup1", "ordm",
"raquo", "frac14", "frac12", "frac34", "iquest", "Agrave", "Aacute", "Acirc", "Atilde",
"Auml", "Aring", "AElig", "Ccedil", "Egrave", "Eacute", "Ecirc", "Euml", "Igrave", "Iacute",
"Icirc", "Iuml", "ETH", "Ntilde", "Ograve", "Oacute", "Ocirc", "Otilde", "Ouml", "times",
"Oslash", "Ugrave", "Uacute", "Ucirc", "Uuml", "Yacute", "THORN", "szlig", "agrave",
"aacute", "acirc", "atilde", "auml", "aring", "aelig", "ccedil", "egrave", "eacute", "ecirc",
"euml", "igrave", "iacute", "icirc", "iuml", "eth", "ntilde", "ograve", "oacute", "ocirc",
"otilde", "ouml", "divide", "oslash", "ugrave", "uacute", "ucirc", "uuml", "yacute", "thorn" };
static
{
// special spellings
for (char i = 0; i < SPECIAL_SPELLINGS.length; i++)
entityTable.put(SPECIAL_SPELLINGS[i], Character.toString((char) (i + 160)));
entityTable.put("#x00bb", Character.toString((char) 187)); // a hack for weird hex references to
// » � » � right-pointing
// double angle quotation mark =
// right pointing guillemet
// even though we fill the table from 0-255, actually
// 0-8 are illegal. 9,10 (decimal) are legal. 11-31 are illegal. 31-127 are legal.
// syntax such as &
for (char c = 0; c < 255; c++)
putNumberedEntityInTable(c);
for (char c = 913; c < 982; c++)
// greek letters
putNumberedEntityInTable(c);
// the rest of the special chars:
for (int i = 0; i < SPECIAL_CHARACTER_ENTITIES.length; i++)
putNumberedEntityInTable(SPECIAL_CHARACTER_ENTITIES[i]);
putEntityInTable("#039", '\'');
putEntityInTable("#x0027", '\'');
putEntityInTable("#x22", '\"');
putEntityInTable("#x27", '\'');
// magic chars from the NY TIMES
// ’ is really ' (ascii 39)
putEntityInTable("#8217", '\'');
// “ is really � =
putEntityInTable("#8220", '�');//If this line will not compile for you, set the encoding to UTF8
// ” is really � =
putEntityInTable("#8221", '�');
// — is really � = -- em dash
putEntityInTable("#8212", '�');
putEntityInTable("#151", '�');
putEntityInTable("#xa0", ' ');
putEntityInTable("#x2019", '\'');
putEntityInTable("#x2013", '-');
putEntityInTable("#x2014", '?');
putEntityInTable("#9660", '<');
// defined in the XML 1.0 spec: "predefined entities"
putEntityInTable("amp", '&');
putEntityInTable("AMP", '&');
putEntityInTable("quot", '"');
putEntityInTable("lt", '<');
putEntityInTable("gt", '>');
putEntityInTable("apos", '\'');
putEntityInTable("nbsp", ' '); // TODO -- start handling nbsp as a char and in TextTokn
putEntityInTable("bull", BULL);
putEntityInTable("hellip", HELLIP);
putEntityInTable("prime", PRIME);
putEntityInTable("oline", OLINE);
putEntityInTable("frasl", FRASL);
putEntityInTable("weierp", WEIERP);
putEntityInTable("image", IMAGE);
putEntityInTable("real", REAL);
putEntityInTable("trade", TRADE);
putEntityInTable("alefsym", ALEFSYM);
putEntityInTable("larr", LARR);
putEntityInTable("uarr", UARR);
putEntityInTable("rarr", RARR);
putEntityInTable("darr", DARR);
putEntityInTable("harr", HARR);
putEntityInTable("crarr", CRARR);
putEntityInTable("forall", FORALL);
putEntityInTable("part", PART);
putEntityInTable("exist", EXIST);
putEntityInTable("empty", EMPTY);
putEntityInTable("nabla", NABLA);
putEntityInTable("isin", ISIN);
putEntityInTable("notin", NOTIN);
putEntityInTable("ni", NI);
putEntityInTable("prod", PROD);
putEntityInTable("sum", SUM);
putEntityInTable("minus", MINUS);
putEntityInTable("lowast", LOWAST);
putEntityInTable("radic", RADIC);
putEntityInTable("prop", PROP);
putEntityInTable("infin", INFIN);
putEntityInTable("ang", ANG);
putEntityInTable("and", AND);
putEntityInTable("or", OR);
putEntityInTable("cap", CAP);
putEntityInTable("cup", CUP);
putEntityInTable("int", INT);
putEntityInTable("there4", THERE4);
putEntityInTable("sim", SIM);
putEntityInTable("cong", CONG);
putEntityInTable("asymp", ASYMP);
putEntityInTable("ne", NE);
putEntityInTable("equiv", EQUIV);
putEntityInTable("le", LE);
putEntityInTable("ge", GE);
putEntityInTable("sub", SUB);
putEntityInTable("sup", SUP);
putEntityInTable("nsub", NSUB);
putEntityInTable("sube", SUBE);
putEntityInTable("supe", SUPE);
putEntityInTable("oplus", OPLUS);
putEntityInTable("otimes", OTIMES);
putEntityInTable("perp", PERP);
putEntityInTable("sdot", SDOT);
putEntityInTable("lceil", LCEIL);
putEntityInTable("rceil", RCEIL);
putEntityInTable("lfloor", LFLOOR);
putEntityInTable("rfloor", RFLOOR);
putEntityInTable("lang", LANG);
putEntityInTable("rang", RANG);
putEntityInTable("loz", LOZ);
putEntityInTable("spades", SPADES);
putEntityInTable("clubs", CLUBS);
putEntityInTable("hearts", HEARTS);
putEntityInTable("diams", DIAMS);
putEntityInTable("oelig", OELIG);
putEntityInTable("scaron", SCARON);
putEntityInTable("yuml", YUML);
putEntityInTable("circ", CIRC);
putEntityInTable("tilde", TILDE);
putEntityInTable("ensp", ENSP);
putEntityInTable("emsp", EMSP);
putEntityInTable("thinsp", THINSP);
putEntityInTable("zwnj", ZWNJ);
putEntityInTable("zwj", ZWJ);
putEntityInTable("lrm", LRM);
putEntityInTable("rlm", RLM);
putEntityInTable("ndash", NDASH);
putEntityInTable("mdash", MDASH);
putEntityInTable("lsquo", LSQUO);
putEntityInTable("rsquo", RSQUO);
putEntityInTable("sbquo", SBQUO);
putEntityInTable("ldquo", LDQUO);
putEntityInTable("rdquo", RDQUO);
putEntityInTable("bdquo", BDQUO);
putEntityInTable("dagger", DAGGER);
putEntityInTable("permil", PERMIL);
putEntityInTable("lsaquo", LSAQUO);
putEntityInTable("rsaquo", RSAQUO);
putEntityInTable("euro", EURO);
putEntityInTable("dblrarr", DBLRARR);
putEntityInTable("imdbDblQt", ANOTHER_DBL_QUOTE);
putEntityInTable("imdbQt", ANOTHER_QUOTE);
}
/**
* Generate lookup from numbered XML entity character reference (e.g., {) to the actual
* Character.
*
* @param i
*/
private static void putNumberedEntityInTable(char c)
{
String entityString = "#" + (int) c;
putEntityInTable(entityString, c);
}
private static void putEntityInTable(String name, char c)
{
entityTable.put(name, Character.toString(c));
}
private static boolean inheritsTagNameFromParent(Class<?> c)
{
final simpl_inherit_parent_tag tagParent = c.getAnnotation(simpl_inherit_parent_tag.class);
if(tagParent != null)
{
return true;
}else{
return false;
}
}
private static final String BOGUS = "BOGUS";
/**
* This method generates a name for the xml tag given a reference type java object. This is used
* during the translation of Java to xml. Part of this is to translate mixed case class name word
* separation into "_" word separtion.
*
* @param obj
* a java reference type object
* @param suffix
* string to remove from class name, null if nothing to be removed
* @return name of the xml tag (element)
*/
public static String xmlTagFromObject(Object obj, String suffix)
{
return getXmlTagName(obj.getClass(), suffix);
}
/**
* This method generates a name for the xml tag given a reference type java object. This is used
* during the translation of Java to xml. Part of this is to translate mixed case class name word
* separation into "_" word separtion.
*
* @param thatClass
* Class object to translate.
* @param suffix
* string to remove from class name, null if nothing to be removed
* @return name of the xml tag (element)
*/
public static String getXmlTagName(Class<?> thatClass, String suffix)
{
final simpl_tag tagAnnotation = thatClass.getAnnotation(simpl_tag.class);
String result = getXmlTagAnnotationIfPresent(tagAnnotation);
if (result == null)
{
if(inheritsTagNameFromParent(thatClass))
{
return getXmlTagName(thatClass.getSuperclass(), suffix);
}else{
result = getXmlTagName(getClassSimpleName(thatClass), suffix);
}
}
return result;
}
/**
* This method generates a name for the xml tag given a reference type java object. This is used
* during the translation of Java to xml. Part of this is to translate mixed case class name word
* separation into "_" word separtion.
*
* @param describedClass
* Class object to translate.
* @param suffix
* string to remove from class name, null if nothing to be removed
* @return name of the xml tag (element)
*/
public static String getXmlTagName(Field thatField)
{
final simpl_tag tagAnnotation = thatField.getAnnotation(simpl_tag.class);
String result = getXmlTagAnnotationIfPresent(tagAnnotation);
if (result == null)
{
result = getXmlTagName(thatField.getName(), null);
}
return result;
}
public static String getBibtexTagName(Field thatField)
{
final bibtex_tag tagAnnotation = thatField.getAnnotation(bibtex_tag.class);
String result = getBibtexTagAnnotationIfPresent(tagAnnotation);
if (result == null)
{
result = getXmlTagName(thatField.getName(), null).replace('_', ' ');
}
return result;
}
public static String getBibtexTagAnnotationIfPresent(final bibtex_tag tagAnnotation)
{
String result = null;
if (tagAnnotation != null)
{
String thatTag = tagAnnotation.value();
if ((thatTag != null) && (thatTag.length() > 0))
result = thatTag;
}
return result;
}
public static String getXmlTagAnnotationIfPresent(final simpl_tag tagAnnotation)
{
String result = null;
if (tagAnnotation != null)
{
String thatTag = tagAnnotation.value();
if ((thatTag != null) && (thatTag.length() > 0))
result = thatTag;
}
return result;
}
/**
* This method generates a name for the xml tag given a reference type java object. This is used
* during the translation of Java to xml. Part of this is to translate mixed case class name word
* separation into "_" word separtion.
*
* @param className
* class name of a java reference type object
* @param suffix
* string to remove from class name, null if nothing to be removed
* @return name of the xml tag (element)
*/
public static String getXmlTagName(String className, String suffix)
{
if ((suffix != null) && (className.endsWith(suffix)))
{
int suffixPosition = className.lastIndexOf(suffix);
className = className.substring(0, suffixPosition);
}
StringBuilder result = new StringBuilder(DEFAULT_TAG_LENGTH);
// translate mixed case class name word separation into
// _ word separtion
int classNameLength = className.length();
for (int i = 0; i < classNameLength; i++)
{
char c = className.charAt(i);
if ((c >= 'A') && (c <= 'Z'))
{
char lc = Character.toLowerCase(c);
if (i > 0)
result.append('_');
result.append(lc);
}
else
result.append(c);
}
return result.toString();
}
/**
* This method generates a name for an ElementState object, given an XML element name. It is used
* during translation of XML to Java. Using the returned class name, the appropriate class can be
* instantiated using reflection.
*
* @param elementName
* the name of the XML element
* @return the name of the Java class corresponding to the elementName
*/
public static String classNameFromElementName(String elementName)
{
return javaNameFromElementName(elementName, true);
}
/**
* This method generates a name for an ElementState object, given an XML attribute name. It is
* used during translation of XML to Java. Using the returned class name, the appropriate class
* can be instantiated using reflection.
*
* @param elementName
* the name of the XML element attribute
* @return the name of the Java class corresponding to the elementName
*/
public static String fieldNameFromElementName(String elementName)
{
return javaNameFromElementName(elementName, false);
}
/**
* Generate the name of a Java class (capitalized) or field (starts with lower case), given the
* name of an XML tag or attribute. Used during translation of XML to Java.
*
* @param elementName
* the name of the XML element or tag
* @param capsOn
* true if the first letter of output should be capitalized.
*
* @return the name of the Java class corresponding to the elementName
*/
public static String javaNameFromElementName(String elementName, boolean capsOn)
{
StringBuilder result = new StringBuilder(DEFAULT_TAG_LENGTH);
for (int i = 0; i < elementName.length(); i++)
{
char c = elementName.charAt(i);
if (capsOn)
{
result.append(Character.toUpperCase(c));
capsOn = false;
}
else
{
if (c != '_')
result.append(c);
}
if (c == '_')
capsOn = true;
}
return result.toString();
}
public static String fieldNameFromNodeName(String nodeName)
{
return javaNameFromElementName(nodeName, false);
}
/**
* This method generates a name for the *setter* method for a given Java primitive type. For
* example, for the attribute <code> intensity </code>, it will generate a setter method named
* <code> setIntensity </code>. It is used during translation of xml to Java. Using this method
* name, the appropriate field is populated.
*
* @param tagName
* the name of the xml element or tag
* @return the name of the *setter* method corresponding to the tagName
*/
public static String methodNameFromTagName(String tagName)
{
StringBuilder result = new StringBuilder(DEFAULT_TAG_LENGTH);
result.append("set");
for (int i = 0; i < tagName.length(); i++)
{
char c = tagName.charAt(i);
if (i == 0)
result.append(Character.toUpperCase(c));
else
result.append(c);
}
return result.toString();
}
/**
* This method generates a field name from a reference type nested object. It just converts the
* camelcase name to the lowercase. This is used while generating xml from Java.
*
* @param elementState
* the reference type field for which a field field name needs to be generated
* @return field name for the given reference type field
*/
public static String fieldNameFromObject(ElementState elementState)
{
StringBuilder result = new StringBuilder(DEFAULT_TAG_LENGTH);
String elementName = getClassSimpleName(elementState);
for (int i = 0; i < elementName.length(); i++)
{
char c = elementName.charAt(i);
if (i == 0)
c = Character.toLowerCase(c);
result.append(c);
}
return result.toString();
}
static final HashMap<String, String> classAbbrevNames = new HashMap<String, String>();
static final HashMap<String, String> packageNames = new HashMap<String, String>();
/**
* This method returns the abbreviated name of the class, without the package qualifier. It also
* puts the name into a hashtable, so that next time the function is called for the same class,
* the class name can be retrieved quickly from the hashtable. Used while generating xml from
* Java.
*
* @param thatClass
* the <code>Class</code> type of an object
* @return the abbreviated name of the class - without the package qualifier
*/
public static String getClassSimpleName(Class thatClass)
{
String fullName = thatClass.getName();
String abbrevName = classAbbrevNames.get(fullName);
if (abbrevName == null)
{
abbrevName = thatClass.getSimpleName();
synchronized (classAbbrevNames)
{
classAbbrevNames.put(fullName, abbrevName);
}
}
return abbrevName;
}
/**
* This method gets the package name of a give Java class. It also puts the name into a hashtable,
* so that next time the function is called for the same class, the package name can be retrieved
* quickly from the hashtable. Used while generating Java class from xml.
*
* @param thatClass
* the <code>Class</code> type of an object
* @return the package name of the class, with an extra "." at the end.
*/
public static String getPackageName(Class thatClass)
{
String className = thatClass.getName();
String packageName = null;
if (packageNames.containsKey(className))
{
packageName = packageNames.get(className);
}
else
{
if (thatClass.getPackage() != null)
{
// packageName = className.substring(6, className.lastIndexOf("."));
packageName = thatClass.getPackage().getName() + ".";
synchronized (packageNames)
{
packageNames.put(className, packageName);
}
}
}
return packageName;
}
/**
* This method returns the abbreviated name of the class, without the package qualifier.
*
* @param o
* the object
* @return the abbreviated name of the class - without the package qualifier.
*/
public static String getClassSimpleName(Object o)
{
return getClassSimpleName(o.getClass());
}
public static final int UTF16_LE = 0;
public static final int UTF16 = 1;
public static final int UTF8 = 2;
/**
* xml header
*/
static protected final String XML_FILE_HEADER = "<?xml version=" + "\"1.0\"" + " encoding="
+ "\"UTF-8\"" + "?>\n";
/**
* This method gets the name of <code>this</code> class.
*
* @return the abbreviated name of this class - without the package qualifier
*/
@Override
public String getClassSimpleName()
{
return getClassSimpleName(this);
}
/**
* This method gets the package name of a give Java class. Used while generating Java class from
* xml.
*
* @param o
* the <code>Class</code> type of an object
* @return the package name of the class
*/
public static String getPackageName(Object o)
{
return getPackageName(o.getClass());
}
/**
* This method gets the package name of <code>this</code>Java class.
*
* @return the package name of the class
*/
@Override
public String getPackageName()
{
return getPackageName(this);
}
@Override
public String toString()
{
return getClassSimpleName(this);
}
/**
* Assumes that outputFile should be a file, whose parent directories may not exist. Creates the
* parent directories for the given File, and throws an XMLTranslationException if outputFile is a
* directory and not a file.
*
* @param outputFile
* @throws SIMPLTranslationException
*/
public static void createParentDirs(File outputFile) throws SIMPLTranslationException
{
if (outputFile.isDirectory())
throw new SIMPLTranslationException(
"Output path is already a directory, so it can't be a file: "
+ outputFile.getAbsolutePath());
String outputDirName = outputFile.getParent();
if (outputDirName != null) // if no parent dir exist, don't make dirs.
{
File outputDir = new File(outputDirName);
outputDir.mkdirs();
if (!outputDir.exists())
throw new SIMPLTranslationException(
"Can't make directories due to permissions problems: " + outputDirName);
}
}
static void createErrorHandler(final DocumentBuilder builder)
{
builder.setErrorHandler(new org.xml.sax.ErrorHandler()
{
// ignore fatal errors (an exception is guaranteed)
@Override
public void fatalError(SAXParseException exception) throws SAXException
{
}
// treat validation errors as fatal
@Override
public void error(SAXParseException e) throws SAXParseException
{
throw e;
}
// dump warnings too
@Override
public void warning(SAXParseException err) throws SAXParseException
{
println(builder + "** Warning" + ", line " + err.getLineNumber() + ", urn "
+ err.getSystemId());
println(" " + err.getMessage());
}
});
}
/**
* This method creates a DOM Document from an XML-formatted String, encoded as UTF8.
*
* @param charSequence
* the XML-formatted String from which the DOM is to be created
*
* @return the Document object
*/
static public Document buildDOMFromXMLString(CharSequence charSequence)
{
return buildDOMFromXMLCharSequence(charSequence, XMLTools.UTF8);
}
/**
* This method creates a DOM Document from an XML-formatted String.
*
* @param charSequence
* the XML-formatted String from which the DOM is to be created
* @param charsetType
* A constant from ecologylab.generic.StringInputStream. 0 for UTF16_LE. 1 for UTF16. 2
* for UTF8.
*
* @return the Document object
*/
static public Document buildDOMFromXMLCharSequence(CharSequence charSequence, int charsetType)
{
InputStream xmlStream = new StringInputStream(charSequence, charsetType);
return buildDOM(xmlStream);
}
/**
* This method creates a DOM Document from the XML file at a given URI, which could be a local
* file or a URL.
*
* @param inStream
* InputStream from which the DOM is to be created
*
* @return the Document object
*/
static public Document buildDOM(InputStream inStream)
{
Document document = null;
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
XMLTools.createErrorHandler(builder);
document = builder.parse(inStream);
}
catch (SAXParseException spe)
{
// Error generated by the parser
println("ERROR parsing DOM in" + inStream + ":\n\t** Parsing error on line "
+ spe.getLineNumber() + ", urn=" + spe.getSystemId());
println(" " + spe.getMessage());
}
catch (SAXException sxe)
{ // Error generated during parsing
Exception x = sxe;
if (sxe.getException() != null)
x = sxe.getException();
x.printStackTrace();
}
catch (ParserConfigurationException pce)
{
// Parser with specified options can't be built
pce.printStackTrace();
}
catch (IOException ioe)
{
// I/O error
ioe.printStackTrace();
}
catch (FactoryConfigurationError fce)
{
fce.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
return document;
}
/**
* Report exception during DOM parsing.
*
* @param spe
* @param xmlFileOrURLName
*/
static void reportException(SAXParseException spe, String xmlFileOrURLName)
{
println(xmlFileOrURLName + ":\n** Parsing error" + ", line " + spe.getLineNumber() + ", urn "
+ spe.getSystemId());
println(" " + spe.getMessage());
// Use the contained exception, if any
Exception x = spe;
if (spe.getException() != null)
x = spe.getException();
x.printStackTrace();
}
/**
* Report exception during DOM parsing.
*
* @param sxe
*/
static void reportException(SAXException sxe)
{
Exception x = sxe;
if (sxe.getException() != null)
x = sxe.getException();
x.printStackTrace();
}
// ////////////// methods to generate DOM objects ///////////////////////
/**
* This method creates a DOM Document from the XML file at a given URL.
*
* @param url
* the URL to the XML from which the DOM is to be created
*
* @return the Document object
*/
static public Document buildDOM(URL url)
{
return buildDOM(url.toString());
}
static public Document buildDOM(ParsedURL purl)
{
return purl.isFile() ? buildDOM(purl.file()) : XMLTools.buildDOM(purl.url());
}
/**
* This method creates a DOM Document from the XML file at a given URI, which could be a local
* file or a URL.
*
* @param xmlFileOrURLName
* the path to the XML from which the DOM is to be created
*
* @return the Document object
*/
static public Document buildDOM(String xmlFileOrURLName)
{
Document document = null;
try
{
DocumentBuilder builder = getDocumentBuilder();
XMLTools.createErrorHandler(builder);
if (!xmlFileOrURLName.contains("://"))
xmlFileOrURLName = "file:///" + xmlFileOrURLName;
document = builder.parse(xmlFileOrURLName);
}
catch (SAXParseException spe)
{
// Error generated by the parser
XMLTools.reportException(spe, xmlFileOrURLName);
}
catch (SAXException sxe)
{
// Error generated during parsing
XMLTools.reportException(sxe);
}
catch (IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
return document;
}
/**
* This method creates a DOM Document from the local XML file.
*
* @param file
* the XML file from which the DOM is to be created
*
* @return the Document object
*/
static public Document buildDOM(File file)
{
Document document = null;
try
{
DocumentBuilder builder = getDocumentBuilder();
XMLTools.createErrorHandler(builder);
document = builder.parse(file);
}
catch (SAXParseException spe)
{
// Error generated by the parser
XMLTools.reportException(spe, file.getAbsolutePath());
}
catch (SAXException sxe)
{
// Error generated during parsing
XMLTools.reportException(sxe);
}
catch (Exception e)
{
e.printStackTrace();
}
return document;
}
/**
* Seek an @xml_format annotaion on the Field object. If there is one with a non-zero length,
* return it.
*
* @param field
*
* @return An array of Strings with format info in them, or null if there wasn't one or it was
* empty.
*/
public static String[] getFormatAnnotation(Field field)
{
String format[] = null;
simpl_format formatAnnotation = field
.getAnnotation(simpl_format.class);
if (formatAnnotation != null)
{
String[] formatStrings = formatAnnotation.value();
if (formatStrings.length > 0)
format = formatStrings;
}
return format;
}
/**
* Get an instance; generate an XmlTranslationException if there's a problem.
*
* @param thatClass
* The type of the object to translate in to.
*
* @return The resulting object.
*
* @throws SIMPLTranslationException
* If the constructor fails, or if that class lacks a constructor that takes no
* parameters.
*/
public static <T> T getInstance(Class<T> thatClass) throws SIMPLTranslationException
{
// form the new object derived from ElementState
T nestedObject = null;
try
{
nestedObject = thatClass.newInstance();
}
catch (IllegalAccessException e)
{
try
{
Constructor c = thatClass.getConstructor(null);
c.setAccessible(true);
return (T)c.newInstance(null);
}
catch(Exception ex)
{
throw new SIMPLTranslationException("Instantiation ERROR for " + thatClass
+ ". Is there a public constructor with no arguments?", ex);
}
}
catch(Exception e)
{
throw new SIMPLTranslationException("Instantiation ERROR for " + thatClass
+ ". Is there a public constructor with no arguments?", e);
}
return nestedObject;
}
public static String toString(Object o)
{
return getClassSimpleName(o);
}
/**
* @param string
* @return The string wrapped in double quote marks.
*/
static String q(String string)
{
return "\"" + string + "\" ";
}
public static String nameVal(String label, String val)
{
return (val == null) ? "" : "\n " + label + "=" + q(val);
}
public static String nameVal(String label, URL val)
{
String valStr = (val == null) ? null : val.toString();
return nameVal(label, valStr);
}
public static String nameVal(String label, long val)
{
return nameVal(label, val + "");
}
public static String nameVal(String label, boolean val)
{
return nameVal(label, val + "");
}
public static String nameVal(String label, float val)
{
return nameVal(label, val + "");
}
/**
* This method propages the values of all the primitive types from the source object to the
* destination object.
*
* @param src
* source object from the values need to be copies
* @param dest
* destination object to which the values need to be copied
*/
public static void propagateFields(Object src, Object dest)
{
// propagate values automatically
Field[] fields = src.getClass().getFields();
for (int i = 0; i < fields.length; i++)
{
Field thatField = fields[i];
// we propage values only for primitive types
// for others we build RealObjects!
// the second condition being checked is for parent objects
// getFields gets the public fields in parent classes too, we dont want them
if ((thatField.getType().isPrimitive())
&& (thatField.getDeclaringClass().getName() == src.getClass().getName()))
{
try
{
String methodName = methodNameFromTagName(thatField.getName());
Class[] parameters = new Class[1]; // just 1 parameter in all the *setter* methods
Method attrMethod = null; // and thats the value of the field to be set
// this gets the type of the field,
// the setter method will have the same type for its argument obviously
parameters[0] = thatField.getType();
attrMethod = dest.getClass().getMethod(methodName, parameters);
if (attrMethod != null)
{
Object[] args = new Object[1]; // just one argument to the method
args[0] = thatField.get(src);
attrMethod.invoke(dest, args);
}
}
catch (Exception e) // IllegalArgument, IllegalAccess, IllegalCast
{
e.printStackTrace();
}
}
}
}
/**
* Use this method to efficiently get a <code>String</code> from a <code>StringBuilder</code> on
* those occassions when you plan to keep using the <code>StringBuilder</code>, and want an
* efficiently made copy. In those cases, <i>much</i> better than
* <code>new String(StringBuilder)</code>
*/
public static final String toString(StringBuilder buffer)
{
return buffer.substring(0);
}
public static String xmlHeader()
{
return "<?xml version=" + "\"1.0\"" + " encoding=" + "\"US-ASCII\"" + "?>";
}
static final int ISO_LATIN1_START = 128;
/**
* Translate XML named entity special characters into their Unicode char equivalents.
*/
public static String unescapeXML(String s)
{
if (s == null)
return null;
int ampPos = s.indexOf('&');
if (ampPos == -1)
return s;
else
{
StringBuilder buffy = new StringBuilder(s);
unescapeXML(buffy, ampPos);
return buffy.toString();
}
}
public static void unescapeXML(StringBuilder buffy)
{
unescapeXML(buffy, 0);
}
/**
* Translate XML named entity special characters into their Unicode char equivalents.
*/
public static void unescapeXML(StringBuilder buffy, int startPos)
{
int ampPos; // , startPos);
while ((ampPos = buffy.indexOf("&", startPos)) != -1)
{
int entityPos = ampPos + 1;
int semicolonPos = buffy.indexOf(";", entityPos);
if (semicolonPos == -1)
return;
else if (semicolonPos - ampPos > 7)
{
startPos = semicolonPos + 1;
continue;
}
/*
* We are aready checking whether entityLenth is larger than 7, so we don't need the below -
* Eunyee int entityLength = semicolonPos - ampPos; if (entityLength > 8) return sb;
*/
// find position of & followed by ;
// lookup entity in the middle of that in the HashMap
// if you find a match, do a replacement *in place*
// this includes shifting the rest of the string up, and
// resetting the length of the StringBuilder to be shorter
// (since the entity is always longer than the char it maps to)
// then call recursively, setting the startPos index to after the last
// entity that we found
String encoded = buffy.substring(entityPos, semicolonPos);
String lookup = entityTable.get(encoded);
if (lookup != null)
{
// if (!"nbsp".equals(encoded))
// println("unescapeXML[" +encoded + "] -> " + lookup );
if (!lookup.equals(BOGUS))
{
buffy = buffy.replace(ampPos, semicolonPos + 1, lookup);
}
startPos = ampPos + 1;
}
else
{
if (buffy.charAt(entityPos) == '#')
{
int numPos = entityPos++;
try
{
Integer newEntity = Integer.decode(buffy.substring(numPos, semicolonPos));
println("unescapeXML[" + encoded + "] Create New Entity!");
char newChar = (char) newEntity.intValue();
putEntityInTable(encoded, newChar);
buffy = buffy.replace(ampPos, semicolonPos + 1, Character.toString(newChar));
startPos = ampPos + 1;
}
catch (NumberFormatException e)
{
println("unescapeXML[" + encoded + "] FAILED");
entityTable.put(encoded, BOGUS);
}
}
startPos = semicolonPos + 1;
if (startPos >= buffy.length())
return;
}
}
}
/**
* Replaces characters that may be confused by a HTML parser with their equivalent character
* entity references.
*
* @param stringToEscape
* original string which may contain some characters which are confusing to the HTML
* parser, for eg. < and >
* @return the string in which the confusing characters are replaced by their equivalent entity
* references
*/
public static void oldEscapeXML(StringBuilder result, CharSequence stringToEscape)
{
int length = stringToEscape.length();
int newLength = length;
// first check for characters that might
// be dangerous and calculate a length
// of the string that has escapes.
for (int i = 0; i < length; i++)
{
char c = stringToEscape.charAt(i);
switch (c)
{
case '\"':
newLength += 5;
break;
case '&':
case '\'':
newLength += 4;
break;
case '<':
case '>':
newLength += 3;
break;
case '\n':
newLength += 4;
break;
default:
if (c >= ISO_LATIN1_START)
newLength += 5;
}
}
if (length == newLength)
{
// nothing to escape in the string
// result.append(stringToEscape)
result.append(stringToEscape);
return;
// return stringToEscape;
}
if (result == null)
result = new StringBuilder(newLength);
for (int i = 0; i < length; i++)
{
char c = stringToEscape.charAt(i);
}
}
/**
* Table of replacement Strings for characters deemed nasty in XML.
*/
// static final String[] ESCAPE_TABLE = new String[ISO_LATIN1_START];
static final String[] ESCAPE_TABLE = new String[Character.MAX_VALUE];
static
{
for (char c = 0; c < ISO_LATIN1_START; c++)
{
switch (c)
{
case '\"':
ESCAPE_TABLE[c] = """;
break;
case '\'':
ESCAPE_TABLE[c] = "'";
break;
case '&':
ESCAPE_TABLE[c] = "&";
break;
case '<':
ESCAPE_TABLE[c] = "<";
break;
case '>':
ESCAPE_TABLE[c] = ">";
break;
case '\n':
ESCAPE_TABLE[c] = "
";
break;
case TAB:
case CR:
default:
break;
}
}
int n = SPECIAL_CHARACTER_ENTITIES.length;
for (int i = 0; i < n; i++)
addEscapeTableEntry(SPECIAL_CHARACTER_ENTITIES[i]);
// for (char c=ISO_LATIN1_START; c<Character.MAX_VALUE; c++)
for (char c = ISO_LATIN1_START; c < 256; c++)
{
addEscapeTableEntry(c);
}
}
private static void addEscapeTableEntry(char c)
{
StringBuilder entry = new StringBuilder(7);
entry.append('&').append('#').append((int) c).append(';');
ESCAPE_TABLE[c] = entry.toString();
}
static boolean noCharsNeedEscaping(CharSequence stringToEscape)
{
int length = stringToEscape.length();
for (int i = 0; i < length; i++)
{
char c = stringToEscape.charAt(i);
if (c >= ISO_LATIN1_START)
{
return false;
}
else if (ESCAPE_TABLE[c] != null)
{
return false;
}
else
{
switch (c)
{
case TAB:
case CR:
break;
default:
if (c < 0x20) // TODO we seem, currently to throw these chars away. this would be easy to
// fix.
return false;
break;
}
}
}
return true;
}
/**
* Replaces characters that may be confused by a HTML parser with their equivalent character
* entity references.
*
* @param stringToEscape
* original string which may contain some characters which are confusing to the HTML
* parser, for eg. < and >
* @return the string in which the confusing characters are replaced by their equivalent entity
* references
*/
public static void escapeXML(StringBuilder buffy, CharSequence stringToEscape)
{
if (noCharsNeedEscaping(stringToEscape))
buffy.append(stringToEscape);
else
{
int length = stringToEscape.length();
for (int i = 0; i < length; i++)
{
final char c = stringToEscape.charAt(i);
final String escaped = ESCAPE_TABLE[c];
if (escaped != null)
buffy.append(escaped); // append as String
else
{
switch (c)
{
case TAB:
case CR:
buffy.append(c); // append as char (fastest!)
break;
default:
if (c >= 255)
{
// println("escapeXML() ERROR: " + ((int) c));
int cInt = c;
buffy.append('&').append('#').append(cInt).append(';');
}
else if (c >= 0x20)
buffy.append(c); // append as char (fastest!)
break;
}
}
}
}
}
public static void escapeXML(Appendable appendable, CharSequence stringToEscape)
throws IOException
{
if (noCharsNeedEscaping(stringToEscape))
appendable.append(stringToEscape);
else
{
int length = stringToEscape.length();
for (int i = 0; i < length; i++)
{
final char c = stringToEscape.charAt(i);
final String escaped = ESCAPE_TABLE[c];
if (escaped != null)
appendable.append(escaped); // append as String
else
{
switch (c)
{
case TAB:
case CR:
appendable.append(c); // append as char (fastest!)
break;
default:
if (c >= 255)
{
// println("escapeXML() ERROR: " + ((int) c));
int cInt = c;
appendable.append('&').append('#').append(String.valueOf(cInt)).append(';');
}
else if (c >= 0x20)
appendable.append(c); // append as char (fastest!)
break;
}
}
}
}
}
/**
* Generate a DOM tree from a given String in the XML form.
* <p/>
* Uses the deprecated StringBufferInputStream class.
*
* @param contents
* the string for which the DOM needs to be constructed.
* @return the DOM tree representing the XML string.
*
* @deprecated
*/
@Deprecated
public static Document getDocument(String contents)
{
return XMLTools.buildDOM(new StringBufferInputStream(contents));
}
public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException
{
return DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
/**
* Pretty printing XML, properly indented according to hierarchy.
*
* @param xmlDoc
* @param outputStreamWriter
* @throws FileNotFoundException
* @throws SIMPLTranslationException
*/
public static void translateToXML(Document xmlDoc, File outFile) throws SIMPLTranslationException
{
try
{
translateToXML(xmlDoc, new FileOutputStream(outFile));
}
catch (FileNotFoundException e)
{
throw new SIMPLTranslationException("Writing pretty XML[" + outFile + "]", e);
}
}
static final int INDENT_AMOUNT = 2;
static final String INDENT_AMOUNT_STRING = Integer.toString(INDENT_AMOUNT);
/**
* Pretty print XML, properly indented according to hierarchy.
*
* @param xmlDoc
* @param out
* @throws SIMPLTranslationException
* @throws IOException
*/
public static void translateToXML(Document xmlDoc, OutputStream outputStream)
throws SIMPLTranslationException
{
Transformer transformer;
try
{
TransformerFactory factory = TransformerFactory.newInstance();
factory.setAttribute("indent-number", new Integer(INDENT_AMOUNT));
transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
// transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
// INDENT_AMOUNT_STRING);
OutputStreamWriter osw = new java.io.OutputStreamWriter(outputStream, "utf-8");
transformer.transform(new DOMSource(xmlDoc), new StreamResult(osw));
osw.close();
}
catch (Exception e)
{
throw new SIMPLTranslationException("Writing pretty XML", e);
}
}
/**
* @param field
*
* @return true if the class name and variable name for the field are equivalent. This is the case
* when the variable name is capitalized, it equals the class name.
*/
public static boolean equivalentClassAndVarNames(Field field)
{
boolean result = false;
String className = field.getType().getName();
String varName = field.getName();
int classLength = className.length();
if (classLength == varName.length())
{
char classStart = className.charAt(0);
char varStart = varName.charAt(0);
if (classStart == Character.toUpperCase(varStart))
{
result = className.regionMatches(1, varName, 1, classLength - 1);
}
}
return result;
}
/**
* @param object
* which might be representable as a collection. Must not be null.
*
* @return if the Object passed in is of Collection or Map type, return a Collection representing
* it. <br/>
* else return null.
*/
public static Collection getCollection(Object object)
{
Collection result = null;
if (object instanceof Collection)
result = (Collection) object;
else if (object instanceof Map)
result = ((Map) object).values();
return result;
}
public static boolean isScalar(Field field)
{
return field.isAnnotationPresent(simpl_scalar.class);
}
public static boolean isCompositeAsScalarvalue(Field field)
{
return field.isAnnotationPresent(simpl_composite_as_scalar.class);
}
public static Hint simplHint(Field field)
{
simpl_hints hintsAnnotation = field.getAnnotation(simpl_hints.class);
return (hintsAnnotation == null) ? Hint.XML_ATTRIBUTE : hintsAnnotation.value()[0];
}
public static boolean representAsComposite(Field field)
{
return field.isAnnotationPresent(simpl_composite.class);
}
public static boolean representAsCollectionOrMap(Field field)
{
return representAsCollection(field) || representAsMap(field);
}
public static boolean representAsMap(Field field)
{
return field.isAnnotationPresent(simpl_map.class);
}
public static boolean representAsCollection(Field field)
{
return field.isAnnotationPresent(simpl_collection.class);
}
/**
* @param field
* @return true if the Field is one translated by the Type system.
*/
public static boolean isScalarValue(Field field)
{
return TypeRegistry.containsScalarType(field.getType());
}
/**
* Wrap the passed in argument in HTML tags, so it can be parsed as XML.
*
* @param htmlFragmentString
* A piece of valid XHTML.
*
* @return
*/
public static String wrapInHTMLTags(String htmlFragmentString)
{
StringBuilder buffy = new StringBuilder(htmlFragmentString.length() + 13);
return buffy.append("<html>").append(htmlFragmentString).append("</html>").toString();
}
public static void main(String[] a)
{
String s = "This is the first& this is the next’";
System.err.println(unescapeXML(s));
}
public static boolean isEnum(Field thatField)
{
return isEnum(thatField.getType()) || thatField.getType().isEnum();
}
public static boolean isEnum(Class thatClass)
{
return Enum.class.isAssignableFrom(thatClass);
}
public static boolean isComposite(Class thatClass)
{
return ElementState.class.isAssignableFrom(thatClass);
}
public static Enum<?> createEnumeratedType(Field field, String valueString)
{
if (field.getType().isEnum())
{
Object[] enumArray = field.getType().getEnumConstants();
for (Object enumObj : enumArray)
{
if (enumObj instanceof Enum<?>)
{
Enum<?> enumeratedType = ((Enum<?>) enumObj);
if (enumeratedType.toString().equals(valueString))
return enumeratedType;
}
}
}
return null;
}
/**
* Utility function to check if the field is declared as generic type
*
* @param field
* @return
*/
public static boolean isParameterizedType(Field field)
{
if (field.getGenericType() instanceof ParameterizedType)
{
return true;
}
else
return false;
}
/**
* Utility function to get the generic parameters of a field as class array
*
* @param field
* @return
*/
public static ArrayList<Class<?>> getGenericParameters(Field field)
{
ArrayList<Class<?>> result = new ArrayList<Class<?>>();
if (field.getGenericType() instanceof ParameterizedType)
{
Type[] ta = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
for (int i = 0; i < ta.length; i++)
{
if (ta[i] instanceof Class<?>)
result.add(((Class<?>) ta[i]));
}
}
return result;
}
/**
* Utility function to get the generic parameters as string array
*
* @param field
* @return
* @throws DotNetTranslationException
*/
public static String[] getCSharpGenericParamters(Field field)
{
String[] result = null;
ArrayList<Class<?>> paramClasses = getGenericParameters(field);
if (paramClasses.size() > 0)
{
result = new String[paramClasses.size()];
for (int i = 0; i < paramClasses.size(); i++)
{
result[i] = inferCSharpType(paramClasses.get(i));
}
}
return result;
}
public static String getCSharpGenericParametersStringRecursive(ParameterizedType pType)
{
StringBuilder result = new StringBuilder();
Type[] ta = pType.getActualTypeArguments();
result.append('<');
for (int i = 0; i < ta.length; i++)
{
if (ta[i] instanceof Class<?>)
{
if (i == 0)
result.append(inferCSharpType((Class<?>) ta[i]));
else
result.append(", " + inferCSharpType((Class<?>) ta[i]));
}
else if (ta[i] instanceof WildcardType)
{
WildcardType wildcardType = (WildcardType) ta[i];
Type[] types = wildcardType.getUpperBounds();
for (Type t : types)
{
Class<?> c = (Class<?>) t;
if (i == 0)
result.append(inferCSharpType(c));
else
result.append(", " + inferCSharpType(c));
}
}
else if (ta[i] instanceof TypeVariable)
{
TypeVariable tv = (TypeVariable) ta[i];
if (i == 0)
result.append(tv.getName());
else
result.append(", " + tv.getName());
}
else
{
ParameterizedType pT = (ParameterizedType) ta[i];
Class<?> rT = (Class<?>) pT.getRawType();
if (i == 0)
result.append(inferCSharpType(rT) + getCSharpGenericParametersStringRecursive(pT));
else
result.append(", " + inferCSharpType(rT) + getCSharpGenericParametersStringRecursive(pT));
}
}
result.append('>');
return result.toString();
}
public static String getCSharpGenericParametersString(Field field)
{
String result;
if (isParameterizedType(field))
{
result = getCSharpGenericParametersStringRecursive((ParameterizedType) field.getGenericType());
return result;
}
else
return "";
}
/**
* Utility function to get the generic parameters as comma separated String
*
* @param field
* @return
* @throws DotNetTranslationException
*/
public static String getCSharpGenericParametersStringOld(Field field)
{
StringBuilder result = null;
String[] paramArray = getCSharpGenericParamters(field);
if (paramArray == null)
{
System.out.println("null generic parameters");
String[] paramTest = getCSharpGenericParamters(field);
}
else if (paramArray.length > 0)
{
result = new StringBuilder();
result.append("<");
result.append(implode(paramArray, ", "));
result.append(">");
}
return result.toString();
}
/**
* Utility function to implode an array of strings
*
* @param ary
* @param delim
* @return
*/
public static String implode(String[] ary, String delim)
{
String out = "";
for (int i = 0; i < ary.length; i++)
{
if (i != 0)
{
out += delim;
}
out += ary[i];
}
return out;
}
/**
* Utility function to translate Java type to CSharp type
*
* @param fieldType
* @return
* @throws DotNetTranslationException
*/
public static String inferCSharpType(Class<?> fieldType)
{
String result = null;
if (int.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_INTEGER;
}
else if (float.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_FLOAT;
}
else if (double.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_DOUBLE;
}
else if (byte.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_BYTE;
}
else if (char.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_CHAR;
}
else if (boolean.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_BOOLEAN;
}
else if (long.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_LONG;
}
else if (short.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_SHORT;
}
else if (String.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_STRING;
}
else if (StringBuilder.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_STRING_BUILDER;
}
else if (URL.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_URL;
}
else if (ParsedURL.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_PARSED_URL;
}
else if (ScalarType.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_SCALAR_TYPE;
}
else if (Date.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_DATE;
}
else if (ArrayList.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_ARRAYLIST;
}
else if (HashMap.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_HASHMAP;
}
else if (HashMapArrayList.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_HASHMAPARRAYLIST;
}
else if (Scope.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_SCOPE;
}
else if (Class.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_CLASS;
}
else if (Field.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_FIELD;
}
else if (ByteBuffer.class == fieldType)
{
result = CrossLanguageTypeConstants.DOTNET_BINARY_DATA;
}
else
{
// Assume the field is custom object
result = fieldType.getSimpleName();
}
return result;
}
/**
* The maximum size to which the padding constant(s) can expand.
*/
private static final int PAD_LIMIT = 8192;
/**
* Returns padding using the specified delimiter repeated to a given length.
*
* <pre>
* StringUtils.padding(0, 'e') = ""
* StringUtils.padding(3, 'e') = "eee"
* StringUtils.padding(-2, 'e') = IndexOutOfBoundsException
* </pre>
*
* Note: this method doesn't not support padding with <a
* href="http://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary
* Characters</a> as they require a pair of <code>char</code>s to be represented. If you are
* needing to support full I18N of your applications consider using {@link #repeat(String, int)}
* instead.
*
*
* @param repeat
* number of times to repeat delim
* @param padChar
* character to repeat
* @return String with repeated character
* @throws IndexOutOfBoundsException
* if <code>repeat < 0</code>
* @see #repeat(String, int)
*/
private static String padding(int repeat, char padChar) throws IndexOutOfBoundsException
{
if (repeat < 0)
{
throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat);
}
final char[] buf = new char[repeat];
for (int i = 0; i < buf.length; i++)
{
buf[i] = padChar;
}
return new String(buf);
}
/**
* Left pad a String with a specified character.
*
* Pad to a size of <code>size</code>.
*
* <pre>
* StringUtils.leftPad(null, *, *) = null
* StringUtils.leftPad("", 3, 'z') = "zzz"
* StringUtils.leftPad("bat", 3, 'z') = "bat"
* StringUtils.leftPad("bat", 5, 'z') = "zzbat"
* StringUtils.leftPad("bat", 1, 'z') = "bat"
* StringUtils.leftPad("bat", -1, 'z') = "bat"
* </pre>
*
* @param str
* the String to pad out, may be null
* @param size
* the size to pad to
* @param padChar
* the character to pad with
* @return left padded String or original String if no padding is necessary, <code>null</code> if
* null String input
* @since 2.0
*/
public static String padLeft(String str, int size, char padChar)
{
if (str == null)
{
return null;
}
int pads = size - str.length();
if (pads <= 0)
{
return str; // returns original String when possible
}
if (pads > PAD_LIMIT)
{
return padLeft(str, size, String.valueOf(padChar));
}
return padding(pads, padChar).concat(str);
}
/**
* Left pad a String with a specified String.
*
* Pad to a size of <code>size</code>.
*
* <pre>
* StringUtils.leftPad(null, *, *) = null
* StringUtils.leftPad("", 3, "z") = "zzz"
* StringUtils.leftPad("bat", 3, "yz") = "bat"
* StringUtils.leftPad("bat", 5, "yz") = "yzbat"
* StringUtils.leftPad("bat", 8, "yz") = "yzyzybat"
* StringUtils.leftPad("bat", 1, "yz") = "bat"
* StringUtils.leftPad("bat", -1, "yz") = "bat"
* StringUtils.leftPad("bat", 5, null) = " bat"
* StringUtils.leftPad("bat", 5, "") = " bat"
* </pre>
*
* @param str
* the String to pad out, may be null
* @param size
* the size to pad to
* @param padStr
* the String to pad with, null or empty treated as single space
* @return left padded String or original String if no padding is necessary, <code>null</code> if
* null String input
*/
public static String padLeft(String str, int size, String padStr)
{
if (str == null)
{
return null;
}
if (isEmpty(padStr))
{
padStr = " ";
}
int padLen = padStr.length();
int strLen = str.length();
int pads = size - strLen;
if (pads <= 0)
{
return str; // returns original String when possible
}
if (padLen == 1 && pads <= PAD_LIMIT)
{
return padLeft(str, size, padStr.charAt(0));
}
if (pads == padLen)
{
return padStr.concat(str);
}
else if (pads < padLen)
{
return padStr.substring(0, pads).concat(str);
}
else
{
char[] padding = new char[pads];
char[] padChars = padStr.toCharArray();
for (int i = 0; i < pads; i++)
{
padding[i] = padChars[i % padLen];
}
return new String(padding).concat(str);
}
}
/**
* Checks if a String is empty ("") or null.
*
* <pre>
* StringUtils.isEmpty(null) = true
* StringUtils.isEmpty("") = true
* StringUtils.isEmpty(" ") = false
* StringUtils.isEmpty("bob") = false
* StringUtils.isEmpty(" bob ") = false
* </pre>
*
* NOTE: This method changed in Lang version 2.0. It no longer trims the String. That
* functionality is available in isBlank().
*
* @param str
* the String to check, may be null
* @return <code>true</code> if the String is empty or null
*/
public static boolean isEmpty(String str)
{
return str == null || str.length() == 0;
}
public static String tlvLength(StringBuilder buffy)
{
return padLeft(Integer.toHexString(buffy.length()), 4, '0');
}
public static boolean getBibtexKey(Field thatField)
{
final bibtex_key keyAnnotation = thatField.getAnnotation(bibtex_key.class);
if (keyAnnotation == null)
return false;
else
return true;
}
public static String getJavaGenericParametersStringRecursive(ParameterizedType pType)
{
StringBuilder result = new StringBuilder();
Type[] ta = pType.getActualTypeArguments();
result.append('<');
for (int i = 0; i < ta.length; i++)
{
if ((ta[i] instanceof Class<?>))
{
if (i == 0)
result.append(inferJavaType((Class<?>) ta[i]));
else
result.append(", " + inferJavaType((Class<?>) ta[i]));
}
else
{
try
{
ParameterizedType pT = (ParameterizedType) ta[i];
Class<?> rT = (Class<?>) pT.getRawType();
if (i == 0)
result.append(inferJavaType(rT) + getJavaGenericParametersStringRecursive(pT));
else
result.append(", " + inferJavaType(rT) + getJavaGenericParametersStringRecursive(pT));
}
catch(Exception ex)
{
if (i == 0)
result.append("?");
else
result.append(", " + "?");
}
}
}
result.append('>');
return result.toString();
}
public static ArrayList<Class> getJavaGenericDependenciesRecursive(ParameterizedType pType)
{
ArrayList<Class> result = new ArrayList<Class>();
Type[] ta = pType.getActualTypeArguments();
for (int i = 0; i < ta.length; i++)
{
if ((ta[i] instanceof Class<?>))
{
result.add((Class<?>) ta[i]);
}
else
{
try
{
ParameterizedType pT = (ParameterizedType) ta[i];
Class<?> rT = (Class<?>) pT.getRawType();
result.add(rT);
getJavaGenericDependenciesRecursive(pT);
}catch(Exception ex)
{
}
}
}
return result;
}
public static String getJavaGenericParametersString(Field field)
{
String result;
if (isParameterizedType(field))
{
result = getJavaGenericParametersStringRecursive((ParameterizedType) field.getGenericType());
return result;
}
else
return "";
}
public static ArrayList<Class> getJavaGenericDependencies(Field field)
{
ArrayList<Class> result;
if (isParameterizedType(field))
{
result = getJavaGenericDependenciesRecursive((ParameterizedType) field.getGenericType());
return result;
}
else
return null;
}
/**
* Utility function to translate Java type to CSharp type
*
* @param fieldType
* @return
* @throws DotNetTranslationException
*/
public static String inferJavaType(Class<?> fieldType)
{
String result = null;
if (int.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_INTEGER;
}
else if (float.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_FLOAT;
}
else if (double.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_DOUBLE;
}
else if (byte.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_BYTE;
}
else if (char.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_CHAR;
}
else if (boolean.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_BOOLEAN;
}
else if (long.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_LONG;
}
else if (short.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_SHORT;
}
else if (String.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_STRING;
}
else if (StringBuilder.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_STRING_BUILDER;
}
else if (URL.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_URL;
}
else if (ParsedURL.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_PARSED_URL;
}
else if (ScalarType.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_SCALAR_TYPE;
}
else if (Date.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_DATE;
}
else if (ArrayList.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_ARRAYLIST;
}
else if (HashMap.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_HASHMAP;
}
else if (HashMapArrayList.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_HASHMAPARRAYLIST;
}
else if (Scope.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_SCOPE;
}
else if (Class.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_CLASS;
}
else if (Field.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_FIELD;
}
else if (ByteBuffer.class == fieldType)
{
result = CrossLanguageTypeConstants.JAVA_BINARY_DATA;
}
else
{
// Assume the field is custom object
result = fieldType.getSimpleName();
}
return result;
}
/**
* remove HTML tag from input text. note that this does not handle nested tags with the same name
* correctly now (case like <tag><tag></tag></tag>)
*/
public static String removeHtmlTag(String input, String tagName, boolean keepInnerText)
{
String p1 = String.format("<%s(\\s+[A-Za-z0-9:_-]+=\"[^\"\\\r\n]*(?:\\.[^\"\\\r\n]*)*\")*\\s*/>", tagName);
String p2 = String.format("<%s(\\s+[A-Za-z0-9:_-]+=\"[^\"\\\r\n]*(?:\\.[^\"\\\r\n]*)*\")*\\s*>(.*?)</%s>", tagName, tagName);
input = input.replaceAll(p1, "");
input = input.replaceAll(p2, keepInnerText ? "$2" : "");
return input;
}
public static boolean isEnumCollection(Field f) {
if(representAsCollection(f))
{
ArrayList<Class<?>> classes = XMLTools.getGenericParameters(f);
if(classes.isEmpty())
{
return false;
}else{
return classes.get(0).isEnum();
}
}else{
return false;
}
}
}