| /* |
| * $Id: $ |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.struts.annotations.taglib.apt; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.FileWriter; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Result; |
| import javax.xml.transform.Source; |
| 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.Text; |
| |
| import com.sun.mirror.apt.AnnotationProcessor; |
| import com.sun.mirror.apt.AnnotationProcessorEnvironment; |
| import com.sun.mirror.declaration.AnnotationMirror; |
| import com.sun.mirror.declaration.AnnotationTypeDeclaration; |
| import com.sun.mirror.declaration.AnnotationTypeElementDeclaration; |
| import com.sun.mirror.declaration.AnnotationValue; |
| import com.sun.mirror.declaration.Declaration; |
| import com.sun.mirror.declaration.MethodDeclaration; |
| import com.sun.mirror.declaration.TypeDeclaration; |
| |
| import freemarker.template.Configuration; |
| import freemarker.template.DefaultObjectWrapper; |
| import freemarker.template.Template; |
| |
| public class TagAnnotationProcessor implements AnnotationProcessor { |
| public static final String TAG = "org.apache.struts.annotations.StrutsTag"; |
| public static final String TAG_ATTRIBUTE = "org.apache.struts.annotations.StrutsTagAttribute"; |
| |
| private AnnotationProcessorEnvironment environment; |
| private AnnotationTypeDeclaration tagDeclaration; |
| private AnnotationTypeDeclaration tagAttributeDeclaration; |
| private Map<String, Tag> tags = new HashMap<String, Tag>(); |
| |
| public TagAnnotationProcessor(AnnotationProcessorEnvironment env) { |
| environment = env; |
| tagDeclaration = (AnnotationTypeDeclaration) environment.getTypeDeclaration(TAG); |
| tagAttributeDeclaration = (AnnotationTypeDeclaration) environment |
| .getTypeDeclaration(TAG_ATTRIBUTE); |
| } |
| |
| public void process() { |
| //make sure all paramters were set |
| checkOptions(); |
| |
| // tags |
| Collection<Declaration> tagDeclarations = environment |
| .getDeclarationsAnnotatedWith(tagDeclaration); |
| Collection<Declaration> attributesDeclarations = environment |
| .getDeclarationsAnnotatedWith(tagAttributeDeclaration); |
| |
| // find Tags |
| for (Declaration declaration : tagDeclarations) { |
| // type |
| TypeDeclaration typeDeclaration = (TypeDeclaration) declaration; |
| String typeName = typeDeclaration.getQualifiedName(); |
| HashMap<String, Object> values = getValues(typeDeclaration, tagDeclaration); |
| // create Tag and apply values found |
| Tag tag = new Tag(); |
| tag.setDescription((String) values.get("description")); |
| tag.setName((String) values.get("name")); |
| tag.setTldBodyContent((String) values.get("tldBodyContent")); |
| tag.setTldTagClass((String) values.get("tldTagClass")); |
| tag.setDeclaredType(typeName); |
| // add to map |
| tags.put(typeName, tag); |
| } |
| |
| // find Tags Attributes |
| for (Declaration declaration : attributesDeclarations) { |
| // type |
| MethodDeclaration methodDeclaration = (MethodDeclaration) declaration; |
| String typeName = methodDeclaration.getDeclaringType().getQualifiedName(); |
| HashMap<String, Object> values = getValues(methodDeclaration, tagAttributeDeclaration); |
| // create Attribute and apply values found |
| TagAttribute attribute = new TagAttribute(); |
| attribute.setDescription((String) values.get("description")); |
| String name = (String) values.get("name"); |
| if(name == null || name.length() == 0) { |
| //get name from method |
| String methodName = methodDeclaration.getSimpleName(); |
| name = String.valueOf(Character.toLowerCase(methodName.charAt(3))) + methodName.substring(4); |
| } |
| attribute.setName(name); |
| attribute.setRequired((Boolean) values.get("required")); |
| attribute.setRtexprvalue((Boolean) values.get("rtexprvalue")); |
| attribute.setDefaultValue((String) values.get("defaultValue")); |
| // add to map |
| Tag parentTag = tags.get(typeName); |
| if(parentTag != null) |
| tags.get(typeName).addTagAttribute(attribute); |
| else { |
| //an abstract or base class |
| parentTag = new Tag(); |
| parentTag.setDeclaredType(typeName); |
| parentTag.setInclude(false); |
| parentTag.addTagAttribute(attribute); |
| tags.put(typeName, parentTag); |
| } |
| } |
| |
| //we can't process the hierarchy on the first pass because |
| //apt does not garantees that the base classes will be processed |
| //before their subclasses |
| for(Map.Entry<String, Tag> entry : tags.entrySet()) { |
| processHierarchy(entry.getValue()); |
| } |
| |
| //save |
| saveAsXml(); |
| saveTemplates(); |
| } |
| |
| private void processHierarchy(Tag tag) { |
| try { |
| Class clazz = Class.forName(tag.getDeclaredType()); |
| while((clazz = clazz.getSuperclass()) != null) { |
| Tag parentTag = tags.get(clazz.getName()); |
| //copy parent annotations to this tag |
| if(parentTag != null) { |
| for(TagAttribute attribute : parentTag.getAttributes()) { |
| tag.addTagAttribute(attribute); |
| } |
| } |
| } |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private void checkOptions() { |
| if(getOption("tlibVersion") == null) |
| throw new IllegalArgumentException("'tlibVersion' is missing"); |
| if(getOption("jspVersion") == null) |
| throw new IllegalArgumentException("'jspVersion' is missing"); |
| if(getOption("shortName") == null) |
| throw new IllegalArgumentException("'shortName' is missing"); |
| if(getOption("description") == null) |
| throw new IllegalArgumentException("'description' is missing"); |
| if(getOption("displayName") == null) |
| throw new IllegalArgumentException("'displayName' is missing"); |
| if(getOption("uri") == null) |
| throw new IllegalArgumentException("'uri' is missing"); |
| if(getOption("outTemplatesDir") == null) |
| throw new IllegalArgumentException("'outTemplatesDir' is missing"); |
| if(getOption("outFile") == null) |
| throw new IllegalArgumentException("'outFile' is missing"); |
| } |
| |
| private void saveTemplates() { |
| //freemarker configuration |
| Configuration config = new Configuration(); |
| config.setClassForTemplateLoading(getClass(), ""); |
| config.setObjectWrapper(new DefaultObjectWrapper()); |
| |
| try { |
| //load template |
| Template template = config.getTemplate("tag.ftl"); |
| String rootDir = (new File(getOption("outTemplatesDir"))).getAbsolutePath(); |
| for(Tag tag : tags.values()) { |
| if(tag.isInclude()) { |
| //model |
| HashMap root = new HashMap(); |
| root.put("tag", tag); |
| |
| //save file |
| BufferedWriter writer = new BufferedWriter(new FileWriter(new File(rootDir, tag.getName() + ".html"))); |
| try { |
| template.process(root, writer); |
| } finally { |
| writer.close(); |
| } |
| } |
| } |
| } catch (Exception e) { |
| //oops we cannot throw checked exceptions |
| throw new RuntimeException(e); |
| } |
| } |
| |
| |
| private void saveAsXml() { |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| DocumentBuilder builder; |
| |
| try { |
| // create xml document |
| builder = factory.newDocumentBuilder(); |
| Document document = builder.newDocument(); |
| document.setXmlVersion("1.0"); |
| |
| // taglib |
| Element tagLib = document.createElement("taglib"); |
| document.appendChild(tagLib); |
| // tag lib attributes |
| appendTextNode(document, tagLib, "tlib-version", getOption("tlibVersion"), false); |
| appendTextNode(document, tagLib, "jsp-version", getOption("jspVersion"), false); |
| appendTextNode(document, tagLib, "short-name", getOption("shortName"), false); |
| appendTextNode(document, tagLib, "uri", getOption("uri"), false); |
| appendTextNode(document, tagLib, "display-name", getOption("displayName"), false); |
| appendTextNode(document, tagLib, "description", getOption("description"), true); |
| |
| // create tags |
| for (Map.Entry<String, Tag> entry : tags.entrySet()) { |
| Tag tag = entry.getValue(); |
| if(tag.isInclude()) |
| createElement(document, tagLib, tag); |
| } |
| |
| // save to file |
| TransformerFactory tf = TransformerFactory.newInstance(); |
| tf.setAttribute("indent-number", new Integer(2)); |
| Transformer transformer = tf.newTransformer(); |
| //if tiger would just format it :( |
| //formatting bug in tiger (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446) |
| |
| transformer.setOutputProperty(OutputKeys.INDENT, "yes"); |
| transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"); |
| transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"); |
| transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); |
| |
| Source source = new DOMSource(document); |
| Result result = new StreamResult(new OutputStreamWriter(new FileOutputStream(getOption("outFile")))); |
| transformer.transform(source, result); |
| } catch (Exception e) { |
| // oops we cannot throw checked exceptions |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private String getOption(String name) { |
| // there is a bug in the 1.5 apt implementation: |
| // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6258929 |
| // this is a hack-around |
| if (environment.getOptions().containsKey(name)) |
| return environment.getOptions().get(name); |
| |
| for (Map.Entry<String, String> entry : environment.getOptions().entrySet()) { |
| String key = entry.getKey(); |
| String[] splitted = key.split("="); |
| if (splitted[0].equals("-A" + name)) |
| return splitted[1]; |
| } |
| return null; |
| } |
| |
| private void createElement(Document doc, Element tagLibElement, Tag tag) { |
| Element tagElement = doc.createElement("tag"); |
| tagLibElement.appendChild(tagElement); |
| appendTextNode(doc, tagElement, "name", tag.getName(), false); |
| appendTextNode(doc, tagElement, "tag-class", tag.getTldTagClass(), false); |
| appendTextNode(doc, tagElement, "body-content", tag.getTldBodyContent(), false); |
| appendTextNode(doc, tagElement, "description", tag.getDescription(), true); |
| |
| // save attributes |
| for (TagAttribute attribute : tag.getAttributes()) { |
| createElement(doc, tagElement, attribute); |
| } |
| |
| } |
| |
| private void createElement(Document doc, Element tagElement, TagAttribute attribute) { |
| Element attributeElement = doc.createElement("attribute"); |
| tagElement.appendChild(attributeElement); |
| appendTextNode(doc, attributeElement, "name", attribute.getName(), false); |
| appendTextNode(doc, attributeElement, "required", String.valueOf(attribute.isRequired()), false); |
| appendTextNode(doc, |
| attributeElement, |
| "rtexprvalue", |
| String.valueOf(attribute.isRtexprvalue()), |
| false); |
| appendTextNode(doc, attributeElement, "description", attribute.getDescription(), true); |
| } |
| |
| private void appendTextNode(Document doc, Element element, String name, String text, boolean cdata) { |
| Text textNode = cdata ? doc.createCDATASection(text) : doc.createTextNode(text); |
| Element newElement = doc.createElement(name); |
| newElement.appendChild(textNode); |
| element.appendChild(newElement); |
| } |
| |
| /** |
| * Get values of annotation |
| * |
| * @param declaration |
| * @param type |
| * The type of the annotation |
| * @return name->value map of annotation values |
| */ |
| private HashMap<String, Object> getValues(Declaration declaration, AnnotationTypeDeclaration type) { |
| HashMap<String, Object> values = new HashMap<String, Object>(); |
| Collection<AnnotationMirror> annotations = declaration.getAnnotationMirrors(); |
| // iterate over the mirrors. |
| |
| for (AnnotationMirror mirror : annotations) { |
| // if the mirror in this iteration is for our note declaration... |
| if (mirror.getAnnotationType().getDeclaration().equals(type)) { |
| for(AnnotationTypeElementDeclaration annotationType : mirror.getElementValues().keySet()) { |
| Object value = mirror.getElementValues().get(annotationType).getValue(); |
| Object defaultValue = annotationType.getDefaultValue(); |
| values.put(annotationType.getSimpleName(), value != null ? value : defaultValue); |
| } |
| } |
| } |
| |
| //find default values...painful |
| for(AnnotationTypeElementDeclaration annotationType : type.getMethods()) { |
| AnnotationValue value = annotationType.getDefaultValue(); |
| if(value != null) { |
| String name = annotationType.getSimpleName(); |
| if(!values.containsKey(name)) |
| values.put(name, value.getValue()); |
| } |
| } |
| |
| return values; |
| } |
| } |