promote maven-shared-jar
git-svn-id: https://svn.apache.org/repos/asf/maven/shared/trunk@553132 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1354439
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" ?>
+
+<!--
+ ~ 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.
+ -->
+
+<project
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.shared</groupId>
+ <artifactId>maven-shared-components</artifactId>
+ <version>7</version>
+ </parent>
+
+ <artifactId>maven-shared-jar</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <name>Maven Shared :: JAR Utilities</name>
+ <description>
+ Utilities that help identify the contents of a JAR, including Java class analysis and Maven metadata analysis.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-maven-plugin</artifactId>
+ <version>1.3.3</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>descriptor</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <version>2.0</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>clean</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.2</version>
+ <!-- TODO: move configuration to parent POM -->
+ <configuration>
+ <!-- TODO: not needed under javadoc plugin 2.3, once released -->
+ <overview>${basedir}/src/main/javadoc/overview.html</overview>
+ <tags>
+ <tag>
+ <name>todo</name>
+ <placement>a</placement>
+ <head>TODO:</head>
+ </tag>
+ <tag>
+ <name>plexus.requirement</name>
+ <placement>f</placement>
+ <head>Plexus Requirement:</head>
+ </tag>
+ <tag>
+ <name>plexus.component</name>
+ <placement>t</placement>
+ <head>Plexus Component:</head>
+ </tag>
+ </tags>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-changelog-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jxr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <version>2.0</version>
+ </plugin>
+ </plugins>
+ </reporting>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-model</artifactId>
+ <version>2.0.7</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ <version>2.0.7</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-digest</artifactId>
+ <version>1.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.bcel</groupId>
+ <artifactId>bcel</artifactId>
+ <version>5.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ <version>3.1</version>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java b/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java
new file mode 100644
index 0000000..3d27894
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java
@@ -0,0 +1,239 @@
+package org.apache.maven.shared.jar;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Open a JAR file to be analyzed. Note that once created, the {@link #closeQuietly()} method should be called to
+ * release the associated file handle.
+ * <p/>
+ * Typical usage:
+ * <pre>
+ * JarAnalyzer jar = new JarAnalyzer( jarFile );
+ * <p/>
+ * try
+ * {
+ * // do some analysis, such as:
+ * jarClasses = jarClassAnalyzer.analyze( jar );
+ * }
+ * finally
+ * {
+ * jar.closeQuietly();
+ * }
+ * <p/>
+ * // use jar.getJarData() in some way, or the data returned by the JAR analyzer. jar itself can no longer be used.
+ * </pre>
+ * <p/>
+ * Note: that the actual data is separated from this class by design to minimise the chance of forgetting to close the
+ * JAR file. The {@link org.apache.maven.shared.jar.JarData} class exposed, as well as any data returned by actual
+ * analyzers that use this class, can be used safely once this class is out of scope.
+ *
+ * @see org.apache.maven.shared.jar.identification.JarIdentificationAnalysis#analyze(JarAnalyzer)
+ * @see org.apache.maven.shared.jar.classes.JarClassesAnalysis#analyze(JarAnalyzer)
+ */
+public class JarAnalyzer
+{
+ /**
+ * Pattern to filter JAR entries for class files.
+ *
+ * @todo why are inner classes and other potentially valid classes omitted? (It flukes it by finding everything after $)
+ */
+ private static final Pattern CLASS_FILTER = Pattern.compile( "[A-Za-z0-9]*\\.class$" );
+
+ /**
+ * Pattern to filter JAR entries for Maven POM files.
+ */
+ private static final Pattern MAVEN_POM_FILTER = Pattern.compile( "META-INF/maven/.*/pom\\.xml$" );
+
+ /**
+ * Pattern to filter JAR entries for text files that may contain a version.
+ */
+ private static final Pattern VERSION_FILTER = Pattern.compile( "[Vv][Ee][Rr][Ss][Ii][Oo][Nn]" );
+
+ /**
+ * The associated JAR file.
+ */
+ private final JarFile jarFile;
+
+ /**
+ * Contains information about the data collected so far.
+ */
+ private final JarData jarData;
+
+ /**
+ * Constructor. Opens the JAR file, so should be matched by a call to {@link #closeQuietly()}.
+ *
+ * @param file the JAR file to open
+ * @throws java.io.IOException if there is a problem opening the JAR file, or reading the manifest. The JAR file will be closed if this occurs.
+ */
+ public JarAnalyzer( File file )
+ throws IOException
+ {
+ this.jarFile = new JarFile( file );
+
+ // Obtain entries list.
+ List entries = Collections.list( jarFile.entries() );
+
+ // Sorting of list is done by name to ensure a bytecode hash is always consistent.
+ Collections.sort( entries, new Comparator()
+ {
+ public int compare( Object o1, Object o2 )
+ {
+ JarEntry entry1 = (JarEntry) o1;
+ JarEntry entry2 = (JarEntry) o2;
+
+ return entry1.getName().compareTo( entry2.getName() );
+ }
+ } );
+
+ Manifest manifest;
+ try
+ {
+ manifest = jarFile.getManifest();
+ }
+ catch ( IOException e )
+ {
+ closeQuietly();
+ throw e;
+ }
+ this.jarData = new JarData( file, manifest, entries );
+ }
+
+ /**
+ * Get the data for an individual entry in the JAR. The caller should closeQuietly the input stream, and should not retain
+ * the stream as the JAR file may be closed elsewhere.
+ *
+ * @param entry the JAR entry to read from
+ * @return the input stream of the individual JAR entry.
+ * @throws java.io.IOException if there is a problem opening the individual entry
+ */
+ public InputStream getEntryInputStream( JarEntry entry )
+ throws IOException
+ {
+ return jarFile.getInputStream( entry );
+ }
+
+ /**
+ * Close the associated JAR file, ignoring any errors that may occur.
+ */
+ public void closeQuietly()
+ {
+ try
+ {
+ jarFile.close();
+ }
+ catch ( IOException e )
+ {
+ // not much we can do about it but ignore it
+ }
+ }
+
+ /**
+ * Filter a list of JAR entries against the pattern.
+ *
+ * @param pattern the pattern to filter against
+ * @return the list of files found, in {@link java.util.jar.JarEntry} elements
+ */
+ public List filterEntries( Pattern pattern )
+ {
+ List ret = new ArrayList();
+
+ Iterator it = getEntries().iterator();
+ while ( it.hasNext() )
+ {
+ JarEntry entry = (JarEntry) it.next();
+
+ Matcher mat = pattern.matcher( entry.getName() );
+ if ( mat.find() )
+ {
+ ret.add( entry );
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Get all the classes in the JAR.
+ *
+ * @return the list of files found, in {@link java.util.jar.JarEntry} elements
+ */
+ public List getClassEntries()
+ {
+ return filterEntries( CLASS_FILTER );
+ }
+
+ /**
+ * Get all the Maven POM entries in the JAR.
+ *
+ * @return the list of files found, in {@link java.util.jar.JarEntry} elements
+ */
+ public List getMavenPomEntries()
+ {
+ return filterEntries( MAVEN_POM_FILTER );
+ }
+
+ /**
+ * Get all the version text files in the JAR.
+ *
+ * @return the list of files found, in {@link java.util.jar.JarEntry} elements
+ */
+ public List getVersionEntries()
+ {
+ return filterEntries( VERSION_FILTER );
+ }
+
+ /**
+ * Get all the contained files in the JAR.
+ *
+ * @return the list of files found, in {@link java.util.jar.JarEntry} elements
+ */
+ public List getEntries()
+ {
+ return jarData.getEntries();
+ }
+
+ /**
+ * Get the file that was opened by this analyzer.
+ *
+ * @return the JAR file reference
+ */
+ public File getFile()
+ {
+ return jarData.getFile();
+ }
+
+ public JarData getJarData()
+ {
+ return jarData;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/JarData.java b/src/main/java/org/apache/maven/shared/jar/JarData.java
new file mode 100644
index 0000000..f06c950
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/JarData.java
@@ -0,0 +1,188 @@
+package org.apache.maven.shared.jar;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.classes.JarClasses;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * Class that contains details of a single JAR file and it's entries.
+ */
+public final class JarData
+{
+ /**
+ * The JAR file.
+ */
+ private final File file;
+
+ /**
+ * Whether the JAR file is sealed.
+ */
+ private final boolean sealed;
+
+ /**
+ * The hashcode for the entire file's contents.
+ */
+ private String fileHash;
+
+ /**
+ * The hashcode for the file's class data contents.
+ */
+ private String bytecodeHash;
+
+ /**
+ * The JAR's manifest.
+ */
+ private final Manifest manifest;
+
+ /**
+ * Information about the JAR's classes.
+ */
+ private JarClasses jarClasses;
+
+ /**
+ * The JAR entries.
+ */
+ private final List entries;
+
+ /**
+ * Information about the JAR's identifying features.
+ */
+ private JarIdentification jarIdentification;
+
+ /**
+ * Constructor.
+ *
+ * @param file the JAR file
+ * @param manifest the JAR manifest
+ * @param entries the JAR entries
+ */
+ public JarData( File file, Manifest manifest, List entries )
+ {
+ this.file = file;
+
+ this.manifest = manifest;
+
+ this.entries = Collections.unmodifiableList( entries );
+
+ boolean sealed = false;
+ if ( this.manifest != null )
+ {
+ String sval = this.manifest.getMainAttributes().getValue( Attributes.Name.SEALED );
+ if ( StringUtils.isNotEmpty( sval ) )
+ {
+ sealed = "true".equalsIgnoreCase( sval.trim() );
+ }
+ }
+ this.sealed = sealed;
+ }
+
+ public List getEntries()
+ {
+ return entries;
+ }
+
+ public Manifest getManifest()
+ {
+ return manifest;
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+
+ public boolean isSealed()
+ {
+ return sealed;
+ }
+
+ public void setFileHash( String fileHash )
+ {
+ this.fileHash = fileHash;
+ }
+
+ public String getFileHash()
+ {
+ return fileHash;
+ }
+
+ public void setBytecodeHash( String bytecodeHash )
+ {
+ this.bytecodeHash = bytecodeHash;
+ }
+
+ public String getBytecodeHash()
+ {
+ return bytecodeHash;
+ }
+
+ public boolean isDebugPresent()
+ {
+ return jarClasses.isDebugPresent();
+ }
+
+ public void setJarClasses( JarClasses jarClasses )
+ {
+ this.jarClasses = jarClasses;
+ }
+
+ public int getNumEntries()
+ {
+ return entries.size();
+ }
+
+ public int getNumClasses()
+ {
+ return jarClasses.getClassNames().size();
+ }
+
+ public int getNumPackages()
+ {
+ return jarClasses.getPackages().size();
+ }
+
+ public String getJdkRevision()
+ {
+ return jarClasses.getJdkRevision();
+ }
+
+ public void setJarIdentification( JarIdentification jarIdentification )
+ {
+ this.jarIdentification = jarIdentification;
+ }
+
+ public JarIdentification getJarIdentification()
+ {
+ return jarIdentification;
+ }
+
+ public JarClasses getJarClasses()
+ {
+ return jarClasses;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/classes/ImportVisitor.java b/src/main/java/org/apache/maven/shared/jar/classes/ImportVisitor.java
new file mode 100644
index 0000000..21fcb1e
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/classes/ImportVisitor.java
@@ -0,0 +1,188 @@
+package org.apache.maven.shared.jar.classes;
+
+/*
+ * 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.
+ */
+
+import org.apache.bcel.classfile.ConstantClass;
+import org.apache.bcel.classfile.ConstantUtf8;
+import org.apache.bcel.classfile.EmptyVisitor;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.commons.collections.list.SetUniqueList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Implementation of a BCEL class visitor that analyzes a class and collects imports.
+ */
+public class ImportVisitor
+ extends EmptyVisitor
+{
+ /**
+ * The list of imports discovered.
+ */
+ private List imports;
+
+ /**
+ * The Java class that is being analyzed.
+ */
+ private JavaClass javaClass;
+
+ /**
+ * Pattern to detect if the import is qualified and allows retrieval of the actual import name from the string via the group 1.
+ */
+ private static final Pattern QUALIFIED_IMPORT_PATTERN = Pattern.compile( "L([a-zA-Z][a-zA-Z0-9\\.]+);" );
+
+ /**
+ * Pattern that checks whether a string is valid UTF-8. Imports that are not are ignored.
+ */
+ private static final Pattern VALID_UTF8_PATTERN = Pattern.compile( "^[\\(\\)\\[A-Za-z0-9;/]+$" );
+
+ /**
+ * Create an Import visitor.
+ *
+ * @param javaClass the javaclass to work from
+ */
+ public ImportVisitor( JavaClass javaClass )
+ {
+ this.javaClass = javaClass;
+
+ // Create a list that is guaranteed to be unique while retaining it's list qualities (LinkedHashSet does not
+ // expose the list interface even if natural ordering is retained)
+ this.imports = SetUniqueList.decorate( new ArrayList() );
+ }
+
+ /**
+ * Get the list of discovered imports.
+ *
+ * @return Returns the imports.
+ */
+ public List getImports()
+ {
+ return imports;
+ }
+
+ /**
+ * Find any formally declared import in the Constant Pool.
+ *
+ * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantClass(org.apache.bcel.classfile.ConstantClass)
+ */
+ public void visitConstantClass( ConstantClass constantClass )
+ {
+ String name = constantClass.getBytes( javaClass.getConstantPool() );
+
+ // only strings with '/' character are to be considered.
+ if ( name.indexOf( '/' ) == -1 )
+ {
+ return;
+ }
+
+ name = name.replace( '/', '.' );
+
+ if ( name.endsWith( ".class" ) )
+ {
+ name = name.substring( 0, name.length() - 6 );
+ }
+
+ Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher( name );
+ if ( mat.find() )
+ {
+ this.imports.add( mat.group( 1 ) );
+ }
+ else
+ {
+ this.imports.add( name );
+ }
+ }
+
+ /**
+ * Find any package class Strings in the UTF8 String Pool.
+ *
+ * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantUtf8(org.apache.bcel.classfile.ConstantUtf8)
+ */
+ public void visitConstantUtf8( ConstantUtf8 constantUtf8 )
+ {
+ String ret = constantUtf8.getBytes().trim();
+
+ // empty strings are not class names.
+ if ( ret.length() <= 0 )
+ {
+ return;
+ }
+
+ // Only valid characters please.
+ if ( !VALID_UTF8_PATTERN.matcher( ret ).matches() )
+ {
+ return;
+ }
+
+ // only strings with '/' character are to be considered.
+ if ( ret.indexOf( '/' ) == -1 )
+ {
+ return;
+ }
+
+ // Strings that start with '/' are bad too
+ // Seen when Pool has regex patterns.
+ if ( ret.charAt( 0 ) == '/' )
+ {
+ return;
+ }
+
+ // Make string more class-like.
+ ret = ret.replace( '/', '.' );
+
+ // Double ".." indicates a bad class fail-fast.
+ // Seen when ConstantUTF8 Pool has regex patterns.
+ if ( ret.indexOf( ".." ) != -1 )
+ {
+ return;
+ }
+
+ Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher( ret );
+ char prefix = ret.charAt( 0 );
+
+ if ( prefix == '(' )
+ {
+ // A Method Declaration.
+
+ // Loop for each Qualified Class found.
+ while ( mat.find() )
+ {
+ this.imports.add( mat.group( 1 ) );
+ }
+ }
+ else
+ {
+ // A Variable Declaration.
+ if ( mat.find() )
+ {
+ // Add a UTF8 Qualified Class reference.
+ this.imports.add( mat.group( 1 ) );
+ }
+ else
+ {
+ // Add a simple Class reference.
+ this.imports.add( ret );
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/classes/JarClasses.java b/src/main/java/org/apache/maven/shared/jar/classes/JarClasses.java
new file mode 100644
index 0000000..b956d67
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/classes/JarClasses.java
@@ -0,0 +1,159 @@
+package org.apache.maven.shared.jar.classes;
+
+/*
+ * 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.
+ */
+
+import org.apache.commons.collections.list.SetUniqueList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Gathered facts about the classes within a JAR file.
+ *
+ * @see org.apache.maven.shared.jar.classes.JarClassesAnalysis#analyze(org.apache.maven.shared.jar.JarAnalyzer)
+ */
+public class JarClasses
+{
+ /**
+ * The list of imports in the classes in the JAR.
+ */
+ private List imports;
+
+ /**
+ * A list of packages represented by classes in the JAR.
+ */
+ private List packages;
+
+ /**
+ * A list of the classes that in the JAR.
+ */
+ private List classNames;
+
+ /**
+ * A list of methods within the classes in the JAR.
+ */
+ private List methods;
+
+ /**
+ * Whether the JAR contains any code with debug information. If there is a mix of debug and release code, this will
+ * still be true.
+ */
+ private boolean isDebugPresent;
+
+ /**
+ * The highest JVM revision available in any class files. While the JAR may work on earlier JVMs if particular
+ * classes are not used, this is the minimum JVM that guarantees compatibility.
+ */
+ private String jdkRevision;
+
+ /**
+ * Constructor to create an empty instance.
+ */
+ public JarClasses()
+ {
+ // Unique list decorators are used to ensure natural ordering is retained, the list interface is availble, and
+ // that duplicates are not entered.
+ imports = SetUniqueList.decorate( new ArrayList() );
+ packages = SetUniqueList.decorate( new ArrayList() );
+ classNames = SetUniqueList.decorate( new ArrayList() );
+ methods = SetUniqueList.decorate( new ArrayList() );
+ }
+
+ /**
+ * Add a discovered class to the record.
+ *
+ * @param name the name of the class
+ */
+ public void addClassName( String name )
+ {
+ this.classNames.add( name );
+ }
+
+ /**
+ * Add a discovered package to the record.
+ *
+ * @param name the name of the package
+ */
+ public void addPackage( String name )
+ {
+ this.packages.add( name );
+ }
+
+ /**
+ * Add a discovered method to the record.
+ *
+ * @param name the name of the method
+ */
+ public void addMethod( String name )
+ {
+ this.methods.add( name );
+ }
+
+ /**
+ * Add a list of discovered imports to the record.
+ *
+ * @param imports the imports to add. Each item should be a String to avoid down the line ClassCastExceptions.
+ */
+ public void addImports( List imports )
+ {
+ this.imports.addAll( imports );
+ }
+
+ public List getImports()
+ {
+ return Collections.unmodifiableList( imports );
+ }
+
+ public List getClassNames()
+ {
+ return Collections.unmodifiableList( classNames );
+ }
+
+ public List getPackages()
+ {
+ return Collections.unmodifiableList( packages );
+ }
+
+ public boolean isDebugPresent()
+ {
+ return isDebugPresent;
+ }
+
+ public void setDebugPresent( boolean hasDebugSymbols )
+ {
+ this.isDebugPresent = hasDebugSymbols;
+ }
+
+ public String getJdkRevision()
+ {
+ return jdkRevision;
+ }
+
+ public void setJdkRevision( String jdkRevision )
+ {
+ this.jdkRevision = jdkRevision;
+ }
+
+ public List getMethods()
+ {
+ return Collections.unmodifiableList( methods );
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java b/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java
new file mode 100644
index 0000000..8cecfe0
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java
@@ -0,0 +1,193 @@
+package org.apache.maven.shared.jar.classes;
+
+/*
+ * 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.
+ */
+
+import org.apache.bcel.classfile.ClassFormatException;
+import org.apache.bcel.classfile.ClassParser;
+import org.apache.bcel.classfile.DescendingVisitor;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.classfile.LineNumberTable;
+import org.apache.bcel.classfile.Method;
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarEntry;
+
+/**
+ * Analyze the classes in a JAR file. This class is thread safe and immutable as it retains no state.
+ * <p/>
+ * Note that you must first create an instance of {@link org.apache.maven.shared.jar.JarAnalyzer} - see its Javadoc for
+ * a typical use.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.classes.JarClassesAnalysis" role-hint="default"
+ * @see #analyze(org.apache.maven.shared.jar.JarAnalyzer)
+ */
+public class JarClassesAnalysis
+ extends AbstractLogEnabled
+{
+ private static final double JAVA_1_6_CLASS_VERSION = 50.0;
+
+ private static final double JAVA_1_5_CLASS_VERSION = 49.0;
+
+ private static final double JAVA_1_4_CLASS_VERSION = 47.0;
+
+ private static final double JAVA_1_3_CLASS_VERSION = 46.0;
+
+ private static final double JAVA_1_2_CLASS_VERSION = 45.65536;
+
+ private static final double JAVA_1_1_CLASS_VERSION = 45.3;
+
+ /**
+ * Analyze a JAR and find any classes and their details. Note that if the provided JAR analyzer has previously
+ * analyzed the JAR, the cached results will be returned. You must obtain a new JAR analyzer to the re-read the
+ * contents of the file.
+ *
+ * @param jarAnalyzer the JAR to analyze. This must not yet have been closed.
+ * @return the details of the classes found
+ */
+ public JarClasses analyze( JarAnalyzer jarAnalyzer )
+ {
+ JarClasses classes = jarAnalyzer.getJarData().getJarClasses();
+ if ( classes == null )
+ {
+ String jarfilename = jarAnalyzer.getFile().getAbsolutePath();
+ classes = new JarClasses();
+
+ List classList = jarAnalyzer.getClassEntries();
+
+ classes.setDebugPresent( false );
+
+ double maxVersion = 0.0;
+
+ Iterator it = classList.iterator();
+ while ( it.hasNext() )
+ {
+ JarEntry entry = (JarEntry) it.next();
+ String classname = entry.getName();
+
+ try
+ {
+ ClassParser classParser = new ClassParser( jarfilename, classname );
+
+ JavaClass javaClass = classParser.parse();
+
+ String classSignature = javaClass.getClassName();
+
+ if ( !classes.isDebugPresent() )
+ {
+ if ( hasDebugSymbols( javaClass ) )
+ {
+ classes.setDebugPresent( true );
+ }
+ }
+
+ double classVersion = javaClass.getMajor();
+ if ( javaClass.getMinor() > 0 )
+ {
+ classVersion = classVersion + 1 / (double) javaClass.getMinor();
+ }
+
+ if ( classVersion > maxVersion )
+ {
+ maxVersion = classVersion;
+ }
+
+ Method[] methods = javaClass.getMethods();
+ for ( int i = 0; i < methods.length; i++ )
+ {
+ classes.addMethod( classSignature + "." + methods[i].getName() + methods[i].getSignature() );
+ }
+
+ String classPackageName = javaClass.getPackageName();
+
+ classes.addClassName( classSignature );
+ classes.addPackage( classPackageName );
+
+ ImportVisitor importVisitor = new ImportVisitor( javaClass );
+ DescendingVisitor descVisitor = new DescendingVisitor( javaClass, importVisitor );
+ javaClass.accept( descVisitor );
+
+ classes.addImports( importVisitor.getImports() );
+ }
+ catch ( ClassFormatException e )
+ {
+ getLogger().warn( "Unable to process class " + classname + " in JarAnalyzer File " + jarfilename,
+ e );
+ }
+ catch ( IOException e )
+ {
+ getLogger().warn( "Unable to process JarAnalyzer File " + jarfilename, e );
+ }
+ }
+
+ // TODO: check these since they are > instead of >=
+ if ( maxVersion >= JAVA_1_6_CLASS_VERSION )
+ {
+ classes.setJdkRevision( "1.6" );
+ }
+ else if ( maxVersion >= JAVA_1_5_CLASS_VERSION )
+ {
+ classes.setJdkRevision( "1.5" );
+ }
+ else if ( maxVersion > JAVA_1_4_CLASS_VERSION )
+ {
+ classes.setJdkRevision( "1.4" );
+ }
+ else if ( maxVersion > JAVA_1_3_CLASS_VERSION )
+ {
+ classes.setJdkRevision( "1.3" );
+ }
+ else if ( maxVersion > JAVA_1_2_CLASS_VERSION )
+ {
+ classes.setJdkRevision( "1.2" );
+ }
+ else if ( maxVersion > JAVA_1_1_CLASS_VERSION )
+ {
+ classes.setJdkRevision( "1.1" );
+ }
+ else if ( maxVersion > 0 )
+ {
+ classes.setJdkRevision( "1.0" );
+ }
+
+ jarAnalyzer.getJarData().setJarClasses( classes );
+ }
+ return classes;
+ }
+
+ private boolean hasDebugSymbols( JavaClass javaClass )
+ {
+ boolean ret = false;
+ Method[] methods = javaClass.getMethods();
+ for ( int i = 0; i < methods.length; i++ )
+ {
+ LineNumberTable linenumbers = methods[i].getLineNumberTable();
+ if ( linenumbers != null && linenumbers.getLength() > 0 )
+ {
+ ret = true;
+ break;
+ }
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/JarIdentification.java b/src/main/java/org/apache/maven/shared/jar/identification/JarIdentification.java
new file mode 100644
index 0000000..97025d1
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/JarIdentification.java
@@ -0,0 +1,292 @@
+package org.apache.maven.shared.jar.identification;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Gathered Maven information about the JAR file. Stores both assumed/validated values and potential values.
+ *
+ * @see org.apache.maven.shared.jar.identification.JarIdentificationAnalysis#analyze(org.apache.maven.shared.jar.JarAnalyzer)
+ */
+public class JarIdentification
+{
+ /**
+ * The group ID derived or guessed from the list of potentials of the JAR.
+ */
+ private String groupId;
+
+ /**
+ * The artifact ID derived or guessed from the list of potentials of the JAR.
+ */
+ private String artifactId;
+
+ /**
+ * The version derived or guessed from the list of potentials of the JAR.
+ */
+ private String version;
+
+ /**
+ * The project name derived or guessed from the list of potentials of the JAR.
+ */
+ private String name;
+
+ /**
+ * The vendor (organization name) derived or guessed from the list of potentials of the JAR.
+ */
+ private String vendor;
+
+ /**
+ * The list of possible group IDs discovered.
+ */
+ private List potentialGroupIds = new ArrayList();
+
+ /**
+ * The list of possible artifact IDs discovered.
+ */
+ private List potentialArtifactIds = new ArrayList();
+
+ /**
+ * The list of possible versions discovered.
+ */
+ private List potentialVersions = new ArrayList();
+
+ /**
+ * The list of possible artifact names discovered.
+ */
+ private List potentialNames = new ArrayList();
+
+ /**
+ * The list of possible vendors discovered.
+ */
+ private List potentialVendors = new ArrayList();
+
+ /**
+ * Add a validated group ID.
+ *
+ * @param groupId the group ID discovered
+ */
+ public void addAndSetGroupId( String groupId )
+ {
+ if ( groupId != null )
+ {
+ this.groupId = groupId;
+ }
+
+ addGroupId( groupId );
+ }
+
+ /**
+ * Add a potential group ID.
+ *
+ * @param groupId the group ID discovered
+ */
+ public void addGroupId( String groupId )
+ {
+ addUnique( potentialGroupIds, groupId );
+ }
+
+ /**
+ * Add a validated artifact ID.
+ *
+ * @param artifactId the artifact ID discovered
+ */
+ public void addAndSetArtifactId( String artifactId )
+ {
+ if ( artifactId != null )
+ {
+ this.artifactId = artifactId;
+ }
+
+ addArtifactId( artifactId );
+ }
+
+ /**
+ * Add a potential artifact ID.
+ *
+ * @param artifactId the artifact ID discovered
+ */
+ public void addArtifactId( String artifactId )
+ {
+ addUnique( potentialArtifactIds, artifactId );
+ }
+
+ /**
+ * Add a validated version.
+ *
+ * @param version the version discovered
+ */
+ public void addAndSetVersion( String version )
+ {
+ if ( version != null )
+ {
+ this.version = version;
+ }
+
+ addVersion( version );
+ }
+
+ /**
+ * Add a potential version.
+ *
+ * @param version the version discovered
+ */
+ public void addVersion( String version )
+ {
+ addUnique( potentialVersions, version );
+ }
+
+ /**
+ * Add a validated vendor name.
+ *
+ * @param name the vendor name discovered
+ */
+ public void addAndSetVendor( String name )
+ {
+ if ( name != null )
+ {
+ vendor = name;
+ }
+
+ addVendor( name );
+ }
+
+ /**
+ * Add a potential vendor name.
+ *
+ * @param name the vendor name discovered
+ */
+ public void addVendor( String name )
+ {
+ addUnique( potentialVendors, name );
+ }
+
+ /**
+ * Add a validated artifact name.
+ *
+ * @param name the artifact name discovered
+ */
+ public void addAndSetName( String name )
+ {
+ if ( name != null )
+ {
+ this.name = name;
+ }
+
+ addName( name );
+ }
+
+ /**
+ * Add a potential artifact name.
+ *
+ * @param name the artifact name discovered
+ */
+ public void addName( String name )
+ {
+ addUnique( potentialNames, name );
+ }
+
+ private static void addUnique( List list, String value )
+ {
+ if ( value != null )
+ {
+ if ( !list.contains( value ) )
+ {
+ list.add( value );
+ }
+ }
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ public void setArtifactId( String artifactId )
+ {
+ this.artifactId = artifactId;
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public void setGroupId( String groupId )
+ {
+ this.groupId = groupId;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+ public String getVendor()
+ {
+ return vendor;
+ }
+
+ public void setVendor( String vendor )
+ {
+ this.vendor = vendor;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion( String version )
+ {
+ this.version = version;
+ }
+
+ public List getPotentialVersions()
+ {
+ return potentialVersions;
+ }
+
+ public List getPotentialNames()
+ {
+ return potentialNames;
+ }
+
+ public List getPotentialGroupIds()
+ {
+ return potentialGroupIds;
+ }
+
+ public List getPotentialArtifactIds()
+ {
+ return potentialArtifactIds;
+ }
+
+ public List getPotentialVendors()
+ {
+ return potentialVendors;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/JarIdentificationAnalysis.java b/src/main/java/org/apache/maven/shared/jar/identification/JarIdentificationAnalysis.java
new file mode 100644
index 0000000..1ea2051
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/JarIdentificationAnalysis.java
@@ -0,0 +1,164 @@
+package org.apache.maven.shared.jar.identification;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Analyze the JAR file to identify Maven artifact metadata. This class is thread safe and immutable as long as all
+ * provided exposers are, as it retains no state.
+ * <p/>
+ * If using Plexus, the class will use all available exposers in the container.
+ * <p/>
+ * If not using Plexus, the exposers must be set using {@link #setExposers(java.util.List)} before calling
+ * {@link #analyze(org.apache.maven.shared.jar.JarAnalyzer)}
+ * <p/>
+ * Note that you must first create an instance of {@link org.apache.maven.shared.jar.JarAnalyzer} - see its Javadoc for
+ * a typical use.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationAnalysis" role-hint="default"
+ */
+public class JarIdentificationAnalysis
+{
+ /**
+ * The Maven information exposers to use during identification.
+ *
+ * @plexus.requirement role="org.apache.maven.shared.jar.identification.JarIdentificationExposer"
+ */
+ private List exposers;
+
+ /**
+ * Analyze a JAR and find any associated Maven metadata. Note that if the provided JAR analyzer has previously
+ * analyzed the JAR, the cached results will be returned. You must obtain a new JAR analyzer to the re-read the
+ * contents of the file.
+ *
+ * @param jarAnalyzer the JAR to analyze. This must not yet have been closed.
+ * @return the Maven metadata discovered
+ */
+ public JarIdentification analyze( JarAnalyzer jarAnalyzer )
+ {
+ JarIdentification taxon = jarAnalyzer.getJarData().getJarIdentification();
+ if ( taxon != null )
+ {
+ return taxon;
+ }
+
+ taxon = new JarIdentification();
+
+ for ( Iterator i = exposers.iterator(); i.hasNext(); )
+ {
+ JarIdentificationExposer exposer = (JarIdentificationExposer) i.next();
+ exposer.expose( taxon, jarAnalyzer );
+ }
+
+ normalize( taxon );
+
+ jarAnalyzer.getJarData().setJarIdentification( taxon );
+
+ return taxon;
+ }
+
+ private void normalize( JarIdentification taxon )
+ {
+ if ( StringUtils.isEmpty( taxon.getGroupId() ) )
+ {
+ taxon.setGroupId( pickSmallest( taxon.getPotentialGroupIds() ) );
+ }
+
+ if ( StringUtils.isEmpty( taxon.getArtifactId() ) )
+ {
+ taxon.setArtifactId( pickLargest( taxon.getPotentialArtifactIds() ) );
+ }
+
+ if ( StringUtils.isEmpty( taxon.getVersion() ) )
+ {
+ taxon.setVersion( pickSmallest( taxon.getPotentialVersions() ) );
+ }
+
+ if ( StringUtils.isEmpty( taxon.getName() ) )
+ {
+ taxon.setName( pickLargest( taxon.getPotentialNames() ) );
+ }
+
+ if ( StringUtils.isEmpty( taxon.getVendor() ) )
+ {
+ taxon.setVendor( pickLargest( taxon.getPotentialVendors() ) );
+ }
+ }
+
+ private String pickSmallest( List list )
+ {
+ String smallest = null;
+
+ if ( !list.isEmpty() )
+ {
+ int size = Integer.MAX_VALUE;
+ Iterator it = list.iterator();
+ while ( it.hasNext() )
+ {
+ String val = (String) it.next();
+
+ if ( StringUtils.isNotEmpty( val ) )
+ {
+ if ( val.length() < size )
+ {
+ smallest = val;
+ size = val.length();
+ }
+ }
+ }
+ }
+
+ return smallest;
+ }
+
+ private String pickLargest( List list )
+ {
+ String largest = null;
+ if ( !list.isEmpty() )
+ {
+ int size = Integer.MIN_VALUE;
+ Iterator it = list.iterator();
+ while ( it.hasNext() )
+ {
+ String val = (String) it.next();
+ if ( StringUtils.isNotEmpty( val ) )
+ {
+ if ( val.length() > size )
+ {
+ largest = val;
+ size = val.length();
+ }
+ }
+ }
+ }
+ return largest;
+ }
+
+ public void setExposers( List exposers )
+ {
+ this.exposers = Collections.unmodifiableList( exposers );
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/JarIdentificationExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/JarIdentificationExposer.java
new file mode 100644
index 0000000..8321a7f
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/JarIdentificationExposer.java
@@ -0,0 +1,39 @@
+package org.apache.maven.shared.jar.identification;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+
+/**
+ * Expose information from a JAR during the identification process. Implementations should be made to be thread safe.
+ *
+ * @see org.apache.maven.shared.jar.identification.JarIdentificationAnalysis
+ */
+public interface JarIdentificationExposer
+{
+ /**
+ * Expose metadata during the identification process.
+ *
+ * @param identification the identification record to populate with the exposed metadata
+ * @param jarAnalyzer the JAR to obtain the information from. Should be treated as read only, with the exception
+ * of caching the metadata if it would be identical when run over the same file again.
+ */
+ void expose( JarIdentification identification, JarAnalyzer jarAnalyzer );
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/EmbeddedMavenModelExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/EmbeddedMavenModelExposer.java
new file mode 100644
index 0000000..5f44182
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/EmbeddedMavenModelExposer.java
@@ -0,0 +1,101 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Organization;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.jar.JarEntry;
+
+
+/**
+ * Exposer that examines a JAR file for any embedded Maven metadata for identification.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="embeddedMavenModel"
+ */
+public class EmbeddedMavenModelExposer
+ extends AbstractLogEnabled
+ implements JarIdentificationExposer
+{
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ List entries = jarAnalyzer.getMavenPomEntries();
+ if ( entries.isEmpty() )
+ {
+ return;
+ }
+
+ if ( entries.size() > 1 )
+ {
+ getLogger().warn(
+ "More than one Maven model entry was found in the JAR, using only the first of: " + entries );
+ }
+
+ JarEntry pom = (JarEntry) entries.get( 0 );
+ MavenXpp3Reader pomreader = new MavenXpp3Reader();
+ InputStream is = null;
+ try
+ {
+ is = jarAnalyzer.getEntryInputStream( pom );
+ InputStreamReader isreader = new InputStreamReader( is );
+ Model model = pomreader.read( isreader );
+
+ identification.addAndSetGroupId( model.getGroupId() );
+ identification.addAndSetArtifactId( model.getArtifactId() );
+ identification.addAndSetVersion( model.getVersion() );
+ identification.addAndSetName( model.getName() );
+
+ // TODO: suboptimal - we are reproducing Maven's built in default
+ if ( model.getName() == null )
+ {
+ identification.addAndSetName( model.getArtifactId() );
+ }
+
+ Organization org = model.getOrganization();
+ if ( org != null )
+ {
+ identification.addAndSetVendor( org.getName() );
+ }
+ }
+ catch ( IOException e )
+ {
+ getLogger().error( "Unable to read model " + pom.getName() + " in " + jarAnalyzer.getFile() + ".", e );
+ }
+ catch ( XmlPullParserException e )
+ {
+ getLogger().error( "Unable to parse model " + pom.getName() + " in " + jarAnalyzer.getFile() + ".", e );
+ }
+ finally
+ {
+ IOUtil.close( is );
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/FilenameExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/FilenameExposer.java
new file mode 100644
index 0000000..a75d05e
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/FilenameExposer.java
@@ -0,0 +1,59 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+import org.codehaus.plexus.util.FileUtils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Exposer that examines a JAR file to derive Maven metadata from the pattern of the JAR's filename.
+ * Will match the format <i>artifactId</i>-<i>version</i>.jar.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="filename"
+ */
+public class FilenameExposer
+ implements JarIdentificationExposer
+{
+ private static final Pattern VERSION_PATTERN = Pattern.compile( "-[0-9]" );
+
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ String filename = FileUtils.removeExtension( jarAnalyzer.getFile().getName() );
+ Matcher mat = VERSION_PATTERN.matcher( filename );
+ if ( mat.find() )
+ {
+ String prefix = filename.substring( 0, mat.start() );
+ identification.addArtifactId( prefix );
+ identification.addName( prefix );
+ identification.addVersion( filename.substring( mat.end() - 1 ) );
+ }
+ else
+ {
+ identification.addArtifactId( filename );
+ identification.addName( filename );
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/JarClassesExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/JarClassesExposer.java
new file mode 100644
index 0000000..2da4cce
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/JarClassesExposer.java
@@ -0,0 +1,64 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.classes.JarClasses;
+import org.apache.maven.shared.jar.classes.JarClassesAnalysis;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+
+import java.util.Iterator;
+
+
+/**
+ * Exposer that examines a JAR file to derive Maven metadata from the classes in a JAR. It will currently identify
+ * potential group IDs from the class packages.
+ * <p/>
+ * Note: if not being used from Plexus, the {@link #setAnalyzer(org.apache.maven.shared.jar.classes.JarClassesAnalysis)}
+ * method must be called to avoid a NullPointerException during the expose method.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="jarClasses"
+ */
+public class JarClassesExposer
+ implements JarIdentificationExposer
+{
+ /**
+ * @plexus.requirement
+ */
+ private JarClassesAnalysis analyzer;
+
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ JarClasses jarclasses = analyzer.analyze( jarAnalyzer );
+
+ Iterator it = jarclasses.getPackages().iterator();
+ while ( it.hasNext() )
+ {
+ String packagename = (String) it.next();
+ identification.addGroupId( packagename );
+ }
+ }
+
+ public void setAnalyzer( JarClassesAnalysis analyzer )
+ {
+ this.analyzer = analyzer;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/ManifestExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/ManifestExposer.java
new file mode 100644
index 0000000..c8de717
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/ManifestExposer.java
@@ -0,0 +1,70 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * Exposer that examines a JAR's manifest to derive Maven metadata.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="manifest"
+ */
+public class ManifestExposer
+ implements JarIdentificationExposer
+{
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ Manifest manifest = jarAnalyzer.getJarData().getManifest();
+ if ( manifest != null )
+ {
+ addManifestAttributeValues( manifest.getMainAttributes(), identification );
+
+ Map entries = manifest.getEntries();
+ Iterator itentries = entries.entrySet().iterator();
+ while ( itentries.hasNext() )
+ {
+ Map.Entry entry = (Map.Entry) itentries.next();
+ Attributes attribs = (Attributes) entry.getValue();
+
+ addManifestAttributeValues( attribs, identification );
+ }
+ }
+ }
+
+ private void addManifestAttributeValues( Attributes attribs, JarIdentification identification )
+ {
+ identification.addName( attribs.getValue( Attributes.Name.IMPLEMENTATION_TITLE ) );
+ identification.addVersion( attribs.getValue( Attributes.Name.IMPLEMENTATION_VERSION ) );
+ identification.addVendor( attribs.getValue( Attributes.Name.IMPLEMENTATION_VENDOR ) );
+
+ identification.addName( attribs.getValue( Attributes.Name.SPECIFICATION_TITLE ) );
+ identification.addVersion( attribs.getValue( Attributes.Name.SPECIFICATION_VERSION ) );
+ identification.addVendor( attribs.getValue( Attributes.Name.SPECIFICATION_VENDOR ) );
+
+ identification.addGroupId( attribs.getValue( Attributes.Name.EXTENSION_NAME ) );
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/RepositorySearchExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/RepositorySearchExposer.java
new file mode 100644
index 0000000..0c74c32
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/RepositorySearchExposer.java
@@ -0,0 +1,115 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+import org.apache.maven.shared.jar.identification.hash.JarHashAnalyzer;
+import org.apache.maven.shared.jar.identification.repository.RepositoryHashSearch;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Exposer that examines a Maven repository for identical files to the JAR being analyzed. It will look for both
+ * identical files, and files with identical classes.
+ * <p/>
+ * Note: if not using Plexus, you must call the following methods to be able to expose any data from the class:
+ * {@link #setBytecodeHashAnalyzer(org.apache.maven.shared.jar.identification.hash.JarHashAnalyzer)},
+ * {@link #setFileHashAnalyzer(org.apache.maven.shared.jar.identification.hash.JarHashAnalyzer)},
+ * {@link #setRepositoryHashSearch(org.apache.maven.shared.jar.identification.repository.RepositoryHashSearch)}
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="repositorySearch"
+ */
+public class RepositorySearchExposer
+ extends AbstractLogEnabled
+ implements JarIdentificationExposer
+{
+ /**
+ * The repository searcher to use.
+ *
+ * @plexus.requirement
+ * @todo this currently only provides for the 'empty' repository search, which isn't very useful
+ */
+ private RepositoryHashSearch repositoryHashSearch;
+
+ /**
+ * The hash analyzer for the entire file.
+ *
+ * @plexus.requirement role-hint="file"
+ */
+ private JarHashAnalyzer fileHashAnalyzer;
+
+ /**
+ * The hash analyzer for the file's bytecode.
+ *
+ * @plexus.requirement role-hint="bytecode"
+ */
+ private JarHashAnalyzer bytecodeHashAnalyzer;
+
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ List repohits = new ArrayList();
+
+ String hash = fileHashAnalyzer.computeHash( jarAnalyzer );
+ if ( hash != null )
+ {
+ repohits.addAll( repositoryHashSearch.searchFileHash( hash ) );
+ }
+
+ String bytecodehash = bytecodeHashAnalyzer.computeHash( jarAnalyzer );
+ if ( bytecodehash != null )
+ {
+ repohits.addAll( repositoryHashSearch.searchBytecodeHash( bytecodehash ) );
+ }
+
+ if ( !repohits.isEmpty() )
+ {
+ // Found hits in the repository.
+ Iterator it = repohits.iterator();
+ while ( it.hasNext() )
+ {
+ Artifact artifact = (Artifact) it.next();
+ identification.addAndSetGroupId( artifact.getGroupId() );
+ identification.addAndSetArtifactId( artifact.getArtifactId() );
+ identification.addAndSetVersion( artifact.getVersion() );
+ }
+ }
+ }
+
+ public void setRepositoryHashSearch( RepositoryHashSearch repo )
+ {
+ this.repositoryHashSearch = repo;
+ }
+
+ public void setFileHashAnalyzer( JarHashAnalyzer fileHashAnalyzer )
+ {
+ this.fileHashAnalyzer = fileHashAnalyzer;
+ }
+
+ public void setBytecodeHashAnalyzer( JarHashAnalyzer bytecodeHashAnalyzer )
+ {
+ this.bytecodeHashAnalyzer = bytecodeHashAnalyzer;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/StaticMainOutputExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/StaticMainOutputExposer.java
new file mode 100644
index 0000000..ec2ef22
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/StaticMainOutputExposer.java
@@ -0,0 +1,59 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Exposer that examines a a JAR for classes that have <code>Version</code> in the name and calls their
+ * <code>main</code> method if it exists to obtain the version.
+ *
+ * @todo not currently implemented
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="staticMainOutput"
+ */
+public class StaticMainOutputExposer
+ implements JarIdentificationExposer
+{
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ List staticMains = findStaticMainVersions();
+ if ( !staticMains.isEmpty() )
+ {
+ Iterator itvers = staticMains.iterator();
+ while ( itvers.hasNext() )
+ {
+ String ver = (String) itvers.next();
+ identification.addVersion( ver );
+ }
+ }
+ }
+
+ private List findStaticMainVersions()
+ {
+ // TODO: Execute the static main methods of classes with 'Version' in their name.
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/TextFileExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/TextFileExposer.java
new file mode 100644
index 0000000..625346b
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/TextFileExposer.java
@@ -0,0 +1,105 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarEntry;
+
+
+/**
+ * Exposer that examines a a JAR for files that contain the text <code>version</code> (case-insensitive) and
+ * adds the contents as potential version(s).
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="textFile"
+ */
+public class TextFileExposer
+ extends AbstractLogEnabled
+ implements JarIdentificationExposer
+{
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ List textFiles = findTextFileVersions( jarAnalyzer );
+ if ( !textFiles.isEmpty() )
+ {
+ Iterator ithits = textFiles.iterator();
+ while ( ithits.hasNext() )
+ {
+ String ver = (String) ithits.next();
+ identification.addVersion( ver );
+ }
+ }
+ }
+
+ private List findTextFileVersions( JarAnalyzer jarAnalyzer )
+ {
+ List textVersions = new ArrayList();
+ List hits = jarAnalyzer.getVersionEntries();
+
+ Iterator it = hits.iterator();
+ while ( it.hasNext() )
+ {
+ JarEntry entry = (JarEntry) it.next();
+
+ // skip this entry if it's a class file.
+ if ( !entry.getName().endsWith( ".class" ) ) //$NON-NLS-1$
+ {
+ getLogger().debug( "Version Hit: " + entry.getName() );
+ InputStream is = null;
+ try
+ {
+ is = jarAnalyzer.getEntryInputStream( entry );
+ BufferedReader br = new BufferedReader( new InputStreamReader( is ) );
+
+ String line = br.readLine();
+ // TODO: check for key=value pair.
+ // TODO: maybe even for groupId entries.
+
+ getLogger().debug( line );
+ if ( StringUtils.isNotEmpty( line ) )
+ {
+ textVersions.add( line );
+ }
+ }
+ catch ( IOException e )
+ {
+ getLogger().warn( "Unable to read line from " + entry.getName(), e );
+ }
+ finally
+ {
+ IOUtil.close( is );
+ }
+ }
+ }
+ return textVersions;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/exposers/TimestampExposer.java b/src/main/java/org/apache/maven/shared/jar/identification/exposers/TimestampExposer.java
new file mode 100644
index 0000000..df97e07
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/exposers/TimestampExposer.java
@@ -0,0 +1,77 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.commons.collections.Bag;
+import org.apache.commons.collections.bag.HashBag;
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+import org.apache.maven.shared.jar.identification.JarIdentificationExposer;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+
+/**
+ * Exposer that examines a a JAR and uses the most recent timestamp as a potential version.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.JarIdentificationExposer" role-hint="timestamp"
+ */
+public class TimestampExposer
+ implements JarIdentificationExposer
+{
+ public void expose( JarIdentification identification, JarAnalyzer jarAnalyzer )
+ {
+ List entries = jarAnalyzer.getEntries();
+ SimpleDateFormat tsformat = new SimpleDateFormat( "yyyyMMdd", Locale.US ); //$NON-NLS-1$
+ Bag timestamps = new HashBag();
+ Iterator it = entries.iterator();
+ while ( it.hasNext() )
+ {
+ JarEntry entry = (JarEntry) it.next();
+ long time = entry.getTime();
+ String timestamp = tsformat.format( new Date( time ) );
+ timestamps.add( timestamp );
+ }
+
+ it = timestamps.iterator();
+ String ts = "";
+ int tsmax = 0;
+ while ( it.hasNext() )
+ {
+ String timestamp = (String) it.next();
+ int count = timestamps.getCount( timestamp );
+ if ( count > tsmax )
+ {
+ ts = timestamp;
+ tsmax = count;
+ }
+ }
+
+ if ( StringUtils.isNotEmpty( ts ) )
+ {
+ identification.addVersion( ts );
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/hash/JarBytecodeHashAnalyzer.java b/src/main/java/org/apache/maven/shared/jar/identification/hash/JarBytecodeHashAnalyzer.java
new file mode 100644
index 0000000..4fe9d47
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/hash/JarBytecodeHashAnalyzer.java
@@ -0,0 +1,102 @@
+package org.apache.maven.shared.jar.identification.hash;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.JarData;
+import org.codehaus.plexus.digest.DigesterException;
+import org.codehaus.plexus.digest.StreamingDigester;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.util.IOUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.jar.JarEntry;
+
+/**
+ * Analyzer that calculates the hash code for the entire file. Can be used to detect an exact copy of the file's class
+ * data. Useful to see thru a recompile, recompression, or timestamp change.
+ * <p/>
+ * If you are not using Plexus, you must call {@link #setDigester(org.codehaus.plexus.digest.StreamingDigester)} before use
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.hash.JarHashAnalyzer" role-hint="bytecode"
+ */
+public class JarBytecodeHashAnalyzer
+ extends AbstractLogEnabled
+ implements JarHashAnalyzer
+{
+ /**
+ * The streaming digester to use for computing the hash. Under Plexus, the default is SHA-1.
+ *
+ * @plexus.requirement role-hint="sha1"
+ */
+ private StreamingDigester digester;
+
+ public String computeHash( JarAnalyzer jarAnalyzer )
+ {
+ JarData jarData = jarAnalyzer.getJarData();
+
+ String result = jarData.getBytecodeHash();
+ if ( result == null )
+ {
+ Iterator it = jarAnalyzer.getClassEntries().iterator();
+
+ try
+ {
+ digester.reset();
+ while ( it.hasNext() )
+ {
+ JarEntry entry = (JarEntry) it.next();
+ computeEntryBytecodeHash( jarAnalyzer.getEntryInputStream( entry ) );
+ }
+ result = digester.calc();
+ jarData.setBytecodeHash( result );
+ }
+ catch ( DigesterException e )
+ {
+ getLogger().warn( "Unable to calculate the hashcode.", e );
+ }
+ catch ( IOException e )
+ {
+ getLogger().warn( "Unable to calculate the hashcode.", e );
+ }
+ }
+ return result;
+ }
+
+ private void computeEntryBytecodeHash( InputStream is )
+ throws IOException, DigesterException
+ {
+ try
+ {
+ digester.update( is );
+ }
+ finally
+ {
+ IOUtil.close( is );
+ }
+ }
+
+ public void setDigester( StreamingDigester digester )
+ {
+ this.digester = digester;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/hash/JarFileHashAnalyzer.java b/src/main/java/org/apache/maven/shared/jar/identification/hash/JarFileHashAnalyzer.java
new file mode 100644
index 0000000..33cb122
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/hash/JarFileHashAnalyzer.java
@@ -0,0 +1,70 @@
+package org.apache.maven.shared.jar.identification.hash;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.JarData;
+import org.codehaus.plexus.digest.Digester;
+import org.codehaus.plexus.digest.DigesterException;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+/**
+ * Analyzer that calculates the hash code for the entire file. Can be used to detect an exact copy of the file.
+ * <p/>
+ * If you are not using Plexus, you must call {@link #setDigester(org.codehaus.plexus.digest.Digester)} before use
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.hash.JarHashAnalyzer" role-hint="file"
+ */
+public class JarFileHashAnalyzer
+ extends AbstractLogEnabled
+ implements JarHashAnalyzer
+{
+ /**
+ * The digester to use for computing the hash. Under Plexus, the default is SHA-1.
+ *
+ * @plexus.requirement role-hint="sha1"
+ */
+ private Digester digester;
+
+ public String computeHash( JarAnalyzer jarAnalyzer )
+ {
+ JarData jarData = jarAnalyzer.getJarData();
+
+ String result = jarData.getFileHash();
+ if ( result == null )
+ {
+ try
+ {
+ result = digester.calc( jarData.getFile() );
+ jarData.setFileHash( result );
+ }
+ catch ( DigesterException e )
+ {
+ getLogger().warn( "Unable to calculate the hashcode.", e );
+ }
+ }
+ return result;
+ }
+
+ public void setDigester( Digester digester )
+ {
+ this.digester = digester;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/hash/JarHashAnalyzer.java b/src/main/java/org/apache/maven/shared/jar/identification/hash/JarHashAnalyzer.java
new file mode 100644
index 0000000..44e3bcd
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/hash/JarHashAnalyzer.java
@@ -0,0 +1,36 @@
+package org.apache.maven.shared.jar.identification.hash;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.JarAnalyzer;
+
+/**
+ * Classes that can calculate various hash signatures for a JAR file to later uniquely identify them.
+ */
+public interface JarHashAnalyzer
+{
+ /**
+ * Compute the hash for the JAR. The hashcode will then be cached in the JAR data class for later use.
+ *
+ * @param jarAnalyzer the JAR analyzer to use to obtain the entries to hash
+ * @return the hash, or null if not able to be computed due to an exception.
+ */
+ String computeHash( JarAnalyzer jarAnalyzer );
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/repository/EmptyRepositoryHashSearch.java b/src/main/java/org/apache/maven/shared/jar/identification/repository/EmptyRepositoryHashSearch.java
new file mode 100644
index 0000000..8e8f4a6
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/repository/EmptyRepositoryHashSearch.java
@@ -0,0 +1,46 @@
+package org.apache.maven.shared.jar.identification.repository;
+
+/*
+ * 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.
+ */
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Empty repository hash search. Always returns an empty list.
+ * <p/>
+ * Used for local only implementation of a RepositoryHashSearch. It is expected for the users of this library to provide
+ * an implementation of a {@link org.apache.maven.shared.jar.identification.repository.RepositoryHashSearch} against a
+ * real repository.
+ *
+ * @plexus.component role="org.apache.maven.shared.jar.identification.repository.RepositoryHashSearch" role-hint="empty"
+ */
+public class EmptyRepositoryHashSearch
+ implements RepositoryHashSearch
+{
+ public List searchBytecodeHash( String hash )
+ {
+ return Collections.EMPTY_LIST;
+ }
+
+ public List searchFileHash( String hash )
+ {
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/identification/repository/RepositoryHashSearch.java b/src/main/java/org/apache/maven/shared/jar/identification/repository/RepositoryHashSearch.java
new file mode 100644
index 0000000..cedefab
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/identification/repository/RepositoryHashSearch.java
@@ -0,0 +1,45 @@
+package org.apache.maven.shared.jar.identification.repository;
+
+/*
+ * 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.
+ */
+
+import java.util.List;
+
+/**
+ * Interface for Repository Hash Searches.
+ */
+public interface RepositoryHashSearch
+{
+ /**
+ * Search the repository for artifacts matching the given hash code when consider the entire contents of the file.
+ *
+ * @param hash the hash code to use
+ * @return a list of {@link org.apache.maven.artifact.Artifact} instances that matched
+ */
+ List searchFileHash( String hash );
+
+ /**
+ * Search the repository for artifacts matching the given hash code when consider the bytecode of the classes in the
+ * file.
+ *
+ * @param hash the hash code to use
+ * @return a list of {@link org.apache.maven.artifact.Artifact} instances that matched
+ */
+ List searchBytecodeHash( String hash );
+}
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
new file mode 100644
index 0000000..6212412
--- /dev/null
+++ b/src/main/javadoc/overview.html
@@ -0,0 +1,119 @@
+<!--
+ ~ 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.
+ -->
+
+<html>
+<head>
+ <title>Maven Shared Components: Jar Analyzer</title>
+</head>
+<body>
+
+<h1>Maven Shared Components: Jar Analyzer</h1>
+
+<p>
+ The Maven Jar Analyzer components can be used to gather various pieces of information about a given JAR file.
+
+ Currently, the following operations are supported:
+</p>
+<ul>
+ <li><a href="#Identification">Maven Artifact Identification</a> - examine the JAR and its contents to try and
+ determine any associated Maven metadata such as group and artifact ID.
+ </li>
+ <li><a href="#ClassAnalysis">Java Class Analysis</a> - examine ther JAR's class file contents to determine various
+ pieces of Java metadata.
+ </li>
+</ul>
+
+<a name="Identification"><h2>Maven Artifact Identification</h2></a>
+
+<p>
+ To determine the Maven artifact metadata for a particular JAR, the
+ {@link org.apache.maven.shared.jar.identification.JarIdentificationAnalysis} class is used.
+ The class can be obtained through Plexus, or created standalone (in which case, see the class Javadoc for
+ information on proper wiring).
+</p>
+
+<p>
+ Note that to use the class, you must first obtain a {@link org.apache.maven.shared.jar.JarAnalyzer} instance as
+ instructed in its Javadoc.
+</p>
+
+<p>
+ The resulting information will be populated in the returned
+ {@link org.apache.maven.shared.jar.identification.JarIdentification} class.
+</p>
+
+<p>
+ Example usage:
+ <pre>
+ JarAnalyzer jar = new JarAnalyzer( jarFile );
+ JarIdenfitication jarIdenfitication;
+ try
+ {
+ // instance must have been previously obtained through Plexus or created as instructed in its Javadoc
+ jarIdenfitication = jarIdenfiticationAnalyzer.analyze( jar );
+ }
+ finally
+ {
+ jar.closeQuietly();
+ }
+
+ // continue to use jarIdenfitication or jar.getJarData()
+ </pre>
+</p>
+
+<a name="ClassAnalysis"><h2>Java Class Analysis</h2></a>
+
+<p>
+ To determine the Java class metadata for a particular JAR, the
+ {@link org.apache.maven.shared.jar.classes.JarClassesAnalysis} class is used.
+ The class can be obtained through Plexus, or created standalone (in which case, see the class Javadoc for
+ information on proper wiring).
+</p>
+
+<p>
+ Note that to use the class, you must first obtain a {@link org.apache.maven.shared.jar.JarAnalyzer} instance as
+ instructed in its Javadoc.
+</p>
+
+<p>
+ The resulting information will be populated in the returned
+ {@link org.apache.maven.shared.jar.classes.JarClasses} class.
+</p>
+
+<p>
+ Example usage:
+ <pre>
+ JarAnalyzer jar = new JarAnalyzer( jarFile );
+ JarClasses jarClasses;
+ try
+ {
+ // instance must have been previously obtained through Plexus or created as instructed in its Javadoc
+ jarClasses = jarClassAnalyzer.analyze( jar );
+ }
+ finally
+ {
+ jar.closeQuietly();
+ }
+
+ // continue to use jarClasses or jar.getJarData()
+ </pre>
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/site/apt/index.apt b/src/site/apt/index.apt
new file mode 100644
index 0000000..aab6c38
--- /dev/null
+++ b/src/site/apt/index.apt
@@ -0,0 +1,15 @@
+ -----
+ JAR Utilities
+ -----
+ Brett Porter
+ -----
+ 2 July 2007
+ -----
+
+Maven Shared Components - JAR Utilities
+
+ Utilities that help identify the contents of a JAR, including Java class analysis and Maven metadata analysis.
+
+* Using the Utilities
+
+ Instructions on how to use the utilities can be found in the {{{apidocs/index.html} Javadoc Overview}}.
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..5d7c5e7
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+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.
+-->
+
+<project>
+ <!-- TODO: banners, skin, publish date, version should be inherited from Maven itself -->
+ <bannerLeft>
+ <name>${project.name}</name>
+ <src>http://maven.apache.org/images/apache-maven-project-2.png</src>
+ <href>http://maven.apache.org/</href>
+ </bannerLeft>
+ <bannerRight>
+ <src>http://maven.apache.org/images/maven-logo-2.gif</src>
+ </bannerRight>
+ <skin>
+ <groupId>org.apache.maven.skins</groupId>
+ <artifactId>maven-stylus-skin</artifactId>
+ </skin>
+ <publishDate format="dd MMM yyyy" position="left" />
+ <version position="left" />
+ <body>
+ <!-- TODO: Link, head, reports should be inherited -->
+ <!-- TODO: use breadcrumbs more structure, links for links, and inherit subprojects as a menu or not at all -->
+ <links>
+ <item name="Apache" href="http://www.apache.org/"/>
+ <item name="Maven 1.x" href="http://maven.apache.org/maven-1.x"/>
+ <item name="Maven 2.x" href="http://maven.apache.org/"/>
+ <item name="Maven 2.x Plugins" href="http://maven.apache.org/plugins/"/>
+ <item name="Continuum" href="http://maven.apache.org/continuum"/>
+ <item name="SCM" href="http://maven.apache.org/scm"/>
+ <item name="Wagon" href="http://maven.apache.org/wagon"/>
+ <item name="JXR" href="http://maven.apache.org/jxr"/>
+ <item name="Doxia" href="http://maven.apache.org/doxia"/>
+ </links>
+ <head>
+ <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+ </script>
+ <script type="text/javascript">
+ _uacct = "UA-140879-1";
+ urchinTracker();
+ </script>
+ </head>
+ <menu name="Usage">
+ <item name="Overview" href="apidocs/index.html" />
+ </menu>
+ <menu ref="reports" inherit="bottom" />
+ </body>
+</project>
diff --git a/src/test/java/org/apache/maven/shared/jar/AbstractJarAnalyzerTestCase.java b/src/test/java/org/apache/maven/shared/jar/AbstractJarAnalyzerTestCase.java
new file mode 100644
index 0000000..ecc4a81
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/jar/AbstractJarAnalyzerTestCase.java
@@ -0,0 +1,73 @@
+package org.apache.maven.shared.jar;
+
+/*
+ * 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.
+ */
+
+import junit.framework.AssertionFailedError;
+import org.codehaus.plexus.PlexusTestCase;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Abstract JarAnalyzer TestCase
+ */
+public abstract class AbstractJarAnalyzerTestCase
+ extends PlexusTestCase
+{
+ public File getSampleJar( String filename )
+ throws MalformedURLException
+ {
+ return new File( getClass().getResource( "/jars/" + filename ).getPath() );
+ }
+
+ public void assertNotContainsRegex( String msg, String regex, Collection coll )
+ {
+ List failures = new ArrayList();
+ Pattern pat = Pattern.compile( regex );
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ String value = (String) it.next();
+ Matcher mat = pat.matcher( value );
+ if ( mat.find() )
+ {
+ failures.add( value );
+ }
+ }
+
+ if ( !failures.isEmpty() )
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append( msg ).append( " collection has illegal regex \"" ).append( regex ).append( "\"" );
+ it = failures.iterator();
+ while ( it.hasNext() )
+ {
+ sb.append( "\n - \"" ).append( it.next() ).append( "\"" );
+ }
+ throw new AssertionFailedError( sb.toString() );
+ }
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java b/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java
new file mode 100644
index 0000000..58d3fd9
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java
@@ -0,0 +1,111 @@
+package org.apache.maven.shared.jar;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.zip.ZipException;
+
+/*
+ * 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.
+ */
+
+/**
+ * Tests for the JarAnalyzer class.
+ */
+public class JarAnalyzerTest
+ extends AbstractJarAnalyzerTestCase
+{
+ private JarAnalyzer jarAnalyzer;
+
+
+ protected void tearDown()
+ throws Exception
+ {
+ super.tearDown();
+
+ if ( jarAnalyzer != null )
+ {
+ jarAnalyzer.closeQuietly();
+ }
+ }
+
+ private JarData getJarData( String filename )
+ throws Exception
+ {
+ jarAnalyzer = getJarAnalyzer( filename );
+ return jarAnalyzer.getJarData();
+ }
+
+ private JarAnalyzer getJarAnalyzer( String filename )
+ throws IOException
+ {
+ return new JarAnalyzer( getSampleJar( filename ) );
+ }
+
+ public void testSealed()
+ throws Exception
+ {
+ JarData jarData = getJarData( "evil-sealed-regex-1.0.jar" );
+ assertTrue( jarData.isSealed() );
+ }
+
+ public void testNotSealed()
+ throws Exception
+ {
+ JarData jarData = getJarData( "codec.jar" );
+ assertFalse( jarData.isSealed() );
+ }
+
+ public void testMissingFile()
+ throws Exception
+ {
+ try
+ {
+ jarAnalyzer = new JarAnalyzer( new File( "foo-bar.jar" ) );
+ fail( "Should not have succeeded to get the missing JAR" );
+ }
+ catch ( ZipException e )
+ {
+ assertTrue( true );
+ }
+ }
+
+ public void testInvalidJarFile()
+ throws Exception
+ {
+ try
+ {
+ getJarAnalyzer( "invalid.jar" );
+ fail( "Should not have succeeded to get the invalid JAR" );
+ }
+ catch ( ZipException e )
+ {
+ assertTrue( true );
+ }
+ }
+
+ public void testCloseTwice()
+ throws Exception
+ {
+ JarAnalyzer jarAnalyzer = getJarAnalyzer( "codec.jar" );
+
+ // no exception should be thrown
+ jarAnalyzer.closeQuietly();
+ jarAnalyzer.closeQuietly();
+ assertTrue( true );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/jar/classes/ImportVisitorTest.java b/src/test/java/org/apache/maven/shared/jar/classes/ImportVisitorTest.java
new file mode 100644
index 0000000..130aedc
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/jar/classes/ImportVisitorTest.java
@@ -0,0 +1,81 @@
+package org.apache.maven.shared.jar.classes;
+
+/*
+ * 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.
+ */
+
+import org.apache.bcel.classfile.ClassFormatException;
+import org.apache.bcel.classfile.ClassParser;
+import org.apache.bcel.classfile.DescendingVisitor;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.maven.shared.jar.AbstractJarAnalyzerTestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+
+/**
+ * Import Visitor Test
+ */
+public class ImportVisitorTest
+ extends AbstractJarAnalyzerTestCase
+{
+ public void testImportsJxr()
+ throws ClassFormatException, IOException
+ {
+ File jxrjar = getSampleJar( "jxr.jar" );
+ String classname = "org/apache/maven/jxr/DirectoryIndexer.class";
+ ClassParser classParser = new ClassParser( jxrjar.getAbsolutePath(), classname );
+ JavaClass javaClass = classParser.parse();
+
+ ImportVisitor importVisitor = new ImportVisitor( javaClass );
+ DescendingVisitor descVisitor = new DescendingVisitor( javaClass, importVisitor );
+ javaClass.accept( descVisitor );
+
+ List imports = importVisitor.getImports();
+ assertNotNull( "Import List", imports );
+
+ assertNotContainsRegex( "Import List", "[\\[\\)\\(\\;]", imports );
+
+ assertTrue( "imports", imports.contains( "org.apache.maven.jxr.pacman.PackageType" ) );
+ assertTrue( "imports", imports.contains( "org.codehaus.plexus.util.IOUtil" ) );
+ assertTrue( "imports", imports.contains( "org.apache.oro.text.perl.Perl5Util" ) );
+ }
+
+ public void testImportsAnt()
+ throws ClassFormatException, IOException
+ {
+ File jxrjar = getSampleJar( "ant.jar" );
+ String classname = "org/apache/tools/ant/Target.class";
+ ClassParser classParser = new ClassParser( jxrjar.getAbsolutePath(), classname );
+ JavaClass javaClass = classParser.parse();
+
+ ImportVisitor importVisitor = new ImportVisitor( javaClass );
+ DescendingVisitor descVisitor = new DescendingVisitor( javaClass, importVisitor );
+ javaClass.accept( descVisitor );
+
+ List imports = importVisitor.getImports();
+ assertNotNull( "Import List", imports );
+
+ assertNotContainsRegex( "Import List", "[\\[\\)\\(\\;]", imports );
+
+ assertTrue( "imports", imports.contains( "org.apache.tools.ant.Location" ) );
+ assertTrue( "imports", imports.contains( "org.apache.tools.ant.Task" ) );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java b/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java
new file mode 100644
index 0000000..0c5e0a8
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java
@@ -0,0 +1,168 @@
+package org.apache.maven.shared.jar.classes;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.AbstractJarAnalyzerTestCase;
+import org.apache.maven.shared.jar.JarAnalyzer;
+
+import java.io.File;
+
+
+/**
+ * JarAnalyzer Classes Test Case
+ */
+public class JarClassesAnalyzerTest
+ extends AbstractJarAnalyzerTestCase
+{
+ private JarClassesAnalysis analyzer;
+
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ analyzer = (JarClassesAnalysis) lookup( JarClassesAnalysis.class.getName() );
+ }
+
+ public void testAnalyzeJXR()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "jxr.jar" );
+
+ assertFalse( "classes.imports.length > 0", jclass.getImports().isEmpty() );
+ assertFalse( "classes.packages.length > 0", jclass.getPackages().isEmpty() );
+ assertFalse( "classes.methods.length > 0", jclass.getMethods().isEmpty() );
+
+ assertNotContainsRegex( "Import List", "[\\[\\)\\(\\;]", jclass.getImports() );
+
+ // TODO: test for classes, methods, etc.
+
+ assertTrue( "classes.imports", jclass.getImports().contains( "org.apache.maven.jxr.JXR" ) );
+ assertTrue( "classes.imports", jclass.getImports().contains( "org.apache.oro.text.perl.Perl5Util" ) );
+ assertTrue( "classes.imports", jclass.getImports().contains( "org.codehaus.plexus.util.IOUtil" ) );
+ assertTrue( "classes.packages", jclass.getPackages().contains( "org.apache.maven.jxr.pacman" ) );
+ }
+
+ public void testAnalyzeANT()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "ant.jar" );
+
+ assertFalse( "classes.imports.length > 0", jclass.getImports().isEmpty() );
+ assertFalse( "classes.packages.length > 0", jclass.getPackages().isEmpty() );
+ assertFalse( "classes.methods.length > 0", jclass.getMethods().isEmpty() );
+
+ assertNotContainsRegex( "Import List", "[\\[\\)\\(\\;]", jclass.getImports() );
+
+ assertTrue( "classes.imports", jclass.getImports().contains( "java.util.zip.GZIPInputStream" ) );
+ assertTrue( "classes.imports", jclass.getImports().contains( "org.apache.tools.ant.XmlLogger$TimedElement" ) );
+ assertTrue( "classes.imports", jclass.getImports().contains( "org.apache.tools.mail.MailMessage" ) );
+ assertTrue( "classes.packages", jclass.getPackages().contains( "org.apache.tools.ant" ) );
+ assertTrue( "classes.packages", jclass.getPackages().contains( "org.apache.tools.bzip2" ) );
+ }
+
+ public void testAnalyzeJarWithInvalidClassFile()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "invalid-class-file.jar" );
+
+ // Doesn't fail, as exceptions are ignored.
+ assertTrue( jclass.getClassNames().isEmpty() );
+ assertTrue( jclass.getPackages().isEmpty() );
+ assertTrue( jclass.getImports().isEmpty() );
+ assertNull( jclass.getJdkRevision() );
+ assertTrue( jclass.getMethods().isEmpty() );
+ }
+
+ public void testAnalyzeJarWithDebug()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.4-debug.jar" );
+
+ assertTrue( "has debug", jclass.isDebugPresent() );
+ }
+
+ public void testAnalyzeJarWithoutDebug()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.4.jar" );
+
+ assertFalse( "no debug present", jclass.isDebugPresent() );
+ }
+
+ public void testAnalyzeJarVersion16()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.6.jar" );
+
+ assertEquals( "jdkrevision", "1.6", jclass.getJdkRevision() );
+ }
+
+ public void testAnalyzeJarVersion15()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.5.jar" );
+
+ assertEquals( "jdkrevision", "1.5", jclass.getJdkRevision() );
+ }
+
+ public void testAnalyzeJarVersion14()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.4.jar" );
+
+ assertEquals( "jdkrevision", "1.4", jclass.getJdkRevision() );
+ }
+
+ public void testAnalyzeJarVersion13()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.3.jar" );
+
+ assertEquals( "jdkrevision", "1.3", jclass.getJdkRevision() );
+ }
+
+ public void testAnalyzeJarVersion12()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.2.jar" );
+
+ assertEquals( "jdkrevision", "1.2", jclass.getJdkRevision() );
+ }
+
+ public void testAnalyzeJarVersion11()
+ throws Exception
+ {
+ JarClasses jclass = getJarClasses( "helloworld-1.1.jar" );
+
+ assertEquals( "jdkrevision", "1.1", jclass.getJdkRevision() );
+ }
+
+ private JarClasses getJarClasses( String filename )
+ throws Exception
+ {
+ File file = getSampleJar( filename );
+
+ JarClasses jclass = analyzer.analyze( new JarAnalyzer( file ) );
+ assertNotNull( "JarClasses", jclass );
+
+ return jclass;
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/jar/identification/JarIdentificationAnalyzerTest.java b/src/test/java/org/apache/maven/shared/jar/identification/JarIdentificationAnalyzerTest.java
new file mode 100644
index 0000000..f788a61
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/jar/identification/JarIdentificationAnalyzerTest.java
@@ -0,0 +1,98 @@
+package org.apache.maven.shared.jar.identification;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.AbstractJarAnalyzerTestCase;
+import org.apache.maven.shared.jar.JarAnalyzer;
+
+import java.io.File;
+
+
+/**
+ * JarAnalyzer Taxon Analyzer Test Case
+ *
+ * @todo test the exposers individually instead of in aggregate here (and test the normalize, etc. methods here instead with controlled exposers)
+ */
+public class JarIdentificationAnalyzerTest
+ extends AbstractJarAnalyzerTestCase
+{
+ private JarIdentification getJarTaxon( String filename )
+ throws Exception
+ {
+ File jarfile = getSampleJar( filename );
+
+ JarIdentificationAnalysis analyzer =
+ (JarIdentificationAnalysis) lookup( JarIdentificationAnalysis.class.getName() );
+ JarIdentification taxon = analyzer.analyze( new JarAnalyzer( jarfile ) );
+ assertNotNull( "JarIdentification", taxon );
+
+ return taxon;
+ }
+
+ public void testTaxonAnalyzerWithJXR()
+ throws Exception
+ {
+ JarIdentification taxon = getJarTaxon( "jxr.jar" );
+
+ assertEquals( "identification.groupId", "org.apache.maven", taxon.getGroupId() );
+ assertEquals( "identification.artifactId", "maven-jxr", taxon.getArtifactId() );
+ assertEquals( "identification.version", "1.1-SNAPSHOT", taxon.getVersion() );
+ assertEquals( "identification.name", "Maven JXR", taxon.getName() );
+ assertEquals( "identification.vendor", "Apache Software Foundation", taxon.getVendor() );
+
+ // TODO assert potentials too
+ }
+
+ /**
+ * Tests JarAnalyzer with No embedded pom, and no useful manifest.mf information.
+ *
+ * @throws Exception failures
+ */
+ public void testTaxonAnalyzerWithCODEC()
+ throws Exception
+ {
+ JarIdentification taxon = getJarTaxon( "codec.jar" );
+
+ assertEquals( "identification.groupId", "org.apache.commons.codec", taxon.getGroupId() );
+ assertEquals( "identification.artifactId", "codec", taxon.getArtifactId() );
+ // TODO fix assertion
+ // assertEquals( "identification.version", "codec_release_1_0_0_interim_20030519095102_build", identification.getVersion() );
+ assertEquals( "identification.version", "20030519", taxon.getVersion() );
+ assertEquals( "identification.name", "codec", taxon.getName() );
+ assertNull( "identification.vendor", taxon.getVendor() );
+
+ // TODO assert potentials too
+ }
+
+ public void testTaxonAnalyzerWithANT()
+ throws Exception
+ {
+ JarIdentification taxon = getJarTaxon( "ant.jar" );
+
+ assertEquals( "identification.groupId", "org.apache.tools.ant", taxon.getGroupId() );
+ assertEquals( "identification.artifactId", "ant", taxon.getArtifactId() );
+ assertEquals( "identification.version", "1.6.5", taxon.getVersion() );
+ // TODO fix assertion
+ // assertEquals( "identification.name", "Apache Ant", identification.getName() );
+ assertEquals( "identification.vendor", "Apache Software Foundation", taxon.getVendor() );
+
+ // TODO assert potentials too
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/jar/identification/exposers/EmbeddedMavenModelExposerTest.java b/src/test/java/org/apache/maven/shared/jar/identification/exposers/EmbeddedMavenModelExposerTest.java
new file mode 100644
index 0000000..7cd39d1
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/jar/identification/exposers/EmbeddedMavenModelExposerTest.java
@@ -0,0 +1,68 @@
+package org.apache.maven.shared.jar.identification.exposers;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.jar.AbstractJarAnalyzerTestCase;
+import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.identification.JarIdentification;
+
+import java.io.File;
+
+
+/**
+ * Test Case for Embedded Maven Model Taxon Data.
+ */
+public class EmbeddedMavenModelExposerTest
+ extends AbstractJarAnalyzerTestCase
+{
+ public void testExposerWithJXR()
+ throws Exception
+ {
+ File file = getSampleJar( "jxr.jar" );
+
+ JarIdentification identification = new JarIdentification();
+
+ EmbeddedMavenModelExposer exposer = new EmbeddedMavenModelExposer();
+ exposer.expose( identification, new JarAnalyzer( file ) );
+
+ assertFalse( "exposer.groupIds", identification.getPotentialGroupIds().isEmpty() );
+ assertFalse( "exposer.artifactIds", identification.getPotentialArtifactIds().isEmpty() );
+ assertFalse( "exposer.versions", identification.getPotentialVersions().isEmpty() );
+
+ // TODO test others
+ }
+
+ public void testExposerWithANT()
+ throws Exception
+ {
+ File file = getSampleJar( "ant.jar" );
+
+ JarIdentification identification = new JarIdentification();
+
+ EmbeddedMavenModelExposer exposer = new EmbeddedMavenModelExposer();
+ exposer.expose( identification, new JarAnalyzer( file ) );
+
+ assertTrue( "exposer.groupIds", identification.getPotentialGroupIds().isEmpty() );
+ assertTrue( "exposer.artifactIds", identification.getPotentialArtifactIds().isEmpty() );
+ assertTrue( "exposer.versions", identification.getPotentialVersions().isEmpty() );
+
+ // TODO test others
+ }
+}
diff --git a/src/test/resources/jars/ant.jar b/src/test/resources/jars/ant.jar
new file mode 100644
index 0000000..3beb3b8
--- /dev/null
+++ b/src/test/resources/jars/ant.jar
Binary files differ
diff --git a/src/test/resources/jars/codec.jar b/src/test/resources/jars/codec.jar
new file mode 100644
index 0000000..c9e120d
--- /dev/null
+++ b/src/test/resources/jars/codec.jar
Binary files differ
diff --git a/src/test/resources/jars/evil-sealed-regex-1.0.jar b/src/test/resources/jars/evil-sealed-regex-1.0.jar
new file mode 100644
index 0000000..fe79f60
--- /dev/null
+++ b/src/test/resources/jars/evil-sealed-regex-1.0.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.1-debug.jar b/src/test/resources/jars/helloworld-1.1-debug.jar
new file mode 100644
index 0000000..430f24a
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.1-debug.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.1.jar b/src/test/resources/jars/helloworld-1.1.jar
new file mode 100644
index 0000000..43e6c20
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.1.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.2-debug.jar b/src/test/resources/jars/helloworld-1.2-debug.jar
new file mode 100644
index 0000000..ff114ea
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.2-debug.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.2.jar b/src/test/resources/jars/helloworld-1.2.jar
new file mode 100644
index 0000000..022ff4b
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.2.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.3-debug.jar b/src/test/resources/jars/helloworld-1.3-debug.jar
new file mode 100644
index 0000000..6ab21a9
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.3-debug.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.3.jar b/src/test/resources/jars/helloworld-1.3.jar
new file mode 100644
index 0000000..f0babf6
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.3.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.4-debug.jar b/src/test/resources/jars/helloworld-1.4-debug.jar
new file mode 100644
index 0000000..8ae0705
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.4-debug.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.4.jar b/src/test/resources/jars/helloworld-1.4.jar
new file mode 100644
index 0000000..aa34f4e
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.4.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.5-debug.jar b/src/test/resources/jars/helloworld-1.5-debug.jar
new file mode 100644
index 0000000..e9e180e
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.5-debug.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.5.jar b/src/test/resources/jars/helloworld-1.5.jar
new file mode 100644
index 0000000..84cc8c2
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.5.jar
Binary files differ
diff --git a/src/test/resources/jars/helloworld-1.6.jar b/src/test/resources/jars/helloworld-1.6.jar
new file mode 100644
index 0000000..62fa04c
--- /dev/null
+++ b/src/test/resources/jars/helloworld-1.6.jar
Binary files differ
diff --git a/src/test/resources/jars/invalid-class-file.jar b/src/test/resources/jars/invalid-class-file.jar
new file mode 100644
index 0000000..ee36df9
--- /dev/null
+++ b/src/test/resources/jars/invalid-class-file.jar
Binary files differ
diff --git a/src/test/resources/jars/invalid.jar b/src/test/resources/jars/invalid.jar
new file mode 100644
index 0000000..4ab08b3
--- /dev/null
+++ b/src/test/resources/jars/invalid.jar
@@ -0,0 +1 @@
+some random text
diff --git a/src/test/resources/jars/jxr.jar b/src/test/resources/jars/jxr.jar
new file mode 100644
index 0000000..1205a6c
--- /dev/null
+++ b/src/test/resources/jars/jxr.jar
Binary files differ