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