/* * 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.felix.ipojo.manipulator.metadata.annotation.model.literal; import static java.lang.String.format; import static org.objectweb.asm.Type.getType; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.felix.ipojo.manipulator.metadata.annotation.model.AnnotationDiscovery; import org.apache.felix.ipojo.manipulator.metadata.annotation.model.Playback; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; /** * User: guillaume * Date: 08/07/13 * Time: 17:15 */ public class AnnotationPlayback implements Playback { public static final List<? extends Class<? extends Serializable>> BOXED_TYPES = Arrays.asList(Byte.class, Long.class, Character.class, Boolean.class, Double.class, Float.class, Integer.class, Short.class); private final Annotation m_annotation; private final Type m_annotationType; public AnnotationPlayback(final Annotation annotation) { m_annotation = annotation; m_annotationType = Type.getType(annotation.annotationType()); } private Map<String, Object> getValues() { Map<String, Object> values = new HashMap<String, Object>(); for (Method method : m_annotation.annotationType().getDeclaredMethods()) { try { values.put(method.getName(), method.invoke(m_annotation)); } catch (Throwable t) { throw new IllegalStateException( format("Cannot get value of the %s.%s attribute", m_annotation.annotationType().getSimpleName(), method.getName()), t ); } } return values; } public void accept(final FieldVisitor visitor) { AnnotationVisitor av = visitor.visitAnnotation(m_annotationType.getDescriptor(), true); if (av != null) { accept(av); } } public void accept(final ClassVisitor visitor) { AnnotationVisitor av = visitor.visitAnnotation(m_annotationType.getDescriptor(), true); if (av != null) { accept(av); } } public void accept(final MethodVisitor visitor) { AnnotationVisitor av = visitor.visitAnnotation(m_annotationType.getDescriptor(), true); if (av != null) { accept(av); } } public void accept(final MethodVisitor visitor, final int index) { AnnotationVisitor av = visitor.visitParameterAnnotation(index, m_annotationType.getDescriptor(), true); if (av != null) { accept(av); } } public void accept(final AnnotationDiscovery visitor) { AnnotationVisitor av = visitor.visitAnnotation(m_annotationType.getDescriptor()); if (av != null) { accept(av); } } private void accept(final AnnotationVisitor visitor) { // As per the ASM doc, visit methods must be called in a given order: // 1. visit() // 2. visitEnum() // 3. visitAnnotation() // 4. visitArray() // So values must be sorted Map<String, Object> values = getValues(); accept(values, visitor); acceptEnum(values, visitor); acceptAnnotation(values, visitor); acceptArray(values, visitor); // Do not forget to visitEnd() visitor.visitEnd(); // TODO This should disappear, only useful for testing if (!values.isEmpty()) { // We missed something during serialization throw new IllegalStateException( format("Attributes of @%s could not be serialized: %s", m_annotation.annotationType().getSimpleName(), values.keySet()) ); } } private void acceptAnnotation(final Map<String, Object> values, final AnnotationVisitor visitor) { Map<String, Object> copy = new HashMap<String, Object>(values); for (Map.Entry<String, Object> entry : copy.entrySet()) { Class<?> type = entry.getValue().getClass(); if (Annotation.class.isAssignableFrom(type)) { Annotation annotation = (Annotation) entry.getValue(); AnnotationVisitor annotationVisitor = visitor.visitAnnotation(entry.getKey(), getType(annotation.annotationType()).getDescriptor()); if (annotationVisitor != null) { AnnotationPlayback playback = new AnnotationPlayback(annotation); playback.accept(annotationVisitor); } values.remove(entry.getKey()); } } } private void acceptEnum(final Map<String, Object> values, final AnnotationVisitor visitor) { Map<String, Object> copy = new HashMap<String, Object>(values); for (Map.Entry<String, Object> entry : copy.entrySet()) { Class<?> type = entry.getValue().getClass(); if (type.isEnum()) { Enum<?> enumValue = (Enum<?>) entry.getValue(); visitor.visitEnum(entry.getKey(), getType(type).getDescriptor(), enumValue.name()); values.remove(entry.getKey()); } } } private void accept(final Map<String, Object> values, final AnnotationVisitor visitor) { Map<String, Object> copy = new HashMap<String, Object>(values); for (Map.Entry<String, Object> entry : copy.entrySet()) { Class<?> type = entry.getValue().getClass(); if (isSimpleType(type)) { // Accept Byte, Boolean, Character, Short, Integer, Long, Float, Double // Accept String // Accept Array of byte, boolean, char, short, int, long, float, double visitor.visit(entry.getKey(), transform(entry.getValue())); values.remove(entry.getKey()); } } } private boolean isSimpleType(final Class<?> type) { return isPrimitive(type) || String.class.equals(type) || Class.class.equals(type) || (type.isArray() && isPrimitive(type.getComponentType())); } private boolean isPrimitive(final Class<?> type) { if (type.isPrimitive()) { return true; } if (BOXED_TYPES.contains(type)) { return true; } return false; } private void acceptArray(final Map<String, Object> values, final AnnotationVisitor visitor) { Map<String, Object> copy = new HashMap<String, Object>(values); for (Map.Entry<String, Object> entry : copy.entrySet()) { Class<?> type = entry.getValue().getClass(); if (type.isArray()) { // Simple arrays have been visited using AnnotationVisitor.visit(String, Object) AnnotationVisitor arrayVisitor = visitor.visitArray(entry.getKey()); if (arrayVisitor != null) { Object[] array = (Object[]) entry.getValue(); Class<?> componentType = array.getClass().getComponentType(); Type asmType = Type.getType(componentType); if (componentType.isEnum()) { for (Object o : array) { Enum eValue = (Enum) o; arrayVisitor.visitEnum(null, asmType.getDescriptor(), eValue.name()); } } else if (componentType.isAnnotation()) { for (Object o : array) { Annotation annotation = (Annotation) o; AnnotationVisitor annotationVisitor = arrayVisitor.visitAnnotation(null, asmType.getDescriptor()); if (annotationVisitor != null) { AnnotationPlayback playback = new AnnotationPlayback(annotation); playback.accept(annotationVisitor); } } } else { for (Object o : array) { arrayVisitor.visit(null, transform(o)); } } arrayVisitor.visitEnd(); } values.remove(entry.getKey()); } } } private Object transform(final Object value) { if (value instanceof Class) { return getType((Class) value); } return value; } }