/******************************************************************************* * Copyright (c) 2008 BEA Systems, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * wharley@bea.com - initial API and implementation * *******************************************************************************/ package org.eclipse.jdt.compiler.apt.tests.processors.base; import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementScanner6; import javax.xml.parsers.DocumentBuilderFactory; 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.w3c.dom.Element; import org.w3c.dom.Node; /** * Generate an XML representation of a language model. * Changes to this class will generally require changes to * the XMLComparer class (which compares documents generated * by this class) and possibly to the reference models of * various tests. * * @since 3.4 */ public class XMLConverter extends ElementScanner6<Void, Node> implements IXMLNames { private final Document _doc; private XMLConverter(Document doc) { _doc = doc; } /** * Convert an XML DOM document to a canonical string representation */ public static String xmlToString(Document model) { StringWriter s = new StringWriter(); DOMSource domSource = new DOMSource(model); StreamResult streamResult = new StreamResult(s); TransformerFactory tf = TransformerFactory.newInstance(); Transformer serializer; try { serializer = tf.newTransformer(); serializer.setOutputProperty(OutputKeys.INDENT, "yes"); serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1"); serializer.transform(domSource, streamResult); } catch (Exception e) { e.printStackTrace(new PrintWriter(s)); } return s.toString(); } /** * Convert an XML DOM document to a string representation suitable for paste * into a processor written in Java. */ // derived from org.eclipse.jdt.core.tests.util.Util#displayString public static String xmlToCutAndPasteString(Document model, int indent, boolean shift) { String modelAsString = xmlToString(model); int length = modelAsString.length(); StringBuffer buffer = new StringBuffer(length); java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(modelAsString, "\n\r", true); for (int i = 0; i < indent; i++) buffer.append("\t"); if (shift) indent++; buffer.append("\""); while (tokenizer.hasMoreTokens()){ String token = tokenizer.nextToken(); if (token.equals("\n")) { buffer.append("\\n"); if (tokenizer.hasMoreTokens()) { buffer.append("\" + \n"); for (int i = 0; i < indent; i++) buffer.append("\t"); buffer.append("\""); } continue; } StringBuffer tokenBuffer = new StringBuffer(); for (int i = 0; i < token.length(); i++){ char c = token.charAt(i); switch (c) { case '\r' : break; case '\n' : tokenBuffer.append("\\n"); break; case '\b' : tokenBuffer.append("\\b"); break; case '\t' : tokenBuffer.append("\t"); break; case '\f' : tokenBuffer.append("\\f"); break; case '\"' : tokenBuffer.append("\\\""); break; case '\'' : tokenBuffer.append("\\'"); break; case '\\' : tokenBuffer.append("\\\\"); break; default : tokenBuffer.append(c); } } buffer.append(tokenBuffer.toString()); } buffer.append("\""); return buffer.toString(); } /** * Recursively convert a collection of language elements (declarations) into an XML representation. * @param declarations the collection of language elements to convert * @return an XML document whose root node is named <model>. * @throws ParserConfigurationException */ public static Document convertModel(Iterable<? extends javax.lang.model.element.Element> declarations) throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Document model = factory.newDocumentBuilder().newDocument(); org.w3c.dom.Element modelNode = model.createElement(MODEL_TAG); XMLConverter converter = new XMLConverter(model); converter.scan(declarations, modelNode); model.appendChild(modelNode); return model; } /* * (non-Javadoc) * * @see javax.lang.model.util.ElementScanner6#visitExecutable(javax.lang.model.element.ExecutableElement, * java.lang.Object) */ @Override public Void visitExecutable(ExecutableElement e, Node target) { Element executableNode = _doc.createElement(EXECUTABLE_ELEMENT_TAG); executableNode.setAttribute(KIND_TAG, e.getKind().name()); executableNode.setAttribute(SNAME_TAG, e.getSimpleName().toString()); convertAnnotationMirrors(e, executableNode); target.appendChild(executableNode); // scan the method's parameters return super.visitExecutable(e, executableNode); } /* * (non-Javadoc) * * @see javax.lang.model.util.ElementScanner6#visitPackage(javax.lang.model.element.PackageElement, * java.lang.Object) */ @Override public Void visitPackage(PackageElement e, Node target) { // TODO not yet implemented return super.visitPackage(e, target); } /* * (non-Javadoc) * * @see javax.lang.model.util.ElementScanner6#visitType(javax.lang.model.element.TypeElement, * java.lang.Object) */ @Override public Void visitType(TypeElement e, Node target) { Element typeNode = _doc.createElement(TYPE_ELEMENT_TAG); typeNode.setAttribute(KIND_TAG, e.getKind().name()); typeNode.setAttribute(SNAME_TAG, e.getSimpleName().toString()); typeNode.setAttribute(QNAME_TAG, e.getQualifiedName().toString()); convertSuperclass(e, typeNode); convertInterfaces(e, typeNode); convertAnnotationMirrors(e, typeNode); target.appendChild(typeNode); // Scan the type's subtypes, fields, and methods return super.visitType(e, typeNode); } /* * (non-Javadoc) * * @see javax.lang.model.util.ElementScanner6#visitTypeParameter(javax.lang.model.element.TypeParameterElement, * java.lang.Object) */ @Override public Void visitTypeParameter(TypeParameterElement e, Node target) { // TODO not yet implemented return super.visitTypeParameter(e, target); } /* * (non-Javadoc) * * @see javax.lang.model.util.ElementScanner6#visitVariable(javax.lang.model.element.VariableElement, * java.lang.Object) */ @Override public Void visitVariable(VariableElement e, Node target) { Element variableNode = _doc.createElement(VARIABLE_ELEMENT_TAG); variableNode.setAttribute(KIND_TAG, e.getKind().name()); variableNode.setAttribute(SNAME_TAG, e.getSimpleName().toString()); // TODO: the spec does not restrict the toString() implementation variableNode.setAttribute(TYPE_TAG, e.asType().toString()); convertAnnotationMirrors(e, variableNode); target.appendChild(variableNode); // Variables do not enclose any elements, so no need to call super. return null; } private void convertAnnotationMirrors(javax.lang.model.element.Element e, Node target) { List<? extends AnnotationMirror> mirrors = e.getAnnotationMirrors(); if (mirrors != null && !mirrors.isEmpty()) { Element annotationsNode = _doc.createElement(ANNOTATIONS_TAG); for (AnnotationMirror am : mirrors) { convertAnnotationMirror(am, annotationsNode); } target.appendChild(annotationsNode); } } /** * Scan an annotation instance in the model and represent it in XML, including all its explicit * values (but not any default values). * * @param am * the annotation mirror to be converted * @param target * the <annotations> XML node to which a new <annotation> node will be * added */ private void convertAnnotationMirror(AnnotationMirror am, Node target) { javax.lang.model.element.Element annoElement = am.getAnnotationType().asElement(); if (annoElement == null) { return; } Element annoNode = _doc.createElement(ANNOTATION_TAG); String sname = am.getAnnotationType().asElement().getSimpleName().toString(); annoNode.setAttribute(SNAME_TAG, sname); Map<? extends ExecutableElement, ? extends AnnotationValue> values = am.getElementValues(); if (values.size() > 0) { Element valuesNode = _doc.createElement(ANNOTATION_VALUES_TAG); annoNode.appendChild(valuesNode); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : values .entrySet()) { AnnotationValue valueHolder = entry.getValue(); if (valueHolder != null) { Object value = valueHolder.getValue(); Element valueNode = _doc.createElement(ANNOTATION_VALUE_TAG); valueNode.setAttribute(MEMBER_TAG, entry.getKey().getSimpleName().toString()); valueNode.setAttribute(TYPE_TAG, value.getClass().getName()); valueNode.setAttribute(VALUE_TAG, value.toString()); valuesNode.appendChild(valueNode); } } } target.appendChild(annoNode); } /** * Scan a type for its extended and implemented interfaces and represent them * in XML. * @param target the node representing the type; an <interfaces> node * will be added as a child of this node, if any interfaces are found. */ private void convertInterfaces(TypeElement e, Node target) { List<? extends TypeMirror> interfaces = e.getInterfaces(); if (interfaces != null && !interfaces.isEmpty()) { Element interfacesNode = _doc.createElement(INTERFACES_TAG); for (TypeMirror intfc : interfaces) { convertTypeMirror(intfc, interfacesNode); } target.appendChild(interfacesNode); } } /** * Create a node representing a class declaration's superclass * @param tmSuper * @param target */ private void convertSuperclass(TypeElement e, Node target) { TypeMirror tmSuper = e.getSuperclass(); if (null != tmSuper) { Element node = _doc.createElement(SUPERCLASS_TAG); convertTypeMirror(tmSuper, node); target.appendChild(node); } } /** * Represent an arbitrary TypeMirror in XML, and append it as a child to * the specified parent node. * * Note this is problematic, because TypeMirror has no well-specified ways * to canonicalize an arbitrary (and possibly erroneous) type. * * @param tm must be non-null * @param target the parent XML node, to which this new node will be appended */ private void convertTypeMirror(TypeMirror tm, Node target) { Element n = _doc.createElement(TYPE_MIRROR_TAG); n.setAttribute(KIND_TAG, tm.getKind().name()); n.setAttribute(TO_STRING_TAG, tm.toString()); // TODO: potentially walk type-variables here target.appendChild(n); } }