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}