| /* |
| * Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package org.jvnet.hk2.config.generator; |
| |
| import com.sun.codemodel.*; |
| import com.sun.enterprise.tools.apt.ContractFinder; |
| import org.jvnet.hk2.annotations.InhabitantAnnotation; |
| import org.jvnet.hk2.annotations.Service; |
| import org.jvnet.hk2.config.*; |
| import org.jvnet.hk2.config.Element; |
| |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.Filer; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.*; |
| import javax.lang.model.type.*; |
| import javax.lang.model.util.SimpleElementVisitor6; |
| import javax.lang.model.util.SimpleTypeVisitor6; |
| import javax.lang.model.util.Types; |
| import javax.tools.Diagnostic; |
| import javax.tools.StandardLocation; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.Writer; |
| import java.util.*; |
| |
| import static javax.tools.StandardLocation.CLASS_PATH; |
| import static javax.tools.StandardLocation.SOURCE_PATH; |
| |
| /** |
| * Generates {@link ConfigInjector} implementations for {@link Configured} objects |
| * and {@link ConfigBeanProxy} subtypes. |
| * |
| * @author Kohsuke Kawaguchi |
| */ |
| |
| public class ConfigInjectorGenerator extends AbstractProcessor { |
| |
| private JCodeModel cm; |
| |
| private TypeMath math; |
| /** |
| * Reference to the {@link ConfigBeanProxy} type. |
| */ |
| private TypeElement configBeanProxy; |
| |
| private final GeneratorVisitor visitor = new GeneratorVisitor(); |
| |
| public ConfigInjectorGenerator() { |
| } |
| |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latest(); // to avoid version warnings |
| } |
| |
| @Override |
| public Set<String> getSupportedAnnotationTypes() { |
| return Collections.singleton(Configured.class.getName()); |
| } |
| |
| @Override |
| public synchronized void init(ProcessingEnvironment processingEnv) { |
| super.init(processingEnv); |
| this.math = new TypeMath(processingEnv); |
| configBeanProxy = processingEnv.getElementUtils().getTypeElement(ConfigBeanProxy.class.getName()); |
| } |
| |
| @Override |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| cm = new JCodeModel(); |
| |
| for (TypeElement annotation : annotations) { |
| for(javax.lang.model.element.Element d : roundEnv.getElementsAnnotatedWith(annotation)) { |
| d.accept(visitor, null); |
| } |
| |
| try { |
| cm.build(new FilerCodeWriter(processingEnv.getFiler())); |
| } catch (IOException e) { |
| throw new Error(e); |
| } |
| } |
| return true; |
| } |
| |
| |
| private class GeneratorVisitor extends SimpleElementVisitor6<Void, Void> { |
| |
| @Override |
| public Void visitType(TypeElement element, Void aVoid) { |
| switch (element.getKind()) { |
| /** |
| * For each {@link ConfigBeanProxy} annotated with {@link Configured}. |
| */ |
| case INTERFACE: { |
| try { |
| if(!isSubType(element,configBeanProxy)) { |
| printError(element.getQualifiedName() + " has @Configured but doesn't extend ConfigBeanProxy", element); |
| } else |
| new ClassGenerator(element,true).generate(); |
| } catch (JClassAlreadyExistsException ex) { |
| printError(ex.toString(), element); |
| } |
| break; |
| } |
| /** |
| * For each class annotated with {@link Configured}. |
| */ |
| case CLASS: { |
| try { |
| new ClassGenerator(element,false).generate(); |
| } catch (JClassAlreadyExistsException ex) { |
| printError(ex.toString(), element); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| return null; |
| } |
| } |
| |
| private boolean isSubType(TypeElement subType, TypeElement baseType) { |
| Types types = processingEnv.getTypeUtils(); |
| return types.isSubtype(types.getDeclaredType(subType), types.getDeclaredType(baseType)); |
| } |
| |
| private static void addToMetadata(TreeMap<String,List<String>> metadata, String key, String value) { |
| List<String> inner = metadata.get(key); |
| if (inner == null) { |
| inner = new LinkedList<String>(); |
| |
| metadata.put(key, inner); |
| } |
| |
| inner.add(value); |
| } |
| |
| /** |
| * @return the map as "key=value1,key=value2,...." |
| */ |
| private static String toCommaSeparatedString(TreeMap<String,List<String>> metadata) { |
| StringBuilder buf = new StringBuilder(); |
| for (Map.Entry<String, List<String>> e : metadata.entrySet()) { |
| for (String v : e.getValue()) { |
| if (buf.length() > 0) { |
| buf.append(','); |
| } |
| buf.append(e.getKey()).append('=').append(v); |
| } |
| } |
| return buf.toString(); |
| } |
| |
| /*package*/ class ClassGenerator { |
| final TypeElement clz; |
| final JDefinedClass injector; |
| final JClass targetType; |
| final JAnnotationUse service; |
| final JMethod injectMethod,injectAttributeMethod,injectElementMethod; |
| final TreeMap<String,List<String>> metadata = new TreeMap<String,List<String>>(); |
| /** |
| * Key property that has {@link Element#key()} or {@link Attribute#key()} |
| */ |
| private Property key=null; |
| |
| /** |
| * If true, generate a ConfigInjector that extends from {@link NoopConfigInjector}. |
| * This is used as the metadata place holder for {@link ConfigBeanProxy}s. |
| * <p> |
| * If false, generate a real {@link ConfigInjector}. |
| * |
| * <p> |
| * See the test-config test module for this difference in action. |
| */ |
| private final boolean generateNoopConfigInjector; |
| |
| |
| public ClassGenerator(TypeElement clz, boolean generateNoopConfigInjector) throws JClassAlreadyExistsException { |
| this.clz = clz; |
| this.generateNoopConfigInjector = generateNoopConfigInjector; |
| Configured c = clz.getAnnotation(Configured.class); |
| |
| String name = clz.getQualifiedName().toString(); |
| targetType = cm.ref(name); |
| |
| // [RESULT] |
| // @Service(name='...') |
| // @InjectionTarget(target) |
| // public class XYZInjector extends ConfigInjector<XYZ> |
| injector = cm._class(name+"Injector"); |
| String elementName = c.name(); |
| if(c.local()) { |
| if(elementName.length()>0) { |
| printError("@Configured.local and @Configured.name is mutually exclusive", clz); |
| elementName = ""; // error recovery |
| } |
| } else { |
| if(elementName.length()==0) // infer default |
| elementName = Dom.convertName(clz.getSimpleName().toString()); |
| } |
| |
| service = injector.annotate(Service.class).param("name",elementName); |
| injector.annotate(InjectionTarget.class).param("value",targetType); |
| |
| Set<String> targetHabitats = new HashSet<String>(); |
| for (AnnotationMirror am : clz.getAnnotationMirrors()) { |
| InhabitantAnnotation ia = am.getAnnotationType().asElement().getAnnotation(InhabitantAnnotation.class); |
| if (ia != null) { |
| targetHabitats.add(ia.value()); |
| } |
| } |
| |
| if(generateNoopConfigInjector) { |
| injector._extends(cm.ref(NoopConfigInjector.class)); |
| injectAttributeMethod = null; |
| injectMethod = null; |
| injectElementMethod = null; |
| } else { |
| injector._extends(cm.ref(ConfigInjector.class).narrow(targetType)); |
| |
| // [RESULT] |
| // public void inject(Dom dom, Property target) { ... } |
| injectMethod = injector.method(JMod.PUBLIC, void.class, "inject"); |
| injectMethod.param(Dom.class, "dom"); |
| injectMethod.param(targetType, "target"); |
| injectMethod.body(); |
| |
| injectAttributeMethod = injector.method(JMod.PUBLIC,void.class,"injectAttribute"); |
| addReinjectionParam(injectAttributeMethod); |
| |
| injectElementMethod = injector.method(JMod.PUBLIC,void.class,"injectElement"); |
| addReinjectionParam(injectElementMethod); |
| } |
| |
| addToMetadata(metadata, ConfigMetadata.TARGET,name); |
| |
| // locate additional contracts for the target. |
| for (TypeElement t : ContractFinder.find(clz)) |
| addToMetadata(metadata, ConfigMetadata.TARGET_CONTRACTS,t.getQualifiedName().toString()); |
| if (targetHabitats.size() > 0) { |
| StringBuilder sb = new StringBuilder(); |
| for (String h : targetHabitats) { |
| sb.append(h).append(";"); |
| } |
| addToMetadata(metadata, ConfigMetadata.TARGET_HABITATS, sb.toString()); |
| } |
| } |
| |
| private void addReinjectionParam(JMethod method) { |
| method.param(Dom.class,"dom"); |
| method.param(String.class,"name"); |
| method.param(targetType, "target"); |
| } |
| |
| /** |
| * Visits all annotated fields/methods and |
| * generates the body of the {@link ConfigInjector#inject(Dom, Object)} code. |
| */ |
| public void generate() { |
| Stack<TypeElement> q = new Stack<TypeElement>(); |
| Set<TypeElement> visited = new HashSet<TypeElement>(); |
| q.push(clz); |
| |
| while(!q.isEmpty()) { |
| TypeElement t = q.pop(); |
| if(!visited.add(t)) continue; // been here already |
| for (javax.lang.model.element.Element child : t.getEnclosedElements()) { |
| switch (child.getKind()) { |
| case FIELD: { // ElementFilter.fieldsIn |
| generate(new Property.Field((VariableElement) child)); |
| break; |
| } |
| case METHOD: { |
| generate(new Property.Method((ExecutableElement) child)); |
| } |
| default: |
| continue; |
| } |
| } |
| |
| for (TypeMirror it : clz.getInterfaces()) |
| q.add((TypeElement) ((DeclaredType)it).asElement()); |
| |
| if (ElementKind.CLASS.equals(t.getKind())) { |
| TypeMirror sc = t.getSuperclass(); |
| if(!TypeKind.NONE.equals(sc.getKind())) |
| q.add((TypeElement) ((DeclaredType) sc).asElement()); |
| } |
| } |
| |
| service.param("metadata", toCommaSeparatedString(metadata)); |
| } |
| |
| private void generate(Property p) { |
| Attribute a = p.getAnnotation(Attribute.class); |
| Element e = p.getAnnotation(Element.class); |
| |
| if(a!=null) { |
| new AttributeMethodGenerator(p,a).generate(); |
| if(e!=null) |
| printError("Cannot have both @Element and @Attribute at the same time", p.decl()); |
| } else { |
| if(e!=null) |
| new ElementMethodGenerator(p,e).generate(); |
| } |
| |
| // Updates #key with error check. |
| if(p.isKey()) { |
| if(key!=null) { |
| printError("Multiple key properties", p.decl()); |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Another one is at here", key.decl()); |
| } |
| key = p; |
| } |
| } |
| |
| /** |
| * Generates a single injection method, which inject |
| * value(s) of a particular element/attribute name. |
| */ |
| private abstract class MethodGenerator { |
| final JBlock body; |
| final JVar $dom; |
| final JVar $target; |
| /** |
| * Element name or attribute name. |
| * A special case is "*" for elements that indicate substitute-by-type. |
| */ |
| final String xmlName; |
| final Property p; |
| private int iota=1; |
| /** |
| * Erasure of {@code p.type()} |
| */ |
| final TypeMirror erasure; |
| /** |
| * If this is a multi-value property, the packer knows how to create a collection value. |
| */ |
| final Packer packer; |
| /** |
| * The type of individual item. If this is a multi-value property, this is a type of the collection |
| * item, otherwise the same as {@link #erasure}. |
| */ |
| final TypeMirror itemType; |
| /** |
| * Converter for {@link #itemType}. |
| */ |
| /*semi-final*/ Converter conv; |
| |
| MethodGenerator(String methodNamePrefix, JMethod reinjectionMethod, Property p, String xmlName) { |
| this.xmlName = p.inferName(xmlName); |
| this.p = p; |
| |
| if(generateNoopConfigInjector) { |
| body = null; |
| $dom = null; |
| $target = null; |
| } else { |
| JMethod m = injector.method(JMod.PUBLIC,void.class, methodNamePrefix+p.seedName()); |
| $dom = m.param(Dom.class,"dom"); |
| $target = m.param(targetType,"target"); |
| body = m.body(); |
| |
| injectMethod.body().invoke(m).arg($dom).arg($target); |
| |
| reinjectionMethod.body()._if(JExpr.lit(this.xmlName).invoke("equals").arg(JExpr.ref("name"))) |
| ._then().invoke(m).arg($dom).arg($target); |
| } |
| |
| erasure = erasure(p.type()); |
| packer = createPacker(p.type(),erasure); |
| |
| itemType = packer==null ? erasure : erasure(packer.itemType()); |
| } |
| |
| private void assign(JExpression rhs) { |
| p.assign($target,body,rhs); |
| } |
| |
| /** |
| * Returns '@xmlName' for attributes and '<xmlName>' for elements. |
| */ |
| protected abstract String xmlTokenName(); |
| |
| protected void generate() { |
| conv = createConverter(itemType); |
| conv.addMetadata(xmlTokenName(),itemType); |
| |
| if(!isVariableExpansion() && toJtype.visit(itemType,null)!=cm.ref(String.class)) |
| printError("variableExpansion=false is only allowed on String", p.decl()); |
| |
| if(!generateNoopConfigInjector) { |
| JVar value = var( |
| packer!=null ? cm.ref(List.class).narrow(conv.sourceType()) : conv.sourceType(),getXmlValue()); |
| |
| if(!isRequired()) |
| body._if(value.eq(JExpr._null()))._then()._return(); |
| |
| if(packer!=null) |
| handleMultiValue(value); |
| else |
| assign(conv.as(value,itemType)); |
| } |
| |
| if(p.isKey()) |
| addKey(); |
| } |
| |
| /** |
| * Returns true if the property must have a value, or if it's optional. |
| */ |
| protected abstract boolean isRequired(); |
| |
| /** |
| * Returns true if the property is a reference to another element |
| */ |
| protected abstract boolean isReference(); |
| |
| /** |
| * Returns true if the property is a a subject of variable expansion. |
| */ |
| protected abstract boolean isVariableExpansion(); |
| |
| /** |
| * Return true if this property is {@code @FromElement("*")}, |
| * which means finding a match by types |
| */ |
| protected abstract boolean isAllElementMatch(); |
| |
| /** |
| * Obtains the source value(s) from {@link Dom}. |
| */ |
| protected abstract JExpression getXmlValue(); |
| |
| private void addKey() { |
| addToMetadata(metadata, ConfigMetadata.KEY, xmlTokenName()); |
| addToMetadata(metadata, ConfigMetadata.KEYED_AS, ((TypeElement) p.decl().getEnclosingElement()).getQualifiedName().toString()); |
| } |
| |
| /** |
| * Invokes a method on DOM by adjusting the name for plural. |
| */ |
| final JInvocation invokeDom(String methodName) { |
| if(packer!=null) methodName+='s'; |
| return $dom.invoke(methodName); |
| } |
| |
| private void handleMultiValue(JVar values) { |
| // [RESULT] |
| // List<S> values = dom.leafElements("..."); |
| // <packer init> |
| // for( S v : values ) { |
| // <packer set>(<as>(v)); |
| // } |
| // ... assign ... |
| packer.start(values.invoke("size")); |
| JForEach forEach = body.forEach(conv.sourceType(), id(), values); |
| packer.pack(forEach.body(),conv.as(forEach.var(),packer.itemType()), forEach.var()); |
| assign(packer.end()); |
| } |
| |
| /** |
| * Creates a variable |
| */ |
| protected JVar var(JType t, JExpression init) { |
| return body.decl(t,id(),init); |
| } |
| |
| protected JVar var(Class t, JExpression init) { |
| return var(cm.ref(t),init); |
| } |
| |
| /** |
| * Creates an unique id. |
| */ |
| private String id() { |
| return "v"+(iota++); |
| } |
| |
| private Packer createPacker(TypeMirror type, TypeMirror erasure) { |
| if(erasure.getKind() == TypeKind.ARRAY) { |
| // T=X[] |
| return new ArrayPacker((ArrayType)erasure); |
| } |
| |
| TypeMirror itemType = math.isCollection(type); |
| if(itemType!=null) { |
| // T=Collection[] |
| return new ListPacker(type,itemType); |
| } |
| |
| TypeMirror mapType = math.baseClassFinder.visit(type, processingEnv.getElementUtils().getTypeElement(Map.class.getName())); |
| if(mapType!=null) { |
| // T=Map<...> |
| DeclaredType d = (DeclaredType)mapType; |
| List<? extends TypeMirror> itr = d.getTypeArguments(); |
| return new MapPacker(itr.get(1)); |
| } |
| |
| return null; |
| } |
| |
| abstract class Packer { |
| abstract TypeMirror itemType(); |
| /** |
| * Starts packing. |
| */ |
| abstract void start(JExpression $valueSize); |
| /** |
| * Adds one more item to the pack. |
| */ |
| abstract void pack(JBlock block, JExpression item, JExpression sourceValue); |
| |
| /** |
| * Returns the packed value to be set. |
| */ |
| abstract JExpression end(); |
| } |
| |
| final class ArrayPacker extends Packer { |
| private JVar $array,$index; |
| private final JType arrayT; |
| private final JType componentT; |
| private final ArrayType at; |
| |
| public ArrayPacker(ArrayType t) { |
| this.at = t; |
| this.componentT = toJtype.visit(itemType(), null); |
| this.arrayT = componentT.array(); |
| } |
| |
| TypeMirror itemType() { |
| return at.getComponentType(); |
| } |
| |
| void start(JExpression $valueSize) { |
| // [RESULT] |
| // T[] x = new T[values.size()]; |
| $array = var(arrayT, JExpr.newArray(componentT, $valueSize)); |
| $index = var(int.class,JExpr.lit(0)); |
| } |
| |
| void pack(JBlock block, JExpression item, JExpression sourceValue) { |
| // [RESULT] |
| // x[i++] = <rhs>; |
| block.assign($array.component($index.incr()),item); |
| } |
| |
| JExpression end() { |
| return $array; |
| } |
| } |
| |
| final class ListPacker extends Packer { |
| private JVar $list; |
| private final JClass collectionType,itemType; |
| private final TypeMirror itemT; |
| |
| public ListPacker(TypeMirror collectionType, TypeMirror itemType) { |
| this.collectionType = toJtype.visit(collectionType, null).boxify(); |
| this.itemType = toJtype.visit(itemType, null).boxify(); |
| this.itemT = itemType; |
| } |
| |
| TypeMirror itemType() { |
| return itemT; |
| } |
| |
| void start(JExpression $valueSize) { |
| // [RESULT] |
| // T x = new ArrayList<T>(values.size()); |
| $list = var(collectionType,JExpr._new(implType()).arg($valueSize)); |
| } |
| |
| /** |
| * Figure out the concrete implementation class to be used. |
| */ |
| JType implType() { |
| if(cm.ref(Set.class).isAssignableFrom(collectionType)) |
| return cm.ref(HashSet.class).narrow(itemType); |
| return cm.ref(ArrayList.class).narrow(itemType); |
| } |
| |
| void pack(JBlock block, JExpression item, JExpression sourceValue) { |
| // [RESULT] |
| // x.add(<rhs>); |
| block.invoke($list,"add").arg(item); |
| } |
| |
| JExpression end() { |
| return $list; |
| } |
| } |
| |
| final class MapPacker extends Packer { |
| private JVar $map; |
| private final TypeMirror itemT; |
| |
| public MapPacker(TypeMirror itemType) { |
| this.itemT = itemType; |
| } |
| |
| TypeMirror itemType() { |
| return itemT; |
| } |
| |
| void start(JExpression $valueSize) { |
| // [RESULT] |
| // T x = new HashMap<T>(); |
| $map = var(Map.class,JExpr._new(cm.ref(HashMap.class)).arg($valueSize)); |
| } |
| |
| void pack(JBlock block, JExpression item, JExpression itemDom) { |
| // [RESULT] |
| // x.put(dom.getKey(),<rhs>); |
| block.invoke($map,"put").arg(itemDom.invoke("getKey")).arg(item); |
| } |
| |
| JExpression end() { |
| return $map; |
| } |
| } |
| |
| private Converter createConverter(TypeMirror itemType) { |
| try { |
| // is this a leaf value? |
| math.simpleValueConverter.visit(itemType, JExpr._null()); |
| return new LeafConverter(); |
| } catch (UnsupportedOperationException e) { |
| // nope |
| } |
| |
| // try to handle it as a reference |
| if (TypeKind.DECLARED.equals(itemType.getKind())) { |
| TypeElement decl = (TypeElement) ((DeclaredType)itemType).asElement(); |
| Configured cfg = decl.getAnnotation(Configured.class); |
| if(cfg!=null) { |
| // node value |
| if(isReference()) |
| return new ReferenceConverter(); |
| else |
| return new NodeConverter(); |
| } |
| } |
| |
| if(isAllElementMatch()) { |
| return new NodeByTypeConverter(itemType); |
| } |
| |
| printError("I don't know how to inject "+itemType+" from configuration", p.decl()); |
| return new NodeConverter(); // error recovery |
| } |
| |
| /** |
| * Encapsulates the source value representation in {@link Dom}. |
| */ |
| abstract class Converter { |
| /** |
| * Generates an expression that converts 'rhs'. |
| * |
| * @param targetType |
| * The expected type of the expression, so that the generated expression |
| * can contain cast operation if necessary. |
| */ |
| abstract JExpression as(JExpression rhs, TypeMirror targetType); |
| /** |
| * Source value type as returned by {@link Dom}. |
| */ |
| abstract JClass sourceType(); |
| |
| /** |
| * True if the XML representation of the source value is a leaf (string value) |
| * as opposed to node (an XML fragment.) |
| */ |
| abstract boolean isLeaf(); |
| |
| abstract void addMetadata(String key,TypeMirror itemType); |
| |
| protected final String makeCollectionIfNecessary(String s) { |
| if(packer!=null) return "collection:"+s; |
| else return s; |
| } |
| } |
| |
| class LeafConverter extends Converter { |
| JExpression as(JExpression rhs, TypeMirror targetType) { |
| return math.simpleValueConverter.visit(targetType, rhs); |
| } |
| JClass sourceType() { |
| return cm.ref(String.class); |
| } |
| |
| boolean isLeaf() { |
| return true; |
| } |
| |
| void addMetadata(String key,TypeMirror itemType) { |
| addToMetadata(metadata, key,makeCollectionIfNecessary("leaf")); |
| } |
| } |
| |
| class NodeConverter extends Converter { |
| JExpression as(JExpression rhs, TypeMirror targetType) { |
| return JExpr.cast(toJtype.visit(targetType, null), rhs.invoke("get")); |
| } |
| JClass sourceType() { |
| return cm.ref(Dom.class); |
| } |
| |
| boolean isLeaf() { |
| return false; |
| } |
| |
| void addMetadata(String key,TypeMirror itemType) { |
| String typeName; |
| if (itemType.getKind() == TypeKind.DECLARED) { |
| typeName=((DeclaredType)itemType).asElement().toString(); |
| } else { |
| typeName=itemType.toString(); |
| } |
| |
| |
| addToMetadata(metadata, key,makeCollectionIfNecessary(typeName)); |
| } |
| } |
| |
| class NodeByTypeConverter extends Converter { |
| final JClass sourceType; |
| |
| NodeByTypeConverter(TypeMirror sourceType) { |
| this.sourceType = toJtype.visit(sourceType, null).boxify(); |
| } |
| |
| JExpression as(JExpression rhs, TypeMirror targetType) { |
| return rhs; |
| } |
| JClass sourceType() { |
| return sourceType; |
| } |
| boolean isLeaf() { |
| return false; |
| } |
| void addMetadata(String key,TypeMirror itemType) { |
| // TODO: we need to indicate that there's open-ended match here |
| } |
| } |
| |
| class ReferenceConverter extends Converter { |
| JExpression as(JExpression rhs, TypeMirror targetType) { |
| return JExpr.invoke("reference").arg($dom).arg(rhs).arg(toJtype.visit(targetType, null).boxify().dotclass()); |
| } |
| JClass sourceType() { |
| return cm.ref(String.class); |
| } |
| |
| boolean isLeaf() { |
| return true; |
| } |
| |
| void addMetadata(String key,TypeMirror itemType) { |
| addToMetadata(metadata, key,makeCollectionIfNecessary("leaf")); |
| addToMetadata(metadata, key, "reference"); |
| } |
| } |
| } |
| |
| |
| private final class AttributeMethodGenerator extends MethodGenerator { |
| private final Attribute a; |
| |
| private AttributeMethodGenerator(Property p, Attribute a) { |
| super("attribute_", injectAttributeMethod, p, a.value()); |
| this.a = a; |
| } |
| |
| protected String xmlTokenName() { |
| return '@'+xmlName; |
| } |
| |
| protected boolean isRequired() { |
| return a.required(); |
| } |
| |
| protected boolean isReference() { |
| return a.reference(); |
| } |
| |
| protected boolean isVariableExpansion() { |
| return a.variableExpansion(); |
| } |
| |
| protected boolean isAllElementMatch() { |
| return false; |
| } |
| |
| protected boolean hasDefault() { |
| boolean noDefaultValue = |
| a.defaultValue().length() == 1 && a.defaultValue().charAt(0) == '\u0000'; |
| return (!noDefaultValue); |
| } |
| /** |
| * Generates the injector that reads an attribute and sets the value. |
| */ |
| @Override |
| protected void generate() { |
| addToMetadata(metadata, xmlTokenName(),isRequired()?"required":"optional"); |
| if (this.hasDefault()) { |
| if (a.defaultValue().indexOf(',')!=-1) { |
| addToMetadata(metadata, xmlTokenName(), '"' + "default:" + a.defaultValue() + '"'); |
| } else { |
| addToMetadata(metadata, xmlTokenName(), "default:" + a.defaultValue()); |
| } |
| } |
| String ant = ""; |
| try { |
| a.dataType(); |
| } catch(MirroredTypeException me) { //hack? |
| ant = getCanonicalTypeFrom(me); |
| } |
| if (ant.length() == 0) { //take it from the return type of method |
| Property.Method m = (Property.Method)p; // Method needn't be Property's inner class |
| String typeReturnedByMethodDecl = m.method.getReturnType().toString(); |
| addToMetadata(metadata, xmlTokenName(), "datatype:" + typeReturnedByMethodDecl); |
| } else { |
| addToMetadata(metadata, xmlTokenName(), "datatype:" + ant); |
| } |
| super.generate(); |
| } |
| |
| protected JExpression getXmlValue() { |
| if(!isVariableExpansion() && packer!=null) { |
| printError("collection attribute property is inconsistent with variableExpansion=false", p.decl()); |
| } |
| return invokeDom(isVariableExpansion()?"attribute":"rawAttribute").arg(xmlName); |
| } |
| } |
| |
| private final class ElementMethodGenerator extends MethodGenerator { |
| private final Element e; |
| private ElementMethodGenerator(Property p, Element e) { |
| super("element_", injectElementMethod, p, e.value()); |
| this.e = e; |
| } |
| |
| protected String xmlTokenName() { |
| return '<'+xmlName+'>'; |
| } |
| |
| protected JExpression getXmlValue() { |
| String name; |
| if(conv.isLeaf()) { |
| if(isVariableExpansion()) |
| name = "leafElement"; |
| else |
| name = "rawLeafElement"; |
| } else { |
| assert isVariableExpansion(); // this error is checked earlier. |
| if(xmlName.equals("*")) { |
| return invokeDom("nodeByTypeElement").arg(toJtype.visit(itemType, null).boxify().dotclass()); |
| } else |
| name = "nodeElement"; |
| } |
| |
| return invokeDom(name).arg(xmlName); |
| } |
| |
| @Override |
| protected void generate() { |
| super.generate(); |
| if (packer==null) { |
| for (AnnotationMirror am : p.decl().getAnnotationMirrors()) { |
| if (!am.toString().contains("hk2")) |
| addToMetadata(metadata, xmlTokenName(), am.toString()); |
| } |
| } |
| } |
| |
| protected boolean isRequired() { |
| return e.required(); |
| } |
| |
| protected boolean isReference() { |
| return e.reference(); |
| } |
| |
| protected boolean isVariableExpansion() { |
| return e.variableExpansion(); |
| } |
| |
| protected boolean isAllElementMatch() { |
| return e.value().equals("*"); |
| } |
| } |
| } |
| |
| private static String getCanonicalTypeFrom(MirroredTypeException me) { |
| TypeMirror tm = me.getTypeMirror(); |
| if (tm.getKind() == TypeKind.DECLARED) { |
| DeclaredType dec = (DeclaredType) tm; |
| return ((TypeElement)dec.asElement()).getQualifiedName().toString(); |
| } |
| return ""; //ok? |
| } |
| |
| private TypeMirror erasure(TypeMirror type) { |
| return processingEnv.getTypeUtils().erasure(type); |
| } |
| |
| /** |
| * Takes {@link TypeMirror} and returns the corresponding {@link JType}. |
| */ |
| final SimpleTypeVisitor6<JType,Void> toJtype = new SimpleTypeVisitor6<JType,Void>() { |
| |
| @Override |
| public JType visitPrimitive(PrimitiveType type, Void param) { |
| switch (type.getKind()) { |
| case BOOLEAN: return cm.BOOLEAN; |
| case BYTE: return cm.BYTE; |
| case CHAR: return cm.CHAR; |
| case DOUBLE: return cm.DOUBLE; |
| case FLOAT: return cm.FLOAT; |
| case INT: return cm.INT; |
| case LONG: return cm.LONG; |
| case SHORT: return cm.SHORT; |
| } |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public JType visitArray(ArrayType type, Void param) { |
| return visit(type.getComponentType(), null).array(); |
| } |
| |
| @Override |
| public JType visitDeclared(DeclaredType type, Void param) { |
| // TODO: generics support |
| return cm.ref(((TypeElement) type.asElement()).getQualifiedName().toString()); |
| } |
| |
| @Override |
| protected JType defaultAction(TypeMirror e, Void aVoid) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public JType visitNoType(NoType t, Void aVoid) { |
| return cm.VOID; |
| } |
| }; |
| |
| private void printError(String error, javax.lang.model.element.Element element) { |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, error, element); |
| } |
| |
| /** |
| * {@link CodeWriter} that generates source code to {@link Filer}. |
| * |
| * @author Kohsuke Kawaguchi |
| * |
| * Moved from jaxb-xjc to break unneeded dependency. |
| */ |
| public static final class FilerCodeWriter extends CodeWriter { |
| |
| private final Filer filer; |
| |
| public FilerCodeWriter(Filer filer) { |
| this.filer = filer; |
| } |
| |
| public OutputStream openBinary(JPackage pkg, String fileName) throws IOException { |
| StandardLocation loc; |
| if(fileName.endsWith(".java")) { |
| // Annotation Processing doesn't do the proper Unicode escaping on Java source files, |
| // so we can't rely on Filer.createSourceFile. |
| loc = SOURCE_PATH; |
| } else { |
| // put non-Java files directly to the output folder |
| loc = CLASS_PATH; |
| } |
| return filer.createResource(loc, pkg.name(), fileName).openOutputStream(); |
| } |
| |
| public Writer openSource(JPackage pkg, String fileName) throws IOException { |
| String name; |
| if(pkg.isUnnamed()) |
| name = fileName; |
| else |
| name = pkg.name()+'.'+fileName; |
| |
| name = name.substring(0,name.length()-5); // strip ".java" |
| |
| return filer.createSourceFile(name).openWriter(); |
| } |
| |
| public void close() {} |
| } |
| |
| } |