001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors; 020 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.stream.Collectors; 030import java.util.stream.Stream; 031 032import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass; 033import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScanner; 034import org.codehaus.plexus.util.StringUtils; 035import org.objectweb.asm.AnnotationVisitor; 036import org.objectweb.asm.ClassVisitor; 037import org.objectweb.asm.FieldVisitor; 038import org.objectweb.asm.MethodVisitor; 039import org.objectweb.asm.Opcodes; 040import org.objectweb.asm.Type; 041import org.objectweb.asm.signature.SignatureReader; 042import org.objectweb.asm.util.TraceSignatureVisitor; 043 044import static org.apache.maven.tools.plugin.extractor.annotations.scanner.DefaultMojoAnnotationsScanner.PARAMETER_V3; 045import static org.apache.maven.tools.plugin.extractor.annotations.scanner.DefaultMojoAnnotationsScanner.PARAMETER_V4; 046 047/** 048 * Visitor for Mojo classes. 049 * 050 * @author Olivier Lamy 051 * @since 3.0 052 */ 053public class MojoClassVisitor extends ClassVisitor { 054 private MojoAnnotatedClass mojoAnnotatedClass; 055 056 private Map<String, MojoAnnotationVisitor> annotationVisitorMap = new HashMap<>(); 057 058 private List<MojoFieldVisitor> fieldVisitors = new ArrayList<>(); 059 060 private List<MojoMethodVisitor> methodVisitors = new ArrayList<>(); 061 062 private int version; 063 064 public MojoClassVisitor() { 065 super(Opcodes.ASM9); 066 } 067 068 public MojoAnnotatedClass getMojoAnnotatedClass() { 069 return mojoAnnotatedClass; 070 } 071 072 public int getVersion() { 073 return version; 074 } 075 076 public MojoAnnotationVisitor getAnnotationVisitor(Class<?> annotation) { 077 return getAnnotationVisitor(annotation.getName()); 078 } 079 080 public MojoAnnotationVisitor getAnnotationVisitor(String name) { 081 return annotationVisitorMap.get(name); 082 } 083 084 public List<MojoFieldVisitor> findFieldWithAnnotation(Class<?> annotation) { 085 return findFieldWithAnnotation(Collections.singleton(annotation.getName())); 086 } 087 088 public List<MojoFieldVisitor> findFieldWithAnnotation(Set<String> annotationClassNames) { 089 List<MojoFieldVisitor> mojoFieldVisitors = new ArrayList<>(); 090 091 for (MojoFieldVisitor mojoFieldVisitor : this.fieldVisitors) { 092 Map<String, MojoAnnotationVisitor> filedVisitorMap = mojoFieldVisitor.getAnnotationVisitorMap(); 093 if (filedVisitorMap.keySet().stream().anyMatch(annotationClassNames::contains)) { 094 mojoFieldVisitors.add(mojoFieldVisitor); 095 } 096 } 097 098 return mojoFieldVisitors; 099 } 100 101 public List<MojoParameterVisitor> findParameterVisitors() { 102 return findParameterVisitors(new HashSet<>(Arrays.asList(PARAMETER_V3, PARAMETER_V4))); 103 } 104 105 public List<MojoParameterVisitor> findParameterVisitors(Set<String> annotationClassNames) { 106 return Stream.concat( 107 findFieldWithAnnotation(annotationClassNames).stream(), 108 methodVisitors.stream().filter(method -> method.getAnnotationVisitorMap().keySet().stream() 109 .anyMatch(annotationClassNames::contains))) 110 .collect(Collectors.toList()); 111 } 112 113 @Override 114 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 115 this.version = version; 116 mojoAnnotatedClass = new MojoAnnotatedClass(); 117 mojoAnnotatedClass.setClassName(Type.getObjectType(name).getClassName()); 118 if (superName != null) { 119 mojoAnnotatedClass.setParentClassName(Type.getObjectType(superName).getClassName()); 120 } 121 } 122 123 @Override 124 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 125 String annotationClassName = Type.getType(desc).getClassName(); 126 if (!MojoAnnotationsScanner.CLASS_LEVEL_ANNOTATIONS.contains(annotationClassName)) { 127 return null; 128 } 129 if (annotationClassName.startsWith(MojoAnnotationsScanner.V4_API_ANNOTATIONS_PACKAGE)) { 130 mojoAnnotatedClass.setV4Api(true); 131 } 132 MojoAnnotationVisitor mojoAnnotationVisitor = new MojoAnnotationVisitor(annotationClassName); 133 annotationVisitorMap.put(annotationClassName, mojoAnnotationVisitor); 134 return mojoAnnotationVisitor; 135 } 136 137 @Override 138 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 139 List<String> typeParameters = extractTypeParameters(access, signature, true); 140 MojoFieldVisitor mojoFieldVisitor = 141 new MojoFieldVisitor(name, Type.getType(desc).getClassName(), typeParameters); 142 fieldVisitors.add(mojoFieldVisitor); 143 return mojoFieldVisitor; 144 } 145 146 /** 147 * Parses the signature according to 148 * <a href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4">JVMS 4.3.4</a> 149 * and returns the type parameters. 150 * @param access 151 * @param signature 152 * @param isField 153 * @return the list of type parameters (may be empty) 154 */ 155 private List<String> extractTypeParameters(int access, String signature, boolean isField) { 156 if (signature == null || signature.isEmpty()) { 157 return Collections.emptyList(); 158 } 159 TraceSignatureVisitor traceSignatureVisitor = new TraceSignatureVisitor(access); 160 SignatureReader signatureReader = new SignatureReader(signature); 161 if (isField) { 162 signatureReader.acceptType(traceSignatureVisitor); 163 } else { 164 signatureReader.accept(traceSignatureVisitor); 165 } 166 String declaration = traceSignatureVisitor.getDeclaration(); 167 int startTypeParameters = declaration.indexOf('<'); 168 if (startTypeParameters == -1) { 169 return Collections.emptyList(); 170 } 171 String typeParameters = declaration.substring(startTypeParameters + 1, declaration.lastIndexOf('>')); 172 return Arrays.asList(typeParameters.split(", ")); 173 } 174 175 @Override 176 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 177 if ((access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC 178 || (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) { 179 return null; 180 } 181 182 if (name.length() < 4 || !(name.startsWith("add") || name.startsWith("set"))) { 183 return null; 184 } 185 186 Type type = Type.getType(desc); 187 188 if ("void".equals(type.getReturnType().getClassName()) && type.getArgumentTypes().length == 1) { 189 String fieldName = StringUtils.lowercaseFirstLetter(name.substring(3)); 190 String className = type.getArgumentTypes()[0].getClassName(); 191 List<String> typeParameters = extractTypeParameters(access, signature, false); 192 193 MojoMethodVisitor mojoMethodVisitor = new MojoMethodVisitor(fieldName, className, typeParameters); 194 methodVisitors.add(mojoMethodVisitor); 195 return mojoMethodVisitor; 196 } 197 198 return null; 199 } 200}