package org.apache.wicket.osgi;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.wicket.util.string.Strings;
import org.junit.Assert;
import org.junit.Test;
/**
* A test that verifies that there are no non-empty packages with the same name in
* two or more wicket modules.
*
* Based on https://gist.github.com/1977817, contributed by Andreas Pieber
*/
public class OsgiClashingPackagesTest extends Assert
{
@Test
public void collectProjectPackages() throws IOException
{
char pathSeparator = System.getProperty("path.separator").charAt(0);
String classpath = System.getProperty("java.class.path");
String[] dependencies = Strings.split(classpath, pathSeparator);
// packageName -> projects containing a package with this name
Map<String, List<Project>> projectBuckets = new HashMap<>();
for (String dependency : dependencies)
{
// process only wicket-xyz.jar
if (dependency.contains("wicket-") && dependency.endsWith(".jar"))
{
JarFile jarFile = new JarFile(dependency);
try
{
String projectName = Strings.afterLast(dependency, '/');
Project project = new Project(projectName, jarFile);
project.addTo(projectBuckets);
} finally {
jarFile.close();
}
}
}
Set<Entry<String, List<Project>>> entrySet = projectBuckets.entrySet();
for (Entry<String, List<Project>> entry : entrySet) {
List<Project> projects = entry.getValue();
if (projects.size() > 1) {
fail(entry);
}
}
}
private void fail(Entry<String, List<Project>> entry) {
StringBuilder builder = new StringBuilder();
String packageName = entry.getKey();
builder.append("Package '").append(packageName).append("' has files in two or more modules: ");
for (Project conflict : entry.getValue()) {
builder.append(conflict.getName()).append(", ");
}
try
{
builder.append("\nResources:\n");
Enumeration<URL> resources = getClass().getClassLoader().getResources(packageName);
while (resources.hasMoreElements())
{
URL resource = resources.nextElement();
builder.append("\n\t").append(resource.toExternalForm());
}
} catch (IOException e)
{
e.printStackTrace();
}
fail(builder.toString());
}
private static class Project {
// a set with all package names in a dependency
private final Set<String> packagesWithContent = new TreeSet<>();
// the name of the dependency
private final String name;
public Project(String name, JarFile jarFile) {
this.name = name;
collectPackageNames(jarFile);
}
/**
* Adds this project to as a value in the global map that contains 'packageName -> List[Project]'
* @param projectBuckets
* the global map
*/
public void addTo(Map<String, List<Project>> projectBuckets) {
for (String packageWithContent : packagesWithContent) {
if (!projectBuckets.containsKey(packageWithContent)) {
projectBuckets.put(packageWithContent, new ArrayList<Project>());
}
projectBuckets.get(packageWithContent).add(this);
}
}
/**
* Collects the names of all packages in this JarFile
* @param jarFile
* the jar file to analyze
*/
private void collectPackageNames(final JarFile jarFile)
{
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry jarEntry = entries.nextElement();
String entryName = jarEntry.getName();
if (shouldCollect(entryName))
{
String packageName = Strings.beforeLast(entryName, '/');
packagesWithContent.add(packageName);
}
}
}
public String getName() {
return name;
}
@Override
public String toString()
{
return "Project{" +
"name='" + name + '\'' +
'}';
}
private boolean shouldCollect(final String entryName)
{
if (
// ignore folder names. count just files/resources
entryName.endsWith("/") ||
// all modules have META-INF {MANIFEST.MF, Maven stuff, ..}
entryName.startsWith("META-INF/") ||
// ignore Wicket's IInitializer conf files
(entryName.startsWith("META-INF/wicket") && entryName.endsWith(".properties"))
)
{
return false;
}
return true;
}
}
}