| Artifact | io.github.microsphere-projects:microsphere-lang-model (0.3.20-SNAPSHOT on release; tag 0.3.20 published) |
| Packages | io.microsphere.lang.model.element (1 type) and io.microsphere.lang.model.util (15 types) — 16 main-source types |
| Wraps | javax.lang.model — Element, TypeMirror, DeclaredType, AnnotationMirror, AnnotationValue, Elements, Types, Messager, ProcessingEnvironment |
| JDK level | No maven.compiler.* / <release> property anywhere in the reactor; the bytecode level is inherited from the external build parent microsphere-build:0.3.16. CI-tested on Temurin JDK 8 / 11 / 17 / 21 / 25 |
| Compile dependency | microsphere-java-core (microsphere-lang-model/pom.xml:24-28, version ${revision}) |
| Test-only dependencies | microsphere-jdk-tools (:44-49), microsphere-annotation-test (:52-57), plus JUnit 5, Logback, javax.ws.rs-api, jaxws-api, spring-context, spring-web |
| Used by | microsphere-annotation-processor — this module is the build-time-metadata half of that pair |
All file:line references are relative to microsphere-lang-model/src/main/java/io/microsphere/ (main sources) or microsphere-lang-model/src/test/java/io/microsphere/ (tests), read from the release branch worktree. After the first mention they shorten to lang/model/util/TypeUtils.java:60-88. References into other modules keep the module name, e.g. microsphere-java-core/src/main/java/io/microsphere/reflect/TypeUtils.java:1543.
You need this module if you write an annotation processor — a javax.annotation.processing.AbstractProcessor, a build-time metadata generator, an API-doc extractor, or anything that runs under javac and inspects source structure rather than loaded classes.
At that point in the build you do not have Class, Method, Field. You have Element, TypeMirror, ExecutableElement, AnnotationMirror and AnnotationValue, and the only ways to ask them anything are the two javax.lang.model.util services Elements and Types, obtained from ProcessingEnvironment. The raw API is verbose in four specific ways, and this module removes each:
- Kind tests are untyped and not null-safe.
element.getKind() == ElementKind.CLASSmissesENUMandRECORD, andElementKind#isClass()throws onnull.ElementUtilsgives null-safeisClass/isInterface/isField/isExecutable/isMember/isVariable/isInitializer(lang/model/util/ElementUtils.java:87-296). TypeMirrorvs.java.lang.reflect.Typecomparison does not exist. NoElements/Typesmethod answers "is thisTypeMirrorthe same type asjava.lang.String?".TypeUtils.isSameTypedoes, in four overloads (lang/model/util/TypeUtils.java:177-266).- Annotation attribute extraction is a four-level manual walk:
AnnotationMirror→getAnnotationType()→ attributeExecutableElements →getElementValues()→AnnotationValue→getValue()— and defaults are absent fromgetElementValues()unless you re-readExecutableElement.getDefaultValue().AnnotationUtils.getElementValuesdoes the whole walk and merges defaults (lang/model/util/AnnotationUtils.java:1509-1529). - Diagnostics need two channels.
Messagerwrites into the build output; aLoggerwrites into your processor's own log, and you normally want both from one message pattern.MessagerUtils.printMessagedoes exactly that (lang/model/util/MessagerUtils.java:292-309).
Do not reach for it when you already have loaded Class objects — that is io.microsphere.reflect in microsphere-java-core, which is faster and has no ProcessingEnvironment requirement. Nor at runtime in a jlink-stripped image: javax.lang.model lives in the java.compiler module and may be absent.
<dependency>
<groupId>io.github.microsphere-projects</groupId>
<artifactId>microsphere-lang-model</artifactId>
<version>0.3.20</version> <!-- newest published tag; 0.3.20-SNAPSHOT is this same line's working version -->
</dependency>| Fact | Value | Evidence (release) |
|---|---|---|
groupId |
io.github.microsphere-projects |
root pom.xml:12 |
artifactId |
microsphere-lang-model |
microsphere-lang-model/pom.xml:14 |
| Version property | ${revision} |
pom.xml:15 |
Value of ${revision} on release |
0.3.20-SNAPSHOT |
root pom.xml:54 |
| Newest published tag | 0.3.20 |
release line of the release branch |
| Build parent | microsphere-build:0.3.16 (external) |
no maven.compiler.* / <release> property in the reactor |
| Compile dependency | microsphere-java-core, ${revision} |
pom.xml:24-28 |
| Test dependencies | JUnit 5 :31-41, microsphere-jdk-tools :44-49, microsphere-annotation-test :52-57, Logback :60-64, javax.ws.rs-api :67-71, jaxws-api :74-78, spring-context :81-85, spring-web :87-91 |
same |
| CI matrix | Temurin JDK 8, 11, 17, 21, 25 |
.github/workflows/maven-build.yml:25, distribution: 'temurin' :33, -Dsurefire.useSystemClassLoader=false :43 |
Important
microsphere-jdk-tools and microsphere-annotation-test are <scope>test</scope> (pom.xml:48, :56). They do not appear on your compile classpath, and your processor will not get them transitively. You need them only to run this module's own test suite (see §10, pitfall 1).
microsphere-java-core is the only compile-scope dependency, and this module uses it heavily: io.microsphere.util.*, io.microsphere.collection.*, io.microsphere.lang.function.*, io.microsphere.reflect.*, io.microsphere.json.JSONUtils, io.microsphere.text.FormatUtils, io.microsphere.logging.*, io.microsphere.constants.SymbolConstants and io.microsphere.annotation.* are all imported from there.
| Question | Answer | Evidence |
|---|---|---|
| Release line | release branch, ${revision} = 0.3.20-SNAPSHOT, tag 0.3.20 published |
root pom.xml:54 |
@Since of all 16 types |
1.0.0 |
lang/model/util/ElementUtils.java:56, lang/model/util/TypeUtils.java:67, lang/model/util/AnnotationUtils.java:99, lang/model/element/StringAnnotationValue.java:30, lang/model/util/MessagerUtils.java:41, lang/model/util/ExecutableElementComparator.java:53 |
| Package namespace | javax.lang.model, never jakarta.lang.model |
every main source imports javax.lang.model.*, e.g. lang/model/util/ElementUtils.java:24-28; there is no jakarta.* import in src/main/java |
javax.lang.model availability |
Element/Types/Elements/ElementVisitor/AnnotationValueVisitor are JDK 6+; ElementKindVisitor6, SimpleAnnotationValueVisitor6 and AbstractElementVisitor6 — the bases this module extends — are JDK 6 and deprecated since JDK 9 in favour of the ...8/...14 variants |
lang/model/util/JSONElementVisitor.java:27-28,57, lang/model/util/JSONAnnotationValueVisitor.java:25,72, lang/model/util/ResolvableAnnotationValueVisitor.java:27,74 |
| JDK range actually tested | 8, 11, 17, 21, 25 | .github/workflows/maven-build.yml:25 |
| Spring version | irrelevant to src/main/java (no Spring import); spring-context/spring-web are test-only |
pom.xml:81-91; parent properties microsphere-java-parent/pom.xml:21-24 (microsphere-bom.version 0.3.13 :22, spring-framework.version 7.0.9 :23), BOM imports :31-37 and :40-46, profile java8-16 :54-62 with <jdk>[1.8,17)</jdk> :57 pinning Spring 5.3.39 :60 |
RECORD / BINDING_VARIABLE kinds |
matched by name, not by enum constant, so the module still compiles on JDK 8 | lang/model/util/ElementUtils.java:358-359 |
Note
Answering the package question directly: yes, this module compiles against javax.*, not jakarta.*. The JDK never renamed javax.lang.model — the Jakarta EE namespace change applied to Jakarta EE APIs, not to the Java Language Model. There is no migration path to "jakarta.lang.model", and a jakarta.annotation-based processor still inspects its elements through the javax.lang.model API this module wraps. The only Jakarta-flavoured imports anywhere in the module are test-scoped and still javax-based (pom.xml:67-78).
| Item | Value | Evidence |
|---|---|---|
| Documented line | release, ${revision} = 0.3.20-SNAPSHOT |
root pom.xml:54 |
| Newest published release | 0.3.20, tagged 2026-10-16 |
the 0.3.20 tag resolves to chore: merge main into release [skip ci], committed 2026-10-16T03:43:20Z |
| Previous release | 0.3.19, tagged 2026-08-30 |
the 0.3.19 tag, committed 2026-08-30T12:51:37Z |
| Changelog source | release-notes.md at the repository root |
The project publishes no CHANGELOG.md; the v0.3.16–v0.3.20 blocks of release-notes.md read Release notes generation failed and carry raw commit lists, so every entry below is quoted as a commit subject and checked against the release sources. |
0.3.20 adds no new API surface for this area — the 0.3.19...0.3.20 range is a hardening release. What it does change, and how far that reaches the sources this page documents:
- Nullability contracts. 8 of 16 main sources in the scope of this page declare
@Nonnull/@Nullable. No commit subject names this area specifically; the library-wide pass34e27823 Refine nullability contracts across core APIscovers it. - No other change in this range touches the area this page covers.
Note
Reading the commit list: Half of the module is annotated — AnnotatedElementJSONElementVisitor, AnnotationUtils, ConstructorUtils, ElementUtils, FieldUtils, MemberUtils, MethodUtils, TypeUtils — yet no commit subject names microsphere-lang-model, so the pass reached it without a commit of its own.
ElementUtils is an interface extending io.microsphere.util.Utils (lang/model/util/ElementUtils.java:58), so it is a namespace for static methods, not an injectable service.
| Method | true for |
Evidence |
|---|---|---|
isClass(ElementKind) |
CLASS, ENUM, RECORD (delegates to ElementKind#isClass()) |
:87-89 |
isInterface(ElementKind) |
INTERFACE, ANNOTATION_TYPE |
:118-120 |
isDeclaredType(ElementKind) |
either of the two above | :149-151 |
isField(ElementKind) |
FIELD, ENUM_CONSTANT |
:180-182 |
isExecutable(ElementKind) |
METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT (own switch, not ElementKind#isExecutable()) |
:216-227 |
isMember(ElementKind) |
isField || isExecutable |
:256-258 |
isInitializer(ElementKind) |
STATIC_INIT, INSTANCE_INIT |
:287-296 |
isVariable(ElementKind) |
ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE, EXCEPTION_PARAMETER, RESOURCE_VARIABLE, plus BINDING_VARIABLE by name |
:347-362 |
toElementKind(ElementType) maps TYPE/TYPE_USE → CLASS, otherwise ElementKind.valueOf(name), and returns OTHER for null (:389-400). Three matchesElementType overloads build on it — (ElementKind, ElementType) :430-432, (ElementKind, ElementType...) :461-472, (Element, ElementType...) :500-502 — and matchesElementKind(Element, ElementKind) :526-528 is the direct form. The remaining group is modifier/filter/signature: isPublicNonStatic :550-552, hasModifiers(Element, Modifier...) :578-589, filterElements(List, Predicate...) :622-633 (AND-combined, returns unmodifiableList), matchParameterTypes(ExecutableElement, Type...) :656-659, matchParameterTypes(List<? extends VariableElement>, Type...) :682-684 and matchParameterTypeNames(List, CharSequence...) :714-733, which compares parameter count first and then each parameter's type name.
Condensed from lang/model/util/ElementUtilsTest.java:100-136; echoMethod is TestServiceImpl#echo(String), resolved once in beforeTest (ElementUtilsTest.java:94-98):
// ElementUtilsTest.java:102-103
assertTrue(matchesElementKind(echoMethod, METHOD));
assertFalse(matchesElementKind(this.echoMethod, FIELD));
// ElementUtilsTest.java:114-117 — Integer#valueOf(String) is public *static* and must not match
methodsIn(getDeclaredMembers(testTypeElement)).forEach(method -> assertTrue(isPublicNonStatic(method)));
assertFalse(isPublicNonStatic(findMethod(getTypeElement(Integer.class), "valueOf", String.class)));
// ElementUtilsTest.java:127-129
List<VariableElement> fields = fieldsIn(getAllDeclaredMembers(testTypeElement.asType()));
assertTrue(hasModifiers(fields.get(0), PROTECTED));
// ElementUtilsTest.java:336-337 — parameter types compared by type name
assertTrue(matchParameterTypeNames(this.echoMethod.getParameters(), "java.lang.String"));
assertFalse(matchParameterTypeNames(this.echoMethod.getParameters(), "java.lang.Object"));Source: lang/model/util/ElementUtilsTest.java:100-136,335-338
TypeUtils (lang/model/util/TypeUtils.java:69) is the largest type in the module: roughly a hundred static methods in five families, most present in two flavours, one taking Element and one taking TypeMirror.
| Family | Representative methods | Evidence |
|---|---|---|
| Type-shape predicates | isSimpleType, isSameType, isArrayType, isEnumType, isClassType, isPrimitiveType, isInterfaceType, isAnnotationType, isTypeElement, isDeclaredType |
:120-628 (pairs at :120/:149, :177/:203/:235/:261, :289/:314, :340/:367, :387/:408, :428/:448, :468/:489, :509/:530, :555/:579, :604/:628) |
| Converters | ofTypeElement(Element/TypeMirror/DeclaredType), ofDeclaredType(Element/TypeMirror), ofTypeMirrors, ofTypeElements, ofDeclaredTypes |
:652-789 |
| Supertype traversal | getTypeElementOfSuperclass, getAllTypeElementsOfSuperclasses, getTypeElementsOfInterfaces, getAllTypeElements, findTypeElements, getDeclaredTypeOfSuperclass, getAllDeclaredTypes, findDeclaredTypes |
:1013-2272 (getAllTypeElements :1169, findTypeElements :1360-1365) |
ProcessingEnvironment bridges |
getTypeMirrors(env, Type...), getTypeMirror(env, Type), getTypeElement(env, Type/TypeMirror/CharSequence), getDeclaredType(env, Type/TypeMirror/CharSequence) |
:2287-2433 |
| Names and finder | toString(TypeMirror), getTypeName(TypeMirror), typeElementFinder(...) |
:2445-2505 |
The name-based comparison core is five lines (:261-266), and SIMPLE_TYPE_NAMES (:77-82) is the immutable name list this module treats as "simple" — isSimpleType is just SIMPLE_TYPE_NAMES.contains(type.toString()) (:149-151). Two TypeElement traversal functions are declared as public static Function constants at :88 and :94-97.
static boolean isSameType(TypeMirror type, CharSequence typeName) {
if (type == null && typeName == null) {
return true;
}
return Objects.equals(valueOf(type), valueOf(typeName));
}TypeUtils is used throughout the test base lang/model/util/UtilTest.java:55-92, which wraps the ProcessingEnvironment-dependent lookups so individual tests do not repeat them.
// UtilTest.java:59-69
protected TypeMirror getTypeMirror(Type type) { return TypeUtils.getTypeMirror(processingEnv, type); }
protected TypeElement getTypeElement(Type type) { return TypeUtils.getTypeElement(processingEnv, type); }
// UtilTest.java:87-88 — Element[] built from Types via TypeMirror then TypeElement
return getTypeMirrors(types).stream().map(TypeUtils::ofTypeElement).toArray(Element[]::new);
// JSONElementVisitorTest.java:237-240 — a TypeMirror back to a TypeElement, then its type parameters
TypeMirror superclass = getTypeElement(StringArrayList.class).getSuperclass();
List<? extends TypeParameterElement> typeParameters = ofTypeElement(superclass).getTypeParameters();Source: lang/model/util/UtilTest.java:55-92, lang/model/util/JSONElementVisitorTest.java:236-245
AnnotationUtils (lang/model/util/AnnotationUtils.java:101) carries four constants worth knowing: VALUE_ATTRIBUTE_NAME = "value" (:106), DEFAULT_ANNOTATION_VALUE_VISITOR — a shared ResolvableAnnotationValueVisitor (:111), EMPTY_ELEMENT_TYPE_ARRAY (:117) and WITH_DEFAULT = true (:119).
| What you want | Call | Evidence (lang/model/util/AnnotationUtils.java) |
|---|---|---|
| The first mirror of a given annotation | getAnnotation(AnnotatedConstruct, Class | CharSequence) |
:141-146, :169-175 |
| Directly present mirrors only | getAnnotations(construct[, class | name]) |
:197-202, :226-231, :256-261 |
| Mirrors including inherited ones | getAllAnnotations(Element | TypeMirror | ProcessingEnvironment, ...) |
:282-287 … :480-485 |
| Search the hierarchy for one annotation | findAnnotation(TypeMirror | Element, Class | CharSequence) |
:509-514, :539-544, :572-577, :606-612 |
| Meta-annotations (annotations on annotations) | findMetaAnnotation, isAnnotationPresent (present or meta-present) |
:640-645, :674-692, :719-724, :752-757 |
| Predicate filtering | findAnnotations (direct), findAllAnnotations (hierarchical) |
:790-807, :822-827, :831-847, :863-875, :892-897, :914-919 |
| All attribute values, defaults merged | getElementValues(...) |
:1373-1375, :1425-1427, :1464-1466, :1509-1529 |
Same as a Map<String, Object> |
getAttributesMap(...) |
:1190-1192, :1235-1237, :1272-1274, :1315-1329 |
| One attribute | getElementValue / getAttribute / getValue |
:1571-1602, :1646-1656, :1695-1697, :1738-1741, :1773-1782, :1811-1813 |
| Compare | matchesAnnotationType, matchesAnnotationTypeName, matchesAttributeMethod, matchesAttributeValue, matchesDefaultAttributeValue |
:947-952, :980-985, :1052-1054, :1081-1091, :1117-1119, :1145-1150 |
@Target of an annotation |
getElementTypes(AnnotationMirror | DeclaredType) |
:1841-1843, :1871-1875 |
The default-merging rule lives in one loop (:1518-1527): the map is built from getDeclaredMethods(annotationType), annotation.getElementValues() supplies the explicit value, and when withDefault is true and the explicit value is null, attributeMethod.getDefaultValue() is substituted. Attributes with neither an explicit value nor a default are absent from the result map.
StringAnnotationValue (lang/model/element/StringAnnotationValue.java:32-49) is a hand-written AnnotationValue implementation over a String, so a value you synthesised yourself can be fed into code that only accepts AnnotationValue. It is @Immutable (:32), stores one final String (:35,37-39), getValue() returns it (:41-44), and accept hard-wires the double dispatch (:46-49):
@Override
public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) {
return v.visitString(this.value, p);
}Because accept bypasses the JDK's own type-switch, StringAnnotationValue is accepted by any AnnotationValueVisitor, including the JDK's and both visitors below.
| Visitor | Base class | Returns | Purpose | Evidence |
|---|---|---|---|---|
ResolvableAnnotationValueVisitor |
SimpleAnnotationValueVisitor6<Object, ExecutableElement> |
Object |
turn an attribute value into a runtime Java object | lang/model/util/ResolvableAnnotationValueVisitor.java:74 |
JSONAnnotationValueVisitor |
SimpleAnnotationValueVisitor6<StringBuilder, ExecutableElement> |
the shared StringBuilder |
append "name":value JSON for an attribute |
lang/model/util/JSONAnnotationValueVisitor.java:72 |
ResolvableAnnotationValueVisitor has three constructors (:88-99) controlled by two booleans: classValuesAsString (:84) and nestedAnnotationsAsMap (:86). visitType returns either the TypeMirror string or a loaded Class (:146-149); visitEnumConstant loads the enum class and calls Enum.valueOf (:151-155); visitAnnotation builds a LinkedHashMap<String, Object> and, unless nestedAnnotationsAsMap is set or the JDK hook is missing, reflects into sun.reflect.annotation.AnnotationParser.annotationForMap (:157-174, hook resolved at :76-78); visitArray needs the attribute method to learn the component type (:176-195).
JSONAnnotationValueVisitor is a thin writer: every scalar visit is one append(jsonBuilder, getAttributeName(attributeMethod), value) call (:81-133). Three cases differ — visitType writes the type name (:135-139); visitEnumConstant writes the simple name (:141-145); visitAnnotation and visitArray recurse with a child JSONAnnotationValueVisitor over a fresh StringBuilder and then append the finished fragment through doAppend (:147-184, :191-195). visitUnknown returns the builder untouched (:186-189).
Note
The JSON output is produced through io.microsphere.json types: the visitor imports JSONUtils.append and JSONUtils.appendName (lang/model/util/JSONAnnotationValueVisitor.java:36-37) and takes its brace/bracket/comma symbols from io.microsphere.constants.SymbolConstants (:31-35). They write into io.microsphere.json's string-building layer, not into JSONObject/JSONArray objects — and note that the sibling JSONElementVisitor appends to a raw StringBuilder and imports no io.microsphere.json type at all. appendName skips blank names entirely (microsphere-java-core/src/main/java/io/microsphere/json/JSONUtils.java:500-508), which is precisely why visitArray passes null as the attribute method (JSONAnnotationValueVisitor.java:176): array elements must render namelessly. See JSON.
JSONElementVisitor (lang/model/util/JSONElementVisitor.java:57) extends ElementKindVisitor6<Boolean, StringBuilder> with the fallback set to FALSE (:59-61). Its visit* methods are final and implement a support-then-decide gate: visitPackage :63-69, visitVariable :71-77, visitExecutable :79-85, visitType :87-101, visitTypeParameter :103-109. The extension points are the protected hooks supports(Element) (:130-132, default true), the kind-specific supportsXxx family (:143-197) and doVisitPackage / doVisitTypeParameter (:206-219), and visitMembers(List, StringBuilder) (:111-119) re-enters accept for every enclosed element.
AnnotatedElementJSONElementVisitor (lang/model/util/AnnotatedElementJSONElementVisitor.java:88) is the "only elements annotated with X" filter: its constructor resolves the annotation once and fails fast on bad input (:102-112), and supports(Element) (:124-126) narrows traversal to the annotation's own @Target set obtained through getElementTypes (:111).
JSONAnnotationValueVisitorTest.java:50-56,128-134 — get the attribute map for @TestAnnotation, pick one attribute, visit it, compare the JSON text:
this.jsonBuilder = new StringBuilder();
this.visitor = new JSONAnnotationValueVisitor(jsonBuilder);
this.testAnnotationAttributes = getElementValues(testTypeElement, TestAnnotation.class);
void testVisit(String attributeName, String expectedJson) {
Map.Entry<ExecutableElement, AnnotationValue> elementValue = getElementValue(this.testAnnotationAttributes, attributeName);
StringBuilder jsonBuilder = visitor.visit(elementValue.getValue(), elementValue.getKey());
assertEquals(expectedJson, jsonBuilder.toString());
}The asserted outputs (:58-121) are the ground truth for the emitted structure:
| Attribute | Emitted JSON | Kind |
|---|---|---|
"z" |
"z":true |
boolean :59-61 |
"c" |
"c":"b" |
char, quoted :69-71 |
"d" / "f" |
"d":1.0 / "f":1.0 |
double / float :73-81 |
"type" |
"type":"io.microsphere.test.service.GenericTestService" |
Class value → type name :103-106 |
"timeUnit" |
"timeUnit":"HOURS" |
enum constant → simple name :108-111 |
"since" |
"since":{"module":"","value":"1.0.0"} |
nested annotation → object :113-116 |
"properties" |
"properties":[{"name":"key","type":"java.lang.String",…},{"name":"key2",…}] |
array of annotations :118-121 |
| unknown | (nothing) — the builder is returned unchanged | :123-126 |
Source: lang/model/util/JSONAnnotationValueVisitorTest.java:50-56,58-134
For JSONElementVisitor, JSONElementVisitorTest.java:57-137 shows the correct subclassing shape — override the JDK visitTypeAsClass / visitVariableAsField / visitExecutableAsMethod methods, not visitType / visitVariable / visitExecutable, which are final:
this.visitor = new JSONElementVisitor() {
@Override protected boolean supports(Element e) { return supported; }
@Override public Boolean visitTypeAsClass(TypeElement e, StringBuilder sb) {
jsonBuilder.append("visitTypeAsClass"); return TRUE;
}
};
assertTrue(visitor.visitType(getTypeElement(testClass), jsonBuilder)); // JSONElementVisitorTest.java:215-219
assertTrue(jsonBuilder.toString().startsWith("visitTypeAsClass"));Source: lang/model/util/JSONElementVisitorTest.java:57-137,214-219
Five types share one shape — getDeclaredX (this type only) / getAllDeclaredX (whole hierarchy) / findX / findDeclaredX(s) / filterDeclaredX(s) / isX / matchesX — and each is an interface of static methods taking TypeElement or TypeMirror.
| Type | Wraps | Key methods (evidence, all in lang/model/util/) |
|---|---|---|
ClassUtils |
TypeMirror → java.lang.Class |
getClassName(TypeMirror) ClassUtils.java:57, loadClass(TypeMirror) :78, loadClass(String) :98 |
FieldUtils |
VariableElement |
getDeclaredField(Element/TypeMirror, String) FieldUtils.java:107,135, getDeclaredFields :161,187, getAllDeclaredFields :213,239, findField :267,294, findDeclaredFields :330,368, findAllDeclaredFields :407,445, filterDeclaredFields :488, isEnumMemberField :524, isNonStaticField :554, isField :580,618, getNonStaticFields :644,674, getAllNonStaticFields :704,734, equalsFieldName :760 |
MethodUtils |
ExecutableElement (methods) |
getDeclaredMethods MethodUtils.java:86,111, getAllDeclaredMethods :136,161, findDeclaredMethods :196,231, findAllDeclaredMethods :264,300,415,451, findPublicNonStaticMethods :338,373, filterDeclaredMethods :487, isMethod :522, isPublicNonStaticMethod :546, findMethod (6 overloads) :575,604,635,664,699,728, getOverrideMethod(env, …) :764, filterMethods :804, getMethodName :841, getReturnTypeName :866, getMethodParameterTypeMirrors :896, getMethodParameterTypeNames :938, matches :967,994, matchesMethodName :1021, matchesMethod :1048,1089, getEnclosingElement :1127, methodPredicateForExcludedTypes :1152 |
ConstructorUtils |
ExecutableElement (constructors) |
getDeclaredConstructors(TypeElement/TypeMirror) ConstructorUtils.java:87,123, findConstructor :151,179, findDeclaredConstructors :220,239, filterDeclaredConstructors :278 |
MemberUtils |
any Element member |
getDeclaredMembers(TypeMirror/TypeElement) MemberUtils.java:98,122, getAllDeclaredMembers :146,170, getDeclaredMembers(..., boolean includeHierarchicalTypes) :196,222, findDeclaredMembers :250,278,388,419, findAllDeclaredMembers :310,349 |
getMethodName (MethodUtils.java:841-843) and getReturnTypeName (:866-868) are the null-tolerant accessors of this family — method == null ? null : method.getSimpleName().toString(). ClassUtils.loadClass is the bridge most likely to surprise you: it resolves through io.microsphere.util.ClassLoaderUtils, so the class must be on the processor's own classpath, not merely on the source path being compiled. ResolvableAnnotationValueVisitor.visitType and visitEnumConstant both depend on it (lang/model/util/ResolvableAnnotationValueVisitor.java:148,153), which is why resolving a Class-valued attribute of a class generated in the same compilation round fails.
Tip
TypeElement and TypeMirror overloads are not redundant. Use the TypeMirror form when you came from Element.asType() or from an annotation's DeclaredType; use the TypeElement form when you already hold one. Both terminate in the same traversal, and both return emptyList() on null.
// ElementUtilsTest.java:97 — resolve a method by name + parameter type *names*
this.echoMethod = findMethod(testTypeElement, "echo", "java.lang.String");
// ExecutableElementComparatorTest.java:41-43 — overloads resolved by parameter Type
TypeElement typeElement = getTypeElement(Integer.class);
ExecutableElement byInt = findMethod(typeElement, "valueOf", int.class);
ExecutableElement byString = findMethod(typeElement, "valueOf", String.class);
// ExecutableElementComparatorTest.java:49-53 — 1-parameter vs 3-parameter append
TypeElement sb = getTypeElement(StringBuilder.class);
findMethod(sb, "append", char[].class);
findMethod(sb, "append", char[].class, int.class, int.class);Source: lang/model/util/ElementUtilsTest.java:94-98, lang/model/util/ExecutableElementComparatorTest.java:38-54
lang/model/util/LoggerUtils.java:30-53 declares one shared Logger named "microsphere-lang-model" (:32, from io.microsphere.logging.LoggerFactory.getLogger) and five static pass-throughs, each (String format, Object... args): trace :34, debug :38, info :42, warn :46, error :50.
lang/model/util/MessagerUtils.java:43 exposes five pairs of methods — one ProcessingEnvironment overload and one Messager overload each:
| Level | Diagnostic.Kind |
MessagerUtils |
LoggerUtils level |
Evidence |
|---|---|---|---|---|
| note | NOTE |
printNote(env|messager, …) |
info |
:63-65, :103-105 |
| warning | WARNING |
printWarning(…) |
warn |
:127-129, :150-152 |
| mandatory warning | MANDATORY_WARNING |
printMandatoryWarning(…) |
warn |
:174-176, :197-199 |
| error | ERROR |
printError(…) |
error |
:221-223, :244-246 |
| any | Kind (parameter) |
printMessage(env|messager, kind, …) |
see below | :268-270, :292-309 |
The whole mapping is one method (:292-309): the pattern is formatted once via io.microsphere.text.FormatUtils.format(pattern, args), sent to the Messager, then re-sent to the logger with the raw pattern and args so the logging backend can do its own parameterisation. ERROR → error, WARNING and MANDATORY_WARNING → warn, NOTE → info, and every other kind (OTHER, and any Diagnostic.Kind added by a future JDK) → debug (:306-307).
Warning
The placeholder syntax is {}, not {0} and not String.format. The javadoc says "supports String#format syntax" and its examples use {0} (lang/model/util/MessagerUtils.java:56-61, :78-82), but the implementation calls FormatUtils.format, whose default placeholder is literally "{}" (microsphere-java-core/src/main/java/io/microsphere/text/FormatUtils.java:17, :38-39). Write "Hello, {}!" — as the tests do (lang/model/util/MessagerUtilsTest.java:55-56) — or your argument will never be substituted.
There is no null guard anywhere in the chain: printX(processingEnv, …) dereferences processingEnv.getMessager() (:64, :128, :175, :222, :269) and printMessage(messager, …) dereferences messager.printMessage(...) (:294) and kind in the switch (:295). See §10, pitfall 8.
// MessagerUtilsTest.java:50-80
this.messager = this.processingEnv.getMessager();
assertDoesNotThrow(() -> printNote(this.processingEnv, "Hello, {}!", "printNote"));
assertDoesNotThrow(() -> printNote(this.messager, "Hello, {}!", "printNote"));
assertDoesNotThrow(() -> printWarning(this.processingEnv, "Hello, {}!", "printWarning"));
assertDoesNotThrow(() -> printMandatoryWarning(this.messager, "Hello, {}!", "printMandatoryWarning"));
assertDoesNotThrow(() -> printError(this.processingEnv, "Hello, {}!", "printError"));
// the generic form, with a Kind outside the four mapped ones
assertDoesNotThrow(() -> printMessage(this.messager, OTHER, "Hello, {}!", "printMessage"));Source: lang/model/util/MessagerUtilsTest.java:47-81
lang/model/util/ExecutableElementComparator.java:55 is a Comparator<ExecutableElement> with a private constructor (:62) and one public singleton, INSTANCE (:60) — so callers always share the same stateless instance. The documented rule (:27-33) is implemented in four steps: an identity/equals shortcut if (e1.equals(e2)) return 0; (:68-70); then simple name, lexicographic via CharSequenceComparator.INSTANCE.compare(e1.getSimpleName(), e2.getSimpleName()) (:73); then parameter count ps1.size() - ps2.size() (:77-80); then per-parameter type names, lexicographic, first difference wins comparing asType().toString() (:82-89).
The result is a stable, name-first ordering that groups overloads together and, inside each overload group, sorts by arity and then by parameter type names. Use it whenever you generate ordered output from getDeclaredMethods(...) so generated metadata is reproducible across javac runs. It does not consult io.microsphere.lang.Prioritized: a grep across all 16 main types finds no reference to that interface. The Developer Guide still records the Prioritized constant-inversion warning because it affects the companion annotation-processor module.
private final ExecutableElementComparator comparator = INSTANCE; // :23
// :40-43 — same name, same arity, different parameter types → compare type names
TypeElement typeElement = getTypeElement(Integer.class);
assertEquals(int.class.getName().compareTo(String.class.getName()),
comparator.compare(findMethod(typeElement, "valueOf", int.class),
findMethod(typeElement, "valueOf", String.class)));
// :48-53 — same name, different arity → the arity difference is returned verbatim
TypeElement sb = getTypeElement(StringBuilder.class);
assertEquals(-2, comparator.compare(findMethod(sb, "append", char[].class),
findMethod(sb, "append", char[].class, int.class, int.class)));
// :59-60 — the same method reached from two different TypeElements compares 0
assertEquals(0, comparator.compare(getMethod("close"),
findMethod(getTypeElement(AutoCloseable.class), "close")));Source: lang/model/util/ExecutableElementComparatorTest.java:21-65
- Every
TypeMirroryou obtain requires a liveProcessingEnvironment.TypeUtils.getTypeElement(env, …)(lang/model/util/TypeUtils.java:2350-2381),getDeclaredType(env, …)(:2401-2433) andgetTypeMirror(env, …)(:2310) delegate toElements.getTypeElement/Types.getDeclaredType, and withnullthey returnnulloremptyList()— silently.AnnotatedElementJSONElementVisitoris the one type that refusesnulloutright, through threeassertNotNullcalls (lang/model/util/AnnotatedElementJSONElementVisitor.java:103,:104,:110). This module's tests only run becausemicrosphere-annotation-testsupplies a real processing environment:UtilTestextendsAbstractAnnotationProcessingTest(lang/model/util/UtilTest.java:20,48). Port a test to your own project without that dependency and nothing will resolve. - A
TypeElementlooked up for a class generated in the current round may benull.Elements.getTypeElementcannot see sources not yet entered, andAnnotatedElementJSONElementVisitor.java:110turns exactly that into a hard failure,"The annotation can't be found by name : …". Pass a pre-existing annotation class. isSameTypeis a string comparison, not aTypes#isSameTypecall.lang/model/util/TypeUtils.java:261-266comparesString.valueOf(typeMirror)with the expected name, so (a) it works with noTypesinstance and (b) it is not equivalence-preserving for types whosetoString()is not the canonical binary name — type-variable usages, capture types and contained types can comparefalsewherejavax.lang.model.util.Types.isSameTypereturnstrue. For declared types and primitives it is reliable. Note also thatisSameType(null, null)returnstrue(:262-264), an "equal nulls" rule that differs from every sibling predicate, all of which returnfalseonnull.isSimpleTypecomparestoString()against a name list.lang/model/util/TypeUtils.java:149-151— a declared type whose name happens to be inSIMPLE_TYPE_NAMES(:77-82) is "simple" regardless of where that class came from. Classpath-independent and classpath-surprising at the same time.getElementValuesincludes defaults;getElementValueprefers explicit values.lang/model/util/AnnotationUtils.java:1509-1529mergesgetDefaultValue()for anything absent and skips attributes with neither explicit value nor default.getElementValue(annotation, name, true)(:1571-1602) re-scans declared methods only in thewithDefault && annotationValue == nullbranch (:1587-1595) and returnsnullwhen the resolved value is stillnull(:1597-1599) — so "attribute absent" and "attribute = null" are indistinguishable.getAttributeadds a hazard: it dispatches throughannotationValue.accept(DEFAULT_ANNOTATION_VALUE_VISITOR, attributeMethod)(:1781) using the shared static visitor, so the two feature flags of §6.3 are whatever that instance was built with, not yours.- Visitor default methods are inherited from JDK classes that are deprecated and half-implemented.
JSONElementVisitorextendsElementKindVisitor6(lang/model/util/JSONElementVisitor.java:28,57) andJSONAnnotationValueVisitor/ResolvableAnnotationValueVisitorextendSimpleAnnotationValueVisitor6(lang/model/util/JSONAnnotationValueVisitor.java:25,72,lang/model/util/ResolvableAnnotationValueVisitor.java:27,74).ElementKindVisitor6is deprecated since JDK 9, andSimpleAnnotationValueVisitor6.visit()falls through tovisitUnknownfor kinds added after JDK 6. Concretely:JSONElementVisitor.visitTypecallssuper.visitType(:93), which dispatches tovisitTypeAsRecord— and neithervisitTypeAsRecordnor adoVisitTypehook is overridden here, so arecordyields no output andfalseunless you override it yourself. JSONElementVisitordoes not guardnullelements.supports(Element)returnstrueunconditionally (:130-132), so with the defaultsupportsthe callsvisitVariable(null, sb)/visitExecutable(null, sb)/visitType(null, sb)fall throughsuper.visitXxxintoElementKindVisitor6, which dereferences the element. Every*OnUnsupportedtest inlang/model/util/JSONElementVisitorTest.java:146-251passes only because the test overridessupportsto returnfalse; they passnullat:177,:197,:250. Always overridesupports.MessagerUtilsthrowsNullPointerExceptionwhenMessagerisnull.printMessagecallsmessager.printMessage(kind, message)with no guard (lang/model/util/MessagerUtils.java:294) andkindis dereferenced by theswitchat:295. TheProcessingEnvironmentoverloads are strictly worse:processingEnv.getMessager()at:64,:128,:175,:222,:269NPEs on anullenvironment. This contradicts the module's otherwise silent-degradation policy (§10.13). Guard yourself:if (processingEnv != null) printNote(processingEnv, …).ExecutableElementComparator.comparehas no null tolerance either.e1.equals(e2)atlang/model/util/ExecutableElementComparator.java:68NPEs ife1isnull;e2 == nullwith non-nulle1survives step one and then NPEs at:73one2.getSimpleName(). Filternulls out before sorting.- Nothing in this module caches
Elementlookups. The only static state is twostatic finalfields resolved once at class-initialisation inResolvableAnnotationValueVisitor—ANNOTATION_PARSER_CLASSandannotationForMapMethod(lang/model/util/ResolvableAnnotationValueVisitor.java:76-78) — plus the immutableSIMPLE_TYPE_NAMESlist (lang/model/util/TypeUtils.java:77-82) and the twoTypeElementtraversalFunctionconstants (:88,:94-97).TypeUtils.getTypeElement(env, name),MemberUtils.getDeclaredMembersand the traversal inTypeUtils.getAllTypeElements(:1169) recompute on every call. In a processor handling thousands of types, hoistTypeElementlookups into your ownMap. getTypeNamereaches into theTypeMirrorreflectively.lang/model/util/TypeUtils.java:2456-2478callsinvokeMethod(true, type, "getTypeArguments")(:2466) becauseTypeMirrordeclares no such method, then appends the rawList<TypeMirror>between<and>(:2470-2472). The bracketed form therefore appears only when the element declares type parameters and the type carries arguments, and the rendering isList.toString()of type mirrors rather than a canonical name. UsetoString(TypeMirror)(:2445-2448) when you want the plain string.ResolvableAnnotationValueVisitorcan hand you aClassthat is not loadable.visitTypeandvisitEnumConstantcallClassUtils.loadClass(:148,:153→lang/model/util/ClassUtils.java:78), andvisitArrayneeds the attribute method to derive the component type (lang/model/util/ResolvableAnnotationValueVisitor.java:177-195):(ArrayType) attributeMethod.getReturnType()(:193) is an unchecked cast, so anullattribute method NPEs before the cast matters.JSONAnnotationValueVisitoravoids this entirely by never needing it for array elements.
The library's policy is silent degradation — missing input yields null, false, an empty list or an empty map rather than an exception. The verified exceptions in this module are exactly:
| Site | Behaviour | Evidence (lang/model/util/) |
|---|---|---|
AnnotatedElementJSONElementVisitor constructor |
throws on null processingEnv, annotationClassName, or unresolvable annotation |
AnnotatedElementJSONElementVisitor.java:103,104,110 |
MessagerUtils |
NPE on null Messager / ProcessingEnvironment / Kind |
MessagerUtils.java:64,128,175,222,269,294,295 |
ExecutableElementComparator.compare |
NPE on null elements |
ExecutableElementComparator.java:68,73 |
TypeUtils.findTypeElements |
declares throws IllegalArgumentException when a filter element is null |
TypeUtils.java:1356,1360-1365, :2034,2064 |
TypeUtils.typeElementFinder |
javadoc declares IllegalArgumentException "if any parameter is invalid or if assertions fail" |
TypeUtils.java:2498-2505 |
ResolvableAnnotationValueVisitor.getComponentType |
unchecked (ArrayType) cast on a possibly-null attribute method |
ResolvableAnnotationValueVisitor.java:189-195 |
Everything else — ElementUtils, AnnotationUtils, MemberUtils, FieldUtils, MethodUtils, ConstructorUtils, ClassUtils, JSONAnnotationValueVisitor, StringAnnotationValue, LoggerUtils — degrades. The full throw-site enumeration is in the Developer Guide.
- Annotation Processor module — the framework that drives this module inside
javac;AnnotatedElementJSONElementVisitoris its natural extension point. - JDK Tools module — a test-scoped dependency here (
pom.xml:44-49) supplying the in-JVMToolProviderdriver that lets this module's tests obtain a realProcessingEnvironment. - Annotation Test module — the test-scoped harness
io.microsphere.test.annotation.processing.AbstractAnnotationProcessingTestthatUtilTestextends (lang/model/util/UtilTest.java:20,48), and the source of theio.microsphere.test.*fixtures. - microsphere-java-core README — the module index and the release-line version table.
- JSON —
JSONUtils.append/appendName, the emitting layer behindJSONAnnotationValueVisitor(lang/model/util/JSONAnnotationValueVisitor.java:36-37). - Developer Guide — Language Model — the 16-type inventory, visitor dispatch diagrams, source analysis of type resolution and
AnnotationValuetrees, and the thread-safety review. - Chinese version: 中文用户指南