de.sayayi.lib.message.adopter.AsmAnnotationAdopter Maven / Gradle / Ivy
Show all versions of message-format Show documentation
/*
* Copyright 2023 Jeroen Gremmen
*
* Licensed 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
*
* https://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 de.sayayi.lib.message.adopter;
import de.sayayi.lib.message.MessageFactory;
import de.sayayi.lib.message.MessageSupport.ConfigurableMessageSupport;
import de.sayayi.lib.message.MessageSupport.MessagePublisher;
import de.sayayi.lib.message.annotation.*;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static java.lang.Math.max;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ASM6;
import static org.objectweb.asm.Type.getDescriptor;
/**
* This class defines various methods for adopting messages and templates defined by annotations.
*
* Message annotations are analysed per class (see {@link #parseClass(InputStream)}). If there is a
* requirement to select a part of the messages provided by a class, the message support must
* be configured with an appropriate
* {@link de.sayayi.lib.message.MessageSupport.MessageFilter MessageFilter} or
* {@link de.sayayi.lib.message.MessageSupport.TemplateFilter TemplateFilter}.
*
* Even though the annotations all have class retention, 2 adopt methods
* ({@link #adopt(MessageDef)} and {@link #adopt(TemplateDef)}) are available for analyzing
* synthesized/mocked annotations.
*
* The scanned classes are analysed using the ASM library. Using this class therefore requires a
* dependency with library {@code org.ow2.asm:asm:9.+}.
*
* @author Jeroen Gremmen
* @since 0.8.0
*/
@SuppressWarnings("unused")
public class AsmAnnotationAdopter extends AbstractAnnotationAdopter
{
private static final String MESSAGE_DEFS_DESCRIPTOR = getDescriptor(MessageDefs.class);
private static final String MESSAGE_DEF_DESCRIPTOR = getDescriptor(MessageDef.class);
private static final String TEMPLATE_DEFS_DESCRIPTOR = getDescriptor(TemplateDefs.class);
private static final String TEMPLATE_DEF_DESCRIPTOR = getDescriptor(TemplateDef.class);
private static final String TEXT_DESCRIPTOR = getDescriptor(Text.class);
/** Highest supported ASM Api version (excluding experimental) */
private static final int ASM_API;
static
{
int api = ASM6; // lowest supported version
for(Field field: Opcodes.class.getDeclaredFields())
if (field.getName().matches("ASM[0-9]+"))
{
try {
api = max(field.getInt(null), api);
} catch(IllegalAccessException ignored) {
}
}
ASM_API = api;
}
/**
* Create an annotation adopter for the given {@code configurableMessageSupport}.
*
* @param configurableMessageSupport configurable message support, not {@code null}
*/
public AsmAnnotationAdopter(@NotNull ConfigurableMessageSupport configurableMessageSupport) {
super(configurableMessageSupport);
}
/**
* Create an annotation adopter for the given {@code messageFactory} and {@code publisher}.
*
* @param messageFactory message factory, not {@code null}
* @param publisher message publisher, not {@code null}
*/
public AsmAnnotationAdopter(@NotNull MessageFactory messageFactory,
@NotNull MessagePublisher publisher) {
super(messageFactory, publisher);
}
@Override
protected void parseClass(@NotNull InputStream classInputStream) throws IOException {
new ClassReader(classInputStream).accept(new MainClassVisitor(), 0);
}
@SuppressWarnings("DuplicatedCode")
private final class MainClassVisitor extends ClassVisitor
{
private MainClassVisitor() {
super(ASM_API);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
return (access & ACC_SYNTHETIC) == ACC_SYNTHETIC ? null : new MessageMethodVisitor();
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return AsmAnnotationAdopter.this.visitAnnotation(descriptor);
}
}
@SuppressWarnings("DuplicatedCode")
private AnnotationVisitor visitAnnotation(String descriptor)
{
if (MESSAGE_DEF_DESCRIPTOR.equals(descriptor))
return new MessageDefAnnotationVisitor();
if (MESSAGE_DEFS_DESCRIPTOR.equals(descriptor))
return new MessageDefsAnnotationVisitor();
if (TEMPLATE_DEF_DESCRIPTOR.equals(descriptor))
return new TemplateDefAnnotationVisitor();
if (TEMPLATE_DEFS_DESCRIPTOR.equals(descriptor))
return new TemplateDefsAnnotationVisitor();
return null;
}
private final class MessageMethodVisitor extends MethodVisitor
{
private MessageMethodVisitor() {
super(ASM_API);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return AsmAnnotationAdopter.this.visitAnnotation(descriptor);
}
}
private final class MessageDefsAnnotationVisitor extends AnnotationVisitor
{
private MessageDefsAnnotationVisitor() {
super(ASM_API);
}
@Override
public AnnotationVisitor visitArray(String name)
{
return new AnnotationVisitor(ASM_API) {
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor)
{
return MESSAGE_DEF_DESCRIPTOR.equals(descriptor)
? new MessageDefAnnotationVisitor() : null;
}
};
}
}
private final class MessageDefAnnotationVisitor extends AnnotationVisitor
{
private String code;
private String text;
private final List texts = new ArrayList<>();
private MessageDefAnnotationVisitor() {
super(ASM_API);
}
@Override
public void visit(String name, Object value)
{
if ("code".equals(name))
code = (String)value;
else if ("text".equals(name))
text = (String)value;
}
@Override
public AnnotationVisitor visitArray(String name)
{
return new AnnotationVisitor(ASM_API) {
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
return TEXT_DESCRIPTOR.equals(descriptor) ? new TextAnnotationVisitor(texts) : null;
}
};
}
@Override
public void visitEnd() {
adopt(new MessageDefImpl(code, text, texts.toArray(Text[]::new)));
}
}
private final class TemplateDefsAnnotationVisitor extends AnnotationVisitor
{
private TemplateDefsAnnotationVisitor() {
super(ASM_API);
}
@Override
public AnnotationVisitor visitArray(String name)
{
return new AnnotationVisitor(ASM_API) {
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor)
{
return TEMPLATE_DEF_DESCRIPTOR.equals(descriptor)
? new TemplateDefAnnotationVisitor() : null;
}
};
}
}
private final class TemplateDefAnnotationVisitor extends AnnotationVisitor
{
private String name;
private String text;
private final List texts = new ArrayList<>();
private TemplateDefAnnotationVisitor() {
super(ASM_API);
}
@Override
public void visit(String name, Object value)
{
if ("name".equals(name))
this.name = (String)value;
else if ("text".equals(name))
text = (String)value;
}
@Override
public AnnotationVisitor visitArray(String name)
{
return new AnnotationVisitor(ASM_API) {
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
return TEXT_DESCRIPTOR.equals(descriptor) ? new TextAnnotationVisitor(texts) : null;
}
};
}
@Override
public void visitEnd() {
adopt(new TemplateDefImpl(name, text, texts.toArray(Text[]::new)));
}
}
private static final class TextAnnotationVisitor extends AnnotationVisitor
{
private final List inheritedTexts;
private String locale;
private String text;
private String value;
private TextAnnotationVisitor(List inheritedTexts)
{
super(ASM_API);
this.inheritedTexts = inheritedTexts;
}
@Override
public void visit(String name, Object value)
{
if ("locale".equals(name))
locale = (String)value;
else if ("text".equals(name))
text = (String)value;
else if ("value".equals(name))
this.value = (String)value;
}
@Override
public void visitEnd() {
inheritedTexts.add(new TextImpl(locale, text, value));
}
}
}