blob: 7bb16c9a2130d3e27871efb2c2e2c038c31e3a23 [file] [log] [blame]
/*
* 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.maven.osgicheck.impl.checks;
import static org.objectweb.asm.ClassReader.SKIP_CODE;
import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.felix.maven.osgicheck.impl.Check;
import org.apache.felix.maven.osgicheck.impl.CheckContext;
import org.apache.felix.maven.osgicheck.impl.featureutil.ManifestUtil;
import org.apache.felix.maven.osgicheck.impl.featureutil.PackageInfo;
import org.apache.maven.plugin.MojoExecutionException;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
/**
* The following checks are performed:
* <ul>
* <li>If a package is exported, the classes must be marked with either ConsumerType or ProviderType
* </ul>
*/
public class ConsumerProviderTypeCheck implements Check {
@Override
public String getName() {
return "exportannotation";
}
private boolean include(final File f) {
return f.isFile() && f.getName().endsWith(".class");
}
private boolean exclude(final File f) {
// skip package info and inner classes
return f.getName().equals("package-info.class") || f.getName().contains("$");
}
@Override
public void check(final CheckContext ctx) throws MojoExecutionException {
ctx.getLog().info("Checking exported packages for provider/consumer type");
final List<PackageInfo> exp = ManifestUtil.extractExportedPackages(ctx.getManifest());
for(final PackageInfo p : exp) {
final File packageDir = new File(ctx.getRootDir(), p.getName().replace(".", File.separator));
if ( !packageDir.exists() ) {
ctx.reportError("Package directory " + packageDir + " does not exist!");
} else {
ctx.getLog().info("Examining package " + p.getName() + "...");
for(final File f : packageDir.listFiles()) {
if ( include(f) && !exclude(f) ) {
final String className = f.getName().substring(0, f.getName().length() - 6);
processClass(ctx, f, className);
}
}
}
}
}
/**
* Scan a single class.
*/
private void processClass(final CheckContext ctx, final File classFile, final String className) {
ctx.getLog().debug("Processing class " + className);
try (final InputStream input = new FileInputStream(classFile)) {
// get the class file for ASM
final ClassReader classReader = new ClassReader(input);
final ClassNode classNode = new ClassNode();
classReader.accept(classNode, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
if ((classNode.access & Opcodes.ACC_INTERFACE) == 0) {
// not an interface
ctx.getLog().debug("Skipping non interface class " + className );
return;
}
final List<String> annotations = extractAnnotation(classNode);
for(final String s : annotations) {
ctx.getLog().debug("Found annotation " + s );
}
int count = 0;
if ( annotations.contains("org.osgi.annotation.versioning.ProviderType") ) {
count++;
}
if ( annotations.contains("org.osgi.annotation.versioning.ConsumerType") ) {
count++;
}
if ( count == 0 ) {
ctx.reportError("Class " + className + " does neither declare ConsumerType nor ProducerType");
} else if ( count == 2 ) {
ctx.reportError("Class " + className + " declares ConsumerType and ProducerType");
}
} catch (final IllegalArgumentException | IOException ioe) {
ctx.reportError("Unable to scan annotations " + ioe.getMessage());
}
}
/**
* Extract annotations
*/
private final List<String> extractAnnotation(final ClassNode classNode) {
final List<String> descriptions = new ArrayList<>();
for(final AnnotationNode n : getAllAnnotations(classNode.invisibleAnnotations)) {
this.parseAnnotation(descriptions, n);
}
for(final AnnotationNode n : getAllAnnotations(classNode.visibleAnnotations)) {
this.parseAnnotation(descriptions, n);
}
return descriptions;
}
@SuppressWarnings("unchecked")
private List<AnnotationNode> getAllAnnotations(@SuppressWarnings("rawtypes") final List annotationList) {
final List<AnnotationNode> resultList = new ArrayList<>();
if ( annotationList != null ) {
resultList.addAll(annotationList);
}
return resultList;
}
/**
* Parse annotation
*/
private void parseAnnotation(final List<String> descriptions, final AnnotationNode annotation) {
// desc has the format 'L' + className.replace('.', '/') + ';'
final String name = annotation.desc.substring(1, annotation.desc.length() - 1).replace('/', '.');
descriptions.add(name);
}
}