| /* |
| * 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.bundleplugin.baseline; |
| |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.LineNumberReader; |
| import java.io.StringReader; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.codehaus.plexus.util.FileUtils; |
| import org.codehaus.plexus.util.IOUtil; |
| import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; |
| import org.codehaus.plexus.util.xml.XMLWriter; |
| import org.sonatype.plexus.build.incremental.BuildContext; |
| |
| import aQute.bnd.differ.Baseline.Info; |
| import aQute.bnd.service.diff.Delta; |
| import aQute.bnd.service.diff.Diff; |
| import aQute.bnd.service.diff.Type; |
| import aQute.bnd.version.Version; |
| |
| /** |
| * BND Baseline check between two bundles. |
| * |
| * @goal baseline |
| * @phase verify |
| * @requiresDependencyResolution test |
| * @threadSafe true |
| * @since 2.4.1 |
| */ |
| public final class BaselinePlugin |
| extends AbstractBaselinePlugin |
| { |
| |
| private static final String TABLE_PATTERN = "%s %-50s %-10s %-10s %-10s %-10s %-10s"; |
| |
| /** |
| * An XML output file to render to <code>${project.build.directory}/baseline.xml</code>. |
| * |
| * @parameter expression="${project.build.directory}/baseline.xml" |
| */ |
| private File xmlOutputFile; |
| |
| /** |
| * Whether to log the results to the console or not, true by default. |
| * |
| * @parameter expression="${logResults}" default-value="true" |
| */ |
| private boolean logResults; |
| |
| /** |
| * @component |
| */ |
| private BuildContext buildContext; |
| |
| private static final class Context { |
| public FileWriter writer; |
| public XMLWriter xmlWriter; |
| } |
| |
| @Override |
| protected Object init(final Object noContext) |
| { |
| if ( xmlOutputFile != null ) |
| { |
| xmlOutputFile.getParentFile().mkdirs(); |
| try |
| { |
| final Context ctx = new Context(); |
| ctx.writer = new FileWriter( xmlOutputFile ); |
| ctx.xmlWriter = new PrettyPrintXMLWriter( ctx.writer ); |
| return ctx; |
| } |
| catch ( IOException e ) |
| { |
| getLog().warn( "No XML report will be produced, cannot write data to " + xmlOutputFile, e ); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| protected void close(final Object writer) |
| { |
| if ( writer != null ) |
| { |
| try { |
| |
| ((Context)writer).writer.close(); |
| } |
| catch (IOException e) |
| { |
| // ignore |
| } |
| } |
| } |
| @Override |
| protected void startBaseline( Object context, |
| String generationDate, |
| String bundleName, |
| String currentVersion, |
| String previousVersion ) |
| { |
| final XMLWriter xmlWriter = context == null ? null : ((Context)context).xmlWriter; |
| if ( isLoggingResults() ) |
| { |
| log( "Baseline Report - Generated by Apache Felix Maven Bundle Plugin on %s based on Bnd - see http://www.aqute.biz/Bnd/Bnd", |
| generationDate ); |
| log( "Comparing bundle %s version %s to version %s", bundleName, currentVersion, previousVersion ); |
| log( "" ); |
| log( TABLE_PATTERN, |
| " ", |
| "PACKAGE_NAME", |
| "DELTA", |
| "CUR_VER", |
| "BASE_VER", |
| "REC_VER", |
| "WARNINGS", |
| "ATTRIBUTES" ); |
| log( TABLE_PATTERN, |
| "=", |
| "==================================================", |
| "==========", |
| "==========", |
| "==========", |
| "==========", |
| "==========", |
| "==========" ); |
| } |
| |
| if ( xmlWriter != null ) |
| { |
| xmlWriter.startElement( "baseline" ); |
| xmlWriter.addAttribute( "version", "1.0.0" ); |
| xmlWriter.addAttribute( "vendor", "The Apache Software Foundation" ); |
| xmlWriter.addAttribute( "vendorURL", "http://www.apache.org/" ); |
| xmlWriter.addAttribute( "generator", "Apache Felix Maven Bundle Plugin" ); |
| xmlWriter.addAttribute( "generatorURL", "http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html" ); |
| xmlWriter.addAttribute( "analyzer", "Bnd" ); |
| xmlWriter.addAttribute( "analyzerURL", "http://www.aqute.biz/Bnd/Bnd" ); |
| xmlWriter.addAttribute( "generatedOn", generationDate ); |
| xmlWriter.addAttribute( "bundleName", bundleName ); |
| xmlWriter.addAttribute( "currentVersion", currentVersion ); |
| xmlWriter.addAttribute( "previousVersion", previousVersion ); |
| } |
| } |
| |
| @Override |
| protected void startPackage( Object context, |
| boolean mismatch, |
| String name, |
| String shortDelta, |
| String delta, |
| Version newerVersion, |
| Version olderVersion, |
| Version suggestedVersion, |
| DiffMessage diffMessage, |
| Map<String,String> attributes ) |
| { |
| final XMLWriter xmlWriter = context == null ? null : ((Context)context).xmlWriter; |
| if ( isLoggingResults() ) |
| { |
| log( TABLE_PATTERN, |
| mismatch ? '*' : shortDelta, |
| name, |
| delta, |
| newerVersion, |
| olderVersion, |
| suggestedVersion, |
| diffMessage != null ? diffMessage : '-', |
| attributes ); |
| } |
| |
| if ( xmlWriter != null ) |
| { |
| xmlWriter.startElement( "package" ); |
| xmlWriter.addAttribute( "name", name ); |
| xmlWriter.addAttribute( "delta", delta ); |
| simpleElement( xmlWriter, "mismatch", String.valueOf( mismatch ) ); |
| simpleElement( xmlWriter, "newerVersion", newerVersion.toString() ); |
| simpleElement( xmlWriter, "olderVersion", olderVersion.toString() ); |
| if ( suggestedVersion != null ) |
| { |
| simpleElement( xmlWriter, "suggestedVersion", suggestedVersion.toString() ); |
| } |
| |
| if ( diffMessage != null ) |
| { |
| simpleElement( xmlWriter, diffMessage.getType().name(), diffMessage.getMessage() ); |
| } |
| |
| xmlWriter.startElement( "attributes" ); |
| if (attributes != null) |
| { |
| for (Entry<String, String> attribute : attributes.entrySet()) |
| { |
| String attributeName = attribute.getKey(); |
| if (':' == attributeName.charAt(attributeName.length() - 1)) |
| { |
| attributeName = attributeName.substring(0, attributeName.length() - 1); |
| } |
| String attributeValue = attribute.getValue(); |
| |
| xmlWriter.startElement(attributeName); |
| xmlWriter.writeText(attributeValue); |
| xmlWriter.endElement(); |
| } |
| } |
| xmlWriter.endElement(); |
| } |
| } |
| |
| @Override |
| protected void startDiff( Object context, int depth, String type, String name, String delta, String shortDelta ) |
| { |
| final XMLWriter xmlWriter = context == null ? null : ((Context)context).xmlWriter; |
| if ( isLoggingResults() ) |
| { |
| log( "%-" + (depth * 4) + "s %s %s %s", |
| "", |
| shortDelta, |
| type, |
| name ); |
| } |
| |
| if ( xmlWriter != null ) |
| { |
| xmlWriter.startElement( type ); |
| xmlWriter.addAttribute( "name", name ); |
| xmlWriter.addAttribute( "delta", delta ); |
| } |
| } |
| |
| @Override |
| protected void endDiff( Object context, int depth ) |
| { |
| final XMLWriter xmlWriter = context == null ? null : ((Context)context).xmlWriter; |
| if ( xmlWriter != null ) |
| { |
| xmlWriter.endElement(); |
| } |
| } |
| |
| @Override |
| protected void endPackage(Object context) |
| { |
| final XMLWriter xmlWriter = context == null ? null : ((Context)context).xmlWriter; |
| if ( isLoggingResults() ) |
| { |
| log( "-----------------------------------------------------------------------------------------------------------" ); |
| } |
| |
| if ( xmlWriter != null ) |
| { |
| xmlWriter.endElement(); |
| } |
| } |
| |
| @Override |
| protected void endBaseline(Object context) |
| { |
| final XMLWriter xmlWriter = context == null ? null : ((Context)context).xmlWriter; |
| if ( xmlWriter != null ) |
| { |
| xmlWriter.endElement(); |
| } |
| } |
| |
| private boolean isLoggingResults() |
| { |
| return logResults && getLog().isInfoEnabled(); |
| } |
| |
| private void log( String format, Object...args ) |
| { |
| getLog().info( String.format( format, args ) ); |
| } |
| |
| private void simpleElement( XMLWriter xmlWriter, String name, String value ) |
| { |
| xmlWriter.startElement( name ); |
| xmlWriter.writeText( value ); |
| xmlWriter.endElement(); |
| } |
| |
| @Override |
| protected void reportErrors(final Info[] infos) |
| throws IOException |
| { |
| for ( final Info info : infos ) |
| { |
| // clear previous messages |
| final FileMarker packageMarker = findMarkerLocationForPackage(info.packageName); |
| |
| if ( info.suggestedVersion != null ) |
| { |
| if ( info.newerVersion.compareTo( info.suggestedVersion ) > 0 ) |
| { |
| final String msg = "Excessive version increase: detected " + info.newerVersion + ", suggested " + info.suggestedVersion; |
| |
| this.buildContext.addMessage(packageMarker.file, packageMarker.line, packageMarker.col, msg, BuildContext.SEVERITY_WARNING, null); |
| } |
| else if ( info.newerVersion.compareTo( info.suggestedVersion ) < 0 ) |
| { |
| final String msg = "Version increase required: detected " + info.newerVersion + ", suggested " + info.suggestedVersion; |
| |
| this.buildContext.addMessage(packageMarker.file, packageMarker.line, packageMarker.col, msg, BuildContext.SEVERITY_ERROR, null); |
| } |
| } |
| |
| if ( info.packageDiff.getDelta() == Delta.UNCHANGED && info.newerVersion.compareTo( info.suggestedVersion ) != 0 ) |
| { |
| final String msg = "Version has been increased but analysis detected no changes: detected " + info.newerVersion + ", suggested " + info.suggestedVersion; |
| this.buildContext.addMessage(packageMarker.file, packageMarker.line, packageMarker.col, msg, BuildContext.SEVERITY_WARNING, null); |
| } |
| generateStructuralChangeMarkers(info); |
| } |
| } |
| |
| private File getPackageFile(final String packageName) |
| { |
| final String sourceDir = this.project.getBuild().getSourceDirectory(); |
| final File packageDir = new File((sourceDir + '/' + packageName.replace('.', '/')).replace('/', File.separatorChar)); |
| |
| return packageDir; |
| } |
| |
| private File getSourceFile(final String className) |
| { |
| final String sourceDir = this.project.getBuild().getSourceDirectory(); |
| final File packageDir = new File((sourceDir + '/' + className.replace('.', '/') + ".java").replace('/', File.separatorChar)); |
| |
| return packageDir; |
| } |
| |
| private static final class FileMarker |
| { |
| public File file; |
| public int line; |
| public int col; |
| } |
| |
| /** |
| * Find marker location for a package |
| * and clear old markers. |
| */ |
| private FileMarker findMarkerLocationForPackage(final String packageName) |
| throws IOException |
| { |
| final File packageDir = getPackageFile(packageName); |
| this.buildContext.removeMessages(packageDir); |
| |
| final File packageInfoFile = new File(packageDir, "packageinfo"); |
| this.buildContext.removeMessages(packageInfoFile); |
| |
| final File packageInfoJavaFile = new File(packageDir, "package-info.java"); |
| this.buildContext.removeMessages(packageInfoJavaFile); |
| |
| // package-info.java has precedence |
| if ( packageInfoJavaFile != null && packageInfoJavaFile.exists() ) |
| { |
| return findVersionLocationInPackageInfoJava(packageInfoJavaFile); |
| } |
| // packageinfo next |
| if ( packageInfoFile != null && packageInfoFile.exists() ) |
| { |
| return findVersionLocationInPackageInfo(packageInfoFile); |
| } |
| |
| final FileMarker defaultMarker = new FileMarker(); |
| defaultMarker.file = packageDir; |
| |
| return defaultMarker; |
| } |
| |
| private FileMarker findVersionLocationInPackageInfo(final File file) |
| throws IOException |
| { |
| final FileMarker marker = new FileMarker(); |
| marker.file = file; |
| final String contents = FileUtils.fileRead(file); |
| final LineNumberReader reader = new LineNumberReader(new StringReader(contents)); |
| try |
| { |
| String line; |
| while ( (line = reader.readLine()) != null ) |
| { |
| final int pos = line.indexOf("version "); |
| if ( pos > -1 ) |
| { |
| marker.col = pos; |
| marker.line = reader.getLineNumber(); |
| |
| break; |
| } |
| } |
| } |
| finally |
| { |
| IOUtil.close(reader); |
| } |
| return marker; |
| } |
| |
| private FileMarker findVersionLocationInPackageInfoJava(final File file) |
| throws IOException |
| { |
| final FileMarker marker = new FileMarker(); |
| marker.file = file; |
| final String contents = FileUtils.fileRead(file); |
| final LineNumberReader reader = new LineNumberReader(new StringReader(contents)); |
| try |
| { |
| String line; |
| while ( (line = reader.readLine()) != null ) |
| { |
| final int pos = line.indexOf("@Version"); |
| if ( pos > -1 ) |
| { |
| marker.col = pos; |
| marker.line = reader.getLineNumber(); |
| |
| break; |
| } |
| } |
| } |
| finally |
| { |
| IOUtil.close(reader); |
| } |
| return marker; |
| } |
| |
| private void generateStructuralChangeMarkers(final Info baselineInfo) |
| { |
| final Delta packageDelta = baselineInfo.packageDiff.getDelta(); |
| |
| // Iterate into the package member diffs |
| for (final Diff pkgMemberDiff : baselineInfo.packageDiff.getChildren()) |
| { |
| // only deal with interfaces and classes |
| if ( pkgMemberDiff.getType() != Type.INTERFACE && pkgMemberDiff.getType() != Type.CLASS ) |
| { |
| continue; |
| } |
| |
| // clear old marker |
| final String className = pkgMemberDiff.getName(); |
| final File classFile = this.getSourceFile(className); |
| this.buildContext.removeMessages(classFile); |
| |
| // Skip deltas that have lesser significance than the overall package delta |
| if (pkgMemberDiff.getDelta().ordinal() < packageDelta.ordinal()) |
| { |
| continue; |
| } |
| |
| if (Delta.ADDED == pkgMemberDiff.getDelta()) |
| { |
| // TODO |
| } |
| else if (Delta.REMOVED == pkgMemberDiff.getDelta()) |
| { |
| // TODO |
| } |
| else |
| { |
| // Iterate into the class member diffs |
| for (final Diff classMemberDiff : pkgMemberDiff.getChildren()) |
| { |
| // Skip deltas that have lesser significance than the overall package delta (again) |
| if (classMemberDiff.getDelta().ordinal() < packageDelta.ordinal()) |
| continue; |
| |
| if (Delta.ADDED == classMemberDiff.getDelta()) |
| { |
| addAddedMarker(classFile, classMemberDiff); |
| } |
| else if (Delta.REMOVED == classMemberDiff.getDelta()) |
| { |
| addRemovedMarker(classFile, classMemberDiff); |
| } |
| else if (Delta.CHANGED == classMemberDiff.getDelta()) |
| { |
| addChangedMarker(classFile, classMemberDiff); |
| } |
| } |
| } |
| } |
| } |
| |
| private void addAddedMarker(final File classFile, final Diff diff) |
| { |
| this.buildContext.addMessage(classFile, 0, 0, diff.getType() + " " + diff.getName() + " has been added.", BuildContext.SEVERITY_ERROR, null); |
| } |
| |
| private void addRemovedMarker(final File classFile, final Diff diff) |
| { |
| this.buildContext.addMessage(classFile, 0, 0, diff.getType() + " " + diff.getName() + " has been removed.", BuildContext.SEVERITY_ERROR, null); |
| } |
| |
| private void addChangedMarker(final File classFile, final Diff diff) |
| { |
| this.buildContext.addMessage(classFile, 0, 0, diff.getType() + " " + diff.getName() + " has been changed.", BuildContext.SEVERITY_ERROR, null); |
| } |
| } |