From 032ab6a311978fa7a4a829585258d8d1831ff71a Mon Sep 17 00:00:00 2001 From: rfeng Date: Mon, 30 Aug 2010 17:50:42 +0000 Subject: Add a maven plugin that checks the class conflicts for the dependencies or all jars from a directory git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@990880 13f79535-47bb-0310-9956-ffa450edef68 --- .../plugin/ClassConflictsDetectorMojo.java | 161 ++++++++++++ .../dependency/plugin/ClassPathHellDetector.java | 270 +++++++++++++++++++++ .../maven/dependency/plugin/ConsoleLogWrapper.java | 72 ++++++ .../maven/dependency/plugin/JDKLogWrapper.java | 65 +++++ .../maven/dependency/plugin/LogWrapper.java | 38 +++ 5 files changed, 606 insertions(+) create mode 100644 maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassConflictsDetectorMojo.java create mode 100644 maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassPathHellDetector.java create mode 100644 maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ConsoleLogWrapper.java create mode 100644 maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/JDKLogWrapper.java create mode 100644 maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/LogWrapper.java (limited to 'maven-plugins/trunk/maven-dependency-plugin/src/main/java') diff --git a/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassConflictsDetectorMojo.java b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassConflictsDetectorMojo.java new file mode 100644 index 0000000000..7c072a6b5c --- /dev/null +++ b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassConflictsDetectorMojo.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.maven.dependency.plugin; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; + +/** + * A Maven plugin that check for class conflicts for the dependency jars + * + * @goal check-class-conflicts + * @phase validate + * @requiresDependencyResolution test + * @description check for class conflicts for the dependency jars + */ +public class ClassConflictsDetectorMojo extends AbstractMojo { + + /** + * The project to build the bundle for. + * + * @parameter expression="${project}" + * @required + * @readonly + */ + private MavenProject project; + + /** + * The root directory to scan the jar files. If not set, we'll check the project dependencies + * + * @parameter + */ + private File root; + + /** + * @parameter default-value="false" + */ + private boolean ignoreTestScope; + + /** + * @parameter default-value="true" + */ + private boolean verbose; + + /** + * @parameter default-value="false" + */ + private boolean skip; + + /** + * @parameter default-value="false" + */ + private boolean failOnConflicts; + + public void execute() throws MojoExecutionException { + if (skip) { + return; + } + + Log log = getLog(); + LogWrapper wrapper = new MavenLogWrapper(log); + + try { + int conflicts = 0; + if (root != null) { + conflicts = ClassPathHellDetector.check(root, wrapper, verbose || wrapper.isDebugEnabled()); + } else { + conflicts = ClassPathHellDetector.check(getJarFiles(log), wrapper, verbose || wrapper.isDebugEnabled()); + } + if (conflicts >= 1 && failOnConflicts) { + throw new MojoExecutionException("Conflicting/overlapping classes are found"); + } + } catch (IOException e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + } + + private Set getJarFiles(Log log) { + Set files = new HashSet(); + for (Object a : project.getArtifacts()) { + Artifact artifact = (Artifact)a; + if (ignoreTestScope && Artifact.SCOPE_TEST.equals(artifact.getScope())) { + continue; + } + if (artifact.isResolved()) { + if ("jar".equals(artifact.getType())) { + files.add(artifact.getFile()); + } + } + } + return files; + } + + private static class MavenLogWrapper implements LogWrapper { + private Log log; + + public MavenLogWrapper(Log log) { + super(); + this.log = log; + } + + public boolean isDebugEnabled() { + return log.isDebugEnabled(); + } + + public void debug(String msg) { + log.debug(msg); + + } + + public void info(String msg) { + log.info(msg); + } + + public void warn(String msg) { + log.warn(msg); + } + + public void error(String msg) { + log.error(msg); + } + + public boolean isInfoEnabled() { + return log.isInfoEnabled(); + } + + public boolean isWarnEnabled() { + return log.isWarnEnabled(); + } + + public boolean isErrorEnabled() { + return log.isErrorEnabled(); + } + + } + +} diff --git a/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassPathHellDetector.java b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassPathHellDetector.java new file mode 100644 index 0000000000..908e33ce12 --- /dev/null +++ b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ClassPathHellDetector.java @@ -0,0 +1,270 @@ +package org.apache.tuscany.maven.dependency.plugin; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Scanning the folder to find jar files and detect class conflicts + */ +public class ClassPathHellDetector { + + /** + * arg[0]: The root directory + * @param args + */ + public static void main(String[] args) throws Exception { + boolean verbose = Boolean.parseBoolean(System.getProperty("verbose", "true")); + File root = new File("."); + if (args.length >= 1) { + root = new File(args[0]); + } + LogWrapper log = new ConsoleLogWrapper(); + check(root, log, verbose); + } + + /** + * Recursively check the jar files under the given root directory + * @param root The root directory + * @param log The log + * @param verbose Indicate if the list of classes will be reported + * @return The number of conflicts at jar level + * @throws IOException + */ + public static int check(File root, LogWrapper log, boolean verbose) throws IOException { + Set jarFiles = findJarFiles(root); + return check(jarFiles, log, verbose); + } + + /** + * Check the given list of jars to find out conflicting classes + * @param jarFiles The list of jar files + * @param log The log + * @param verbose Indicate if the list of classes will be reported + * @return The number of conflicts at jar level + * @throws IOException + */ + public static int check(Set jarFiles, LogWrapper log, boolean verbose) throws IOException { + Map> classToFileMapping = new HashMap>(); + for (File f : jarFiles) { + if (log.isDebugEnabled()) { + log.debug("Scanning " + f); + } + for (ClassFile classFile : listClasses(f)) { + Collection files = classToFileMapping.get(classFile.name); + if (files == null) { + files = new ArrayList(); + classToFileMapping.put(classFile.name, files); + } + files.add(classFile); + } + } + + Map> conflicts = new HashMap>(); + for (Map.Entry> entry : classToFileMapping.entrySet()) { + Collection files = entry.getValue(); + if (files.size() > 1) { + Set jars = new HashSet(); + for (ClassFile cf : files) { + jars.add(cf.jarFile); + } + List sorted = new ArrayList(jars); + Collections.sort(sorted); + String conflict = sorted.toString(); + Set classes = conflicts.get(conflict); + if (classes == null) { + classes = new HashSet(); + conflicts.put(conflict, classes); + } + classes.add(entry.getKey()); + } + } + + int size = conflicts.size(); + if (size >= 1) { + log.warn("Number of conflicts: " + conflicts.size()); + } else { + log.info("No class conflicts are found."); + } + + for (Map.Entry> c : conflicts.entrySet()) { + log.warn(""); + log.warn("Conflicting jars: " + c.getKey()); + + if (verbose) { + log.warn("Conflicting or overlapping classes [X: size, ?: crc]: "); + List list = new ArrayList(c.getValue()); + Collections.sort(list); + for (String cls : list) { + Collection classFiles = classToFileMapping.get(cls); + String flag = compare(classFiles); + if (!" ".equals(flag)) { + // Class files with different size or crc + log.warn(" " + flag + " " + cls); + } else { + // Class files with same size and crc + log.warn(" " + flag + " " + cls); + } + } + } + } + + return conflicts.size(); + } + + private static String compare(Collection files) { + long size = 0; + long crc = 0; + String name = null; + for (ClassFile f : files) { + if (name != null && !f.name.equals(name)) { + // Different name + return "X"; + } + if (size != 0 && f.size != size) { + // Different size + return "X"; + } + if (crc != 0 && f.crc != crc) { + // Different crc + return "?"; + } + size = f.size; + crc = f.crc; + name = f.name; + } + return " "; + } + + /** + * List all class files within a jar + * @param file + * @return + * @throws IOException + */ + private static Collection listClasses(File file) throws IOException { + Collection classFiles = new ArrayList(); + JarFile jarFile = new JarFile(file); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + ClassFile cls = new ClassFile(file, entry.getName(), entry.getSize(), entry.getCrc()); + classFiles.add(cls); + } + } + jarFile.close(); + return classFiles; + + } + + private static Set findJarFiles(File root) throws IOException { + Set jarFiles = new HashSet(); + traverse(jarFiles, root, new HashSet()); + return jarFiles; + } + + /** + * Recursively traverse a root directory + * + * @param fileList + * @param file + * @param root + * @param visited The visited directories + * @throws IOException + */ + private static void traverse(Set fileList, File file, Set visited) throws IOException { + if (file.isFile()) { + fileList.add(file); + } else if (file.isDirectory()) { + File dir = file.getCanonicalFile(); + if (!visited.contains(dir)) { + // [rfeng] Add the canonical file into the visited set to avoid duplicate navigation of directories + // following the symbolic links + visited.add(dir); + + File[] files = file.listFiles(new FileFilter() { + + public boolean accept(File f) { + return f.isDirectory() || f.getName().endsWith(".jar"); + } + }); + for (File f : files) { + if (!f.getName().startsWith(".")) { + traverse(fileList, f, visited); + } + } + } + } + } + + /** + * Description of a class file within the jar + */ + private static class ClassFile { + private File jarFile; + private String name; + private long size; + private long crc; + + public ClassFile(File jarFile, String name, long size, long crc) { + super(); + this.jarFile = jarFile; + this.name = name; + this.size = size; + this.crc = crc; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int)(crc ^ (crc >>> 32)); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + (int)(size ^ (size >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ClassFile other = (ClassFile)obj; + if (crc != other.crc) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (size != other.size) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ClassFile [jarFile=").append(jarFile).append(", name=").append(name).append(", size=") + .append(size).append(", crc=").append(crc).append("]"); + return builder.toString(); + } + } + +} diff --git a/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ConsoleLogWrapper.java b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ConsoleLogWrapper.java new file mode 100644 index 0000000000..2e96d7f713 --- /dev/null +++ b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/ConsoleLogWrapper.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.maven.dependency.plugin; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ConsoleLogWrapper implements LogWrapper { + private final static Logger log = Logger.getLogger(ConsoleLogWrapper.class.getName()); + + public ConsoleLogWrapper() { + super(); + } + + public boolean isDebugEnabled() { + return log.isLoggable(Level.FINE); + } + + public boolean isInfoEnabled() { + return log.isLoggable(Level.INFO); + } + + public boolean isWarnEnabled() { + return log.isLoggable(Level.WARNING); + } + + public boolean isErrorEnabled() { + return log.isLoggable(Level.SEVERE); + } + + public void debug(String msg) { + if (isDebugEnabled()) { + System.out.println(msg); + } + } + + public void info(String msg) { + if (isInfoEnabled()) { + System.out.println(msg); + } + } + + public void warn(String msg) { + if (isWarnEnabled()) { + System.err.println(msg); + } + } + + public void error(String msg) { + if (isErrorEnabled()) { + System.err.println(msg); + } + } + +} diff --git a/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/JDKLogWrapper.java b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/JDKLogWrapper.java new file mode 100644 index 0000000000..cf37465568 --- /dev/null +++ b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/JDKLogWrapper.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.maven.dependency.plugin; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JDKLogWrapper implements LogWrapper { + private Logger log; + + public JDKLogWrapper(Logger log) { + super(); + this.log = log; + } + + public boolean isDebugEnabled() { + return log.isLoggable(Level.FINE); + } + + public boolean isInfoEnabled() { + return log.isLoggable(Level.INFO); + } + + public boolean isWarnEnabled() { + return log.isLoggable(Level.WARNING); + } + + public boolean isErrorEnabled() { + return log.isLoggable(Level.SEVERE); + } + + public void debug(String msg) { + log.fine(msg); + } + + public void info(String msg) { + log.info(msg); + } + + public void warn(String msg) { + log.warning(msg); + } + + public void error(String msg) { + log.severe(msg); + } + +} \ No newline at end of file diff --git a/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/LogWrapper.java b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/LogWrapper.java new file mode 100644 index 0000000000..9a62797fdf --- /dev/null +++ b/maven-plugins/trunk/maven-dependency-plugin/src/main/java/org/apache/tuscany/maven/dependency/plugin/LogWrapper.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.maven.dependency.plugin; + +public interface LogWrapper { + boolean isDebugEnabled(); + + boolean isInfoEnabled(); + + boolean isWarnEnabled(); + + boolean isErrorEnabled(); + + void debug(String msg); + + void info(String msg); + + void warn(String msg); + + void error(String msg); +} \ No newline at end of file -- cgit v1.2.3