├── .editorconfig
├── .github
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── de
│ └── is24
│ └── maven
│ └── enforcer
│ └── rules
│ ├── ArtifactRepositoryAnalyzer.java
│ ├── ClassDependencyResolvingVisitor.java
│ ├── ClassFilter.java
│ ├── IllegalTransitiveDependencyCheck.java
│ ├── Repository.java
│ └── Types.java
└── test
└── java
└── de
└── is24
└── maven
└── enforcer
└── rules
├── ArtifactRepositoryAnalyzerTest.java
├── ClassFileReference.java
├── ClassFilterTest.java
├── EnforcerRuleHelperWrapper.java
├── IllegalTransitiveDependencyCheckTest.java
├── LogStub.java
├── RepositoryTest.java
├── TypesTest.java
└── testtypes
├── ClassInAnotherTransitiveDependency.java
├── ClassInDirectDependency.java
├── ClassInMavenProjectSource.java
└── ClassInTransitiveDependency.java
/.editorconfig:
--------------------------------------------------------------------------------
1 | # default identation settings
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | # workflow_dispatch enables manual triggering of the workflow
5 | workflow_dispatch:
6 | schedule:
7 | - cron: '51 2 * * 3'
8 | env:
9 | FAST_EMAIL: ${{ secrets.FAST_EMAIL }}
10 | FAST_USER: ${{ secrets.FAST_USER }}
11 | FAST_TOKEN: ${{ secrets.FAST_TOKEN }}
12 | FAST_HTTPAUTH: ${{ secrets.FAST_HTTPAUTH }}
13 |
14 | jobs:
15 | analyze:
16 | name: Analyze
17 | runs-on: ubuntu-latest
18 | permissions:
19 | actions: read
20 | contents: read
21 | security-events: write
22 |
23 | steps:
24 | - name: S24 static application security testing (SAST) action
25 | uses: scout24/s24-sast-action@v1
26 | with:
27 | languages: java
28 | fast_user: ${{ env.FAST_USER }}
29 | fast_token: ${{ env.FAST_TOKEN }}
30 | java_version: '11'
31 |
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignored resources
2 | .idea
3 | *.iml
4 | target
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | branches:
5 | only:
6 | - master
7 | after_success:
8 | - mvn clean test jacoco:report coveralls:jacoco
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 André Schubert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project is end of live and not any longer maintained!
2 | ==========================================================
3 |
4 |
5 | The 'Illegal' Transitive Dependency Check Rule
6 | ==============================================
7 |
8 | [](https://travis-ci.org/ImmobilienScout24/illegal-transitive-dependency-check)
9 | [](https://coveralls.io/r/ImmobilienScout24/illegal-transitive-dependency-check)
10 | [](https://maven-badges.herokuapp.com/maven-central/de.is24.maven.enforcer.rules/illegal-transitive-dependency-check/)
11 |
12 | The `IllegalTransitiveDependencyCheck` is an additional rule for the `maven-enforcer-plugin`. The rule checks if
13 | all classes in a certain artifact references only classes that are provided by explicitly declared dependencies.
14 | Thus the rule will list (or complain about) all classes that are only available through transitive dependencies.
15 |
16 | You can run the check by configuring the maven-enforcer-plugin to make use of the additional rule:
17 |
18 | ```xml
19 |
20 | ...
21 |
22 |
23 |
24 | org.apache.maven.plugins
25 | maven-enforcer-plugin
26 | 1.3.1
27 |
28 |
29 | de.is24.maven.enforcer.rules
30 | illegal-transitive-dependency-check
31 | 1.7.4
32 |
33 |
34 |
35 |
36 | enforce
37 | verify
38 |
39 | enforce
40 |
41 |
42 |
43 |
44 | false
45 | true
46 | true
47 |
48 | javax\..+
49 | org\.hibernate\..+
50 |
51 | false
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ...
61 |
62 | ```
63 |
64 | The rule itself can be configured to only report violations or even to signal the enforcer-plugin to break the build by
65 | specifying the attribute `reportOnly`. You may also exclude classes or packages from analysis by providing
66 | regex-patterns to parameter `regexIgnoredClasses` (e.g. `my\.suppressed\.Type`).
67 |
68 | In addition to these exclusions types from packages `javax.*`,`sun.*`, `jdk.*`, `org.*` and `com.sun.*` that are available through the current
69 | Java runtime can be excluded automatically by setting parameter `suppressTypesFromJavaRuntime`.
70 |
71 | By default the rule will resolve the currently analyzed artifact in the Maven repository. In case the enforcer-plugin
72 | runs in a phase compiled classes are available in the target folder (e.g. `verify`) artifact-resolving can be avoided
73 | by setting parameter `useClassesFromLastBuild` to `true`.
74 |
75 | (Since version 1.7.4 the `regexIngoredClasses` filtering is also applied to the classes of the artifact currently
76 | analyzed. Thus direct dependencies of that classes will not be considered. See request [#29](https://github.com/ImmobilienScout24/illegal-transitive-dependency-check/issues/29))
77 |
78 | If not only the classes but also the transitively used artifacts should be listed the parameter `listMissingArtifacts`
79 | can be set to `true`. **Caution: This option is really slow!**
80 |
81 | Releases are available [here](http://repo1.maven.org/maven2/de/is24/maven/enforcer/rules/illegal-transitive-dependency-check/) in Maven's central repository.
82 |
83 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 |
7 | org.sonatype.oss
8 | oss-parent
9 | 7
10 |
11 |
12 | de.is24.maven.enforcer.rules
13 | illegal-transitive-dependency-check
14 | 1.8-SNAPSHOT
15 | jar
16 |
17 | Illegal Transitive Dependency Check
18 | This rule checks whether the code uses classes of another module through transitive dependencies.
19 | https://github.com/ImmobilienScout24/illegal-transitive-dependency-check
20 |
21 |
22 |
23 | The MIT License (MIT)
24 | http://opensource.org/licenses/MIT
25 | repo
26 |
27 |
28 |
29 |
30 | scm:git:https://github.com/ImmobilienScout24/illegal-transitive-dependency-check.git
31 | scm:git:git@github.com:ImmobilienScout24/illegal-transitive-dependency-check.git
32 | https://github.com/ImmobilienScout24/illegal-transitive-dependency-check.git
33 |
34 |
35 |
36 |
37 | 2bad4u
38 | André Schubert
39 | 2bad4u@scubizone.de
40 |
41 |
42 |
43 |
44 | 1.3.1
45 | 3.1
46 | 2.0.9
47 | 1.7.6
48 | 5.0.3
49 | 1.6
50 | 1.8
51 | 0.7.1.201405082137
52 | 2.2.0
53 | 2.1
54 | 1.5.5
55 |
56 |
57 |
58 |
59 |
60 | maven-compiler-plugin
61 | ${build.plugin.version}
62 |
63 |
64 | default-compile
65 |
66 |
67 | ${jdk.src.version}
68 | ${jdk.src.version}
69 |
70 |
71 |
72 |
73 | default-testCompile
74 |
75 |
76 | ${jdk.test.version}
77 | ${jdk.test.version}
78 |
79 |
80 |
81 |
82 |
83 |
84 | org.eluder.coveralls
85 | coveralls-maven-plugin
86 | ${coveralls.plugin.version}
87 |
88 |
89 | org.jacoco
90 | jacoco-maven-plugin
91 | ${jacoco.plugin.version}
92 |
93 |
94 | prepare-agent
95 |
96 | prepare-agent
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | org.eclipse.m2e
107 | lifecycle-mapping
108 | 1.0.0
109 |
110 |
111 |
112 |
113 |
114 | org.jacoco
115 |
116 | jacoco-maven-plugin
117 |
118 |
119 | [0.7.1.201405082137,)
120 |
121 |
122 | prepare-agent
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | org.apache.maven.enforcer
140 | enforcer-api
141 | ${enforcer.plugin.version}
142 |
143 |
144 | org.apache.maven
145 | maven-project
146 | ${maven.version}
147 |
148 |
149 | org.apache.maven
150 | maven-core
151 | ${maven.version}
152 |
153 |
154 | org.apache.maven
155 | maven-artifact
156 | ${maven.version}
157 |
158 |
159 | org.apache.maven
160 | maven-plugin-api
161 | ${maven.version}
162 |
163 |
164 | org.codehaus.plexus
165 | plexus-container-default
166 | ${plexus-container.version}
167 |
168 |
169 | org.apache.maven.shared
170 | maven-dependency-tree
171 | ${maven-dependency-tree.version}
172 |
173 |
174 | junit
175 | junit
176 | 4.11
177 | test
178 |
179 |
180 | org.ow2.asm
181 | asm-analysis
182 | ${asm.version}
183 |
184 |
185 | org.apache.maven.enforcer
186 | enforcer-rules
187 | ${enforcer.plugin.version}
188 | test-jar
189 | test
190 |
191 |
192 | org.apache.maven.shared
193 | maven-plugin-testing-harness
194 | 1.1
195 | test
196 |
197 |
198 | org.apache.maven.plugins
199 | maven-enforcer-plugin
200 | ${enforcer.plugin.version}
201 | maven-plugin
202 | test
203 |
204 |
205 | org.slf4j
206 | slf4j-api
207 | ${sl4j.version}
208 | test
209 |
210 |
211 | org.slf4j
212 | slf4j-jdk14
213 | ${sl4j.version}
214 | test
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/src/main/java/de/is24/maven/enforcer/rules/ArtifactRepositoryAnalyzer.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.artifact.Artifact;
4 | import org.apache.maven.plugin.logging.Log;
5 | import org.objectweb.asm.ClassReader;
6 | import org.objectweb.asm.ClassVisitor;
7 |
8 | import java.io.File;
9 | import java.io.FileInputStream;
10 | import java.io.IOException;
11 | import java.util.Enumeration;
12 | import java.util.regex.Pattern;
13 | import java.util.zip.ZipEntry;
14 | import java.util.zip.ZipFile;
15 |
16 |
17 | final class ArtifactRepositoryAnalyzer {
18 | private static final String CLASS_SUFFIX = ".class";
19 | private static final Pattern JAR_FILE_PATTERN = Pattern.compile("^.+\\.(jar|war|JAR|WAR)$");
20 |
21 | private final Log logger;
22 | private final boolean analyzeDependencies;
23 | private final ClassFilter filter;
24 |
25 | private ArtifactRepositoryAnalyzer(Log logger, boolean analyzeDependencies, ClassFilter filter) {
26 | this.logger = logger;
27 | this.analyzeDependencies = analyzeDependencies;
28 | this.filter = filter;
29 | }
30 |
31 | static ArtifactRepositoryAnalyzer analyzeArtifacts(Log logger, boolean analyzeDependencies,
32 | ClassFilter filter) {
33 | return new ArtifactRepositoryAnalyzer(logger, analyzeDependencies, filter);
34 | }
35 |
36 | Repository analyzeArtifacts(Iterable artifacts) {
37 | final Repository repository = new Repository(filter);
38 |
39 | for (Artifact artifact : artifacts) {
40 | final File artifactFile = artifact.getFile();
41 | if (artifactFile == null) {
42 | logger.info("Artifact '" + artifact + "' has no associated file, skip it.");
43 | continue;
44 | }
45 |
46 | if (artifactFile.isDirectory()) {
47 | analyzeClassesDirectory(repository, artifactFile);
48 | } else {
49 | final String absolutePath = artifactFile.getAbsolutePath();
50 | if (JAR_FILE_PATTERN.matcher(absolutePath).matches()) {
51 | analyzeJar(repository, artifactFile);
52 | } else {
53 | logger.info("Artifact '" + artifact + "' associated file '" + absolutePath + "', is skipped.");
54 | }
55 | }
56 | }
57 | return repository;
58 | }
59 |
60 | private void analyzeJar(Repository repository, File jar) {
61 | final ClassVisitor classVisitor = new ClassDependencyResolvingVisitor(repository, logger);
62 |
63 | ZipFile zipFile = null;
64 | try {
65 | zipFile = new ZipFile(jar.getAbsolutePath());
66 |
67 | final Enumeration extends ZipEntry> entries = zipFile.entries();
68 | while (entries.hasMoreElements()) {
69 | final ZipEntry entry = entries.nextElement();
70 | final String fileName = entry.getName();
71 | if (fileName.endsWith(CLASS_SUFFIX)) {
72 | if (logger.isDebugEnabled()) {
73 | logger.debug("Analyze class '" + fileName + "' in JAR '" + jar + "'.");
74 | }
75 |
76 | final ClassReader classReader = new ClassReader(zipFile.getInputStream(entry));
77 |
78 | final String className = classReader.getClassName().replace('/', '.');
79 | if (analyzeDependencies) {
80 | if (filter.isConsideredType(className)) {
81 | classReader.accept(classVisitor, ClassReader.SKIP_FRAMES);
82 | }
83 | } else {
84 | repository.addType(className);
85 | }
86 | }
87 | }
88 | } catch (IOException e) {
89 | throw logAndWrapIOException(e, jar, "artifact");
90 | } finally {
91 | if (zipFile != null) {
92 | try {
93 | zipFile.close();
94 | } catch (IOException e) {
95 | throw logAndWrapIOException(e, jar, "artifact");
96 | }
97 | }
98 | }
99 | }
100 |
101 | private IllegalStateException logAndWrapIOException(IOException e, File file, final String description) {
102 | final String error = "Unable to read class(es) from " + description + " '" + file + "'.";
103 | logger.error(error, e);
104 | return new IllegalStateException(error, e);
105 | }
106 |
107 | private void analyzeClassesDirectory(Repository repository, File classesDirectory) {
108 | final ClassVisitor classVisitor = new ClassDependencyResolvingVisitor(repository, logger);
109 | analyzeClassesDirectory(repository, classesDirectory, classVisitor);
110 | }
111 |
112 | private void analyzeClassesDirectory(Repository repository, File directory, ClassVisitor classVisitor) {
113 | if (directory.isDirectory()) {
114 | final String[] entries = directory.list();
115 | for (String entry : entries) {
116 | analyzeClassesDirectory(repository, new File(directory, entry), classVisitor);
117 | }
118 | }
119 |
120 | final String path = directory.getPath();
121 | if (path.endsWith(CLASS_SUFFIX)) {
122 | if (logger.isDebugEnabled()) {
123 | logger.debug("Analyze class '" + path + "'.");
124 | }
125 |
126 | FileInputStream classFileStream = null;
127 | try {
128 | classFileStream = new FileInputStream(directory);
129 | final ClassReader classReader = new ClassReader(classFileStream);
130 | String className = classReader.getClassName().replace('/', '.');
131 | if (analyzeDependencies) {
132 | if (filter.isConsideredType(className)) {
133 | classReader.accept(classVisitor, ClassReader.SKIP_FRAMES);
134 | }
135 | } else {
136 | repository.addType(className);
137 | }
138 | } catch (IOException e) {
139 | throw logAndWrapIOException(e, directory, "file");
140 | } finally {
141 | try {
142 | if (classFileStream != null) {
143 | classFileStream.close();
144 | }
145 | } catch (IOException e) {
146 | throw logAndWrapIOException(e, directory, "file");
147 | }
148 | }
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/java/de/is24/maven/enforcer/rules/ClassDependencyResolvingVisitor.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.plugin.logging.Log;
4 | import org.objectweb.asm.AnnotationVisitor;
5 | import org.objectweb.asm.ClassVisitor;
6 | import org.objectweb.asm.FieldVisitor;
7 | import org.objectweb.asm.Label;
8 | import org.objectweb.asm.MethodVisitor;
9 | import org.objectweb.asm.Opcodes;
10 | import org.objectweb.asm.Type;
11 | import org.objectweb.asm.signature.SignatureReader;
12 | import org.objectweb.asm.signature.SignatureVisitor;
13 |
14 |
15 | final class ClassDependencyResolvingVisitor extends ClassVisitor {
16 | private final Repository repository;
17 | private final Log logger;
18 | private final AnnotationVisitor annotationVisitor = new ClassDependencyAnnotationVisitor();
19 | private final FieldVisitor fieldVisitor = new ClassDependencyFieldVisitor();
20 | private final MethodVisitor methodVisitor = new ClassDependencyMethodVisitor();
21 | private final SignatureVisitor signatureVisitor = new ClassDependencySignatureVisitor();
22 |
23 | ClassDependencyResolvingVisitor(Repository repository, Log logger) {
24 | super(Opcodes.ASM5);
25 | this.repository = repository;
26 | this.logger = logger;
27 | }
28 |
29 | @Override
30 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
31 | final String className = Types.readInternalTypeName(name);
32 | logger.debug("Add new type '" + className + "'.");
33 | repository.addType(className);
34 |
35 | if (superName != null) {
36 | final String superTypeName = Types.readInternalTypeName(superName);
37 | addDependency("super type", superTypeName);
38 | }
39 |
40 | if (interfaces != null) {
41 | for (String iface : interfaces) {
42 | final String interfaceType = Types.readInternalTypeName(iface);
43 | addDependency("interface type", interfaceType);
44 | }
45 | }
46 |
47 | processSignature(signature);
48 | }
49 |
50 | @Override
51 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
52 | final String fieldType = Types.readTypeDescription(desc);
53 | addDependency("field type", fieldType);
54 |
55 | // add initial field value if any
56 | if (value != null) {
57 | final String fieldValueType = Types.readValueType(value);
58 | addDependency("field value type", fieldValueType);
59 | }
60 | processSignature(signature);
61 |
62 | return fieldVisitor;
63 | }
64 |
65 | @Override
66 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
67 | return delegateToAnnotationVisitor(desc);
68 | }
69 |
70 | @Override
71 | public void visitInnerClass(String name, String outerName, String innerName, int access) {
72 | final String innerClassName = Types.readInternalTypeName(name);
73 | addDependency("inner class", innerClassName);
74 | }
75 |
76 | @Override
77 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
78 | final Type[] argumentTypes = Type.getArgumentTypes(desc);
79 |
80 | for (Type argumentType : argumentTypes) {
81 | final String parameterTypeName = Types.readType(argumentType);
82 | addDependency("annotation's method parameter type",
83 | parameterTypeName);
84 | }
85 |
86 | final Type returnType = Type.getReturnType(desc);
87 | final String returnTypeName = Types.readType(returnType);
88 | addDependency("annotation's method return type", returnTypeName);
89 |
90 | if (exceptions != null) {
91 | for (String exception : exceptions) {
92 | final String exceptionName = Types.readInternalTypeName(exception);
93 | addDependency("exception type", exceptionName);
94 | }
95 | }
96 |
97 | processSignature(signature);
98 |
99 | return methodVisitor;
100 | }
101 |
102 | private AnnotationVisitor delegateToAnnotationVisitor(String desc) {
103 | final String annotationType = Types.readTypeDescription(desc);
104 | addDependency("annotation", annotationType);
105 |
106 | return annotationVisitor;
107 | }
108 |
109 | private void addDependency(String typeDescription, String typeName) {
110 | if (logger.isDebugEnabled()) {
111 | logger.debug("Add " + typeDescription + " '" + typeName + "' as dependency.");
112 | }
113 | repository.addDependency(typeName);
114 | }
115 |
116 | private void processSignature(String signature) {
117 | if (signature != null) {
118 | final SignatureReader signatureReader = new SignatureReader(
119 | signature);
120 | signatureReader.accept(signatureVisitor);
121 | }
122 | }
123 |
124 | private final class ClassDependencySignatureVisitor extends SignatureVisitor {
125 | private ClassDependencySignatureVisitor() {
126 | super(Opcodes.ASM5);
127 | }
128 |
129 | @Override
130 | public void visitClassType(String name) {
131 | final String classType = Types.readInternalTypeName(name);
132 | addDependency("class type", classType);
133 | }
134 | }
135 |
136 | private final class ClassDependencyAnnotationVisitor extends AnnotationVisitor {
137 | private ClassDependencyAnnotationVisitor() {
138 | super(Opcodes.ASM5);
139 | }
140 |
141 | @Override
142 | public void visit(String name, Object value) {
143 | final String valueType = Types.readValueType(value);
144 | addDependency("annotation's value type", valueType);
145 | }
146 |
147 | @Override
148 | public void visitEnum(String name, String desc, String value) {
149 | final String enumType = Types.readTypeDescription(desc);
150 | addDependency("annotation's enum type", enumType);
151 | }
152 |
153 | @Override
154 | public AnnotationVisitor visitAnnotation(String name, String desc) {
155 | final String annotationType = Types.readTypeDescription(desc);
156 | addDependency("annotation's annotation type", annotationType);
157 |
158 | return this;
159 | }
160 |
161 | @Override
162 | public AnnotationVisitor visitArray(String name) {
163 | return this;
164 | }
165 | }
166 |
167 | private final class ClassDependencyFieldVisitor extends FieldVisitor {
168 | private ClassDependencyFieldVisitor() {
169 | super(Opcodes.ASM5);
170 | }
171 |
172 | @Override
173 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
174 | return delegateToAnnotationVisitor(desc);
175 | }
176 | }
177 |
178 | private final class ClassDependencyMethodVisitor extends MethodVisitor {
179 | private ClassDependencyMethodVisitor() {
180 | super(Opcodes.ASM5);
181 | }
182 |
183 | @Override
184 | public AnnotationVisitor visitAnnotationDefault() {
185 | return annotationVisitor;
186 | }
187 |
188 | @Override
189 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
190 | return delegateToAnnotationVisitor(desc);
191 | }
192 |
193 | @Override
194 | public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
195 | return delegateToAnnotationVisitor(desc);
196 | }
197 |
198 | @Override
199 | public void visitTypeInsn(int opcode, String type) {
200 | final String typeName = Types.readInternalTypeName(type);
201 | addDependency("Type instruction type", typeName);
202 | }
203 |
204 | @Override
205 | public void visitFieldInsn(int opcode, String owner, String name, String desc) {
206 | final String fieldType = Types.readTypeDescription(desc);
207 | addDependency("field instruction type", fieldType);
208 | }
209 |
210 | @Override
211 | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
212 | final String ownerType = Types.readInternalTypeName(owner);
213 | addDependency("method owner", ownerType);
214 |
215 | final Type[] argumentTypes = Type.getArgumentTypes(desc);
216 |
217 | for (Type argumentType : argumentTypes) {
218 | final String parameterTypeName = Types.readType(argumentType);
219 | addDependency("method parameter type", parameterTypeName);
220 | }
221 |
222 | final Type returnType = Type.getReturnType(desc);
223 | final String returnTypeName = Types.readType(returnType);
224 | addDependency("method return type", returnTypeName);
225 | }
226 |
227 | @Override
228 | public void visitLdcInsn(Object cst) {
229 | final String constantTypeName = Types.readValueType(cst);
230 | addDependency("constant's type", constantTypeName);
231 | }
232 |
233 | @Override
234 | public void visitMultiANewArrayInsn(String desc, int dims) {
235 | final String arrayType = Types.readTypeDescription(desc);
236 | addDependency("array's type", arrayType);
237 | }
238 |
239 | @Override
240 | public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
241 | if (type != null) {
242 | final String exceptionType = Types.readInternalTypeName(type);
243 | addDependency("exception type", exceptionType);
244 | }
245 | }
246 |
247 | @Override
248 | public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
249 | final String localVariableType = Types.readTypeDescription(desc);
250 | addDependency("local variable", localVariableType);
251 |
252 | processSignature(signature);
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/main/java/de/is24/maven/enforcer/rules/ClassFilter.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.plugin.logging.Log;
4 | import org.codehaus.plexus.util.StringUtils;
5 |
6 | import java.net.URL;
7 | import java.util.Collection;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 | import java.util.regex.Pattern;
11 |
12 | final class ClassFilter {
13 | // ignore primitives, numerical names (for anonymous classes) and all classes in package java
14 | private static final String JAVA_TYPES_REGEX = "[0-9\\$]+|" +
15 | "(boolean)|(byte)|(char)|(short)|(int)|(long)|(float)|(double)|(void)|" +
16 | "(java\\.[\\w\\.\\$]*)";
17 |
18 | // path of current Java runtime environment
19 | private static final String JAVA_HOME_PATH = "file:" + System.getProperty("java.home");
20 | private static final Pattern JAVA_RUNTIME_PACKAGES = Pattern.compile(
21 | "^(javax|com\\.sun|org|sun|jdk)\\..+");
22 |
23 | // cache JDK types that already have been checked..
24 | private final Map alreadyProcessedJavaTypes = new HashMap();
25 |
26 | private final Pattern ignoredClassesPattern;
27 | private final boolean suppressTypesFromJavaRuntime;
28 | private final Log logger;
29 |
30 | ClassFilter(Log logger, boolean suppressTypesFromJavaRuntime, String... regexIgnoredClasses) {
31 | this.logger = logger;
32 | this.suppressTypesFromJavaRuntime = suppressTypesFromJavaRuntime;
33 |
34 | if ((regexIgnoredClasses == null) || (regexIgnoredClasses.length == 0)) {
35 | ignoredClassesPattern = Pattern.compile(JAVA_TYPES_REGEX);
36 | } else {
37 | final StringBuilder regexBuilder = new StringBuilder(JAVA_TYPES_REGEX);
38 | for (String regex : regexIgnoredClasses) {
39 | if (StringUtils.isNotEmpty(regex)) {
40 | regexBuilder.append("|(").append(regex).append(")");
41 | }
42 | }
43 |
44 | final String regex = regexBuilder.toString();
45 | logger.debug("Use type suppression pattern '" + regex + "'.");
46 | ignoredClassesPattern = Pattern.compile(regex);
47 | }
48 | }
49 |
50 | private boolean typeFromJavaRuntime(String type) {
51 | if (JAVA_RUNTIME_PACKAGES.matcher(type).matches()) {
52 | // check if this type has already been checked
53 | final Boolean isJdkType = alreadyProcessedJavaTypes.get(type);
54 | if (isJdkType != null) {
55 | if (logger.isDebugEnabled()) {
56 | logger.debug("Type's '" + type + "' existence in current Java runtime has already been checked.");
57 | }
58 | return isJdkType;
59 | }
60 |
61 | final String classResource = type.replace('.', '/') + ".class";
62 | final URL it = ClassLoader.getSystemClassLoader().getResource(classResource);
63 | if (it != null) {
64 | final String sourcePath = it.getFile();
65 | if (sourcePath.startsWith(JAVA_HOME_PATH)) {
66 | if (logger.isDebugEnabled()) {
67 | logger.debug("Suppress type '" + type + "', it's in current Java runtime '" + JAVA_HOME_PATH + "'.");
68 | }
69 | alreadyProcessedJavaTypes.put(type, true);
70 | return true;
71 | }
72 | }
73 | alreadyProcessedJavaTypes.put(type, false);
74 | return false;
75 | }
76 | return false;
77 | }
78 |
79 | void addFiltered(Collection set, String type) {
80 | if (isConsideredType(type)) {
81 | set.add(type);
82 | }
83 | }
84 |
85 | boolean isConsideredType(String type) {
86 | if (ignoredClassesPattern.matcher(type).matches()) {
87 | if (logger.isDebugEnabled()) {
88 | logger.debug("Suppress type '" + type + "'.");
89 | }
90 | return false;
91 | }
92 |
93 | // check if JDK classes should be ignored and class comes from current Java runtime..
94 | return !(suppressTypesFromJavaRuntime && typeFromJavaRuntime(type));
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/de/is24/maven/enforcer/rules/IllegalTransitiveDependencyCheck.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.artifact.Artifact;
4 | import org.apache.maven.artifact.repository.ArtifactRepository;
5 | import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
6 | import org.apache.maven.artifact.resolver.ArtifactResolver;
7 | import org.apache.maven.enforcer.rule.api.EnforcerRule;
8 | import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
9 | import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
10 | import org.apache.maven.model.Build;
11 | import org.apache.maven.plugin.logging.Log;
12 | import org.apache.maven.project.MavenProject;
13 | import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
14 | import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
15 | import org.apache.maven.shared.dependency.graph.DependencyNode;
16 | import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
17 | import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
18 | import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
19 | import org.codehaus.plexus.util.StringUtils;
20 |
21 | import java.io.File;
22 | import java.io.FileWriter;
23 | import java.io.IOException;
24 | import java.util.ArrayList;
25 | import java.util.Collections;
26 | import java.util.HashSet;
27 | import java.util.List;
28 | import java.util.Set;
29 |
30 |
31 | /**
32 | * Rule enforcing directly declared maven dependencies only
33 | *
34 | * @author André Schubert
35 | */
36 | public final class IllegalTransitiveDependencyCheck implements EnforcerRule {
37 | private static final String NO_CACHE_ID_AVAILABLE = null;
38 | private static final String OUTPUT_FILE_EXTENSION = ".txt";
39 | private static final String OUTPUT_FILE_PREFIX = "itd-";
40 |
41 | private ArtifactResolver resolver;
42 |
43 | private ArtifactRepository localRepository;
44 |
45 | private DependencyGraphBuilder dependencyGraphBuilder;
46 |
47 | private List remoteRepositories;
48 |
49 | private String outputDirectory;
50 |
51 | private MavenProject project;
52 |
53 | private Log logger;
54 |
55 | private boolean reportOnly;
56 |
57 | private boolean listMissingArtifacts;
58 |
59 | private String[] regexIgnoredClasses;
60 |
61 | private boolean useClassesFromLastBuild;
62 |
63 | private boolean suppressTypesFromJavaRuntime;
64 |
65 | private ClassFilter filter;
66 |
67 |
68 | @Override
69 | public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {
70 | logger = helper.getLog();
71 |
72 | if (reportOnly) {
73 | logger.info("Flag 'reportOnly' is set. Exceptions from rule will only be reported!");
74 | }
75 |
76 | if (listMissingArtifacts) {
77 | logger.info("Flag 'listMissingArtifacts' is set. Transitively used artifacts are resolved.");
78 | initializeDependencyGraphBuilder(helper);
79 | }
80 |
81 | if (useClassesFromLastBuild) {
82 | logger.info("Flag 'useClassesFromLastBuild' is set. Try to use existing output folder.");
83 | }
84 |
85 | if (suppressTypesFromJavaRuntime) {
86 | logger.info(
87 | "Flag 'suppressTypesFromJavaRuntime' is set. Classes available in current Java-runtime will be ignored.");
88 | }
89 |
90 | initializeArtifactResolver(helper);
91 |
92 | initializeProject(helper);
93 |
94 | final Artifact artifact = resolveArtifact();
95 |
96 | // skip analyzes if the artifact has no associated file..
97 | if (artifact.getFile() == null) {
98 | logger.info("Nothing to analyze in '" + artifact.getId() + "'.");
99 | return;
100 | }
101 |
102 | // initialize the suppression filter
103 | filter = new ClassFilter(logger, suppressTypesFromJavaRuntime, regexIgnoredClasses);
104 |
105 | final Repository artifactClassesRepository = ArtifactRepositoryAnalyzer.analyzeArtifacts(logger,
106 | true,
107 | filter)
108 | .analyzeArtifacts(Collections.singleton(artifact));
109 |
110 | final Set dependencies = resolveDirectDependencies(artifact);
111 |
112 | final Repository dependenciesClassesRepository = ArtifactRepositoryAnalyzer.analyzeArtifacts(logger,
113 | false,
114 | filter)
115 | .analyzeArtifacts(dependencies);
116 |
117 | if (logger.isDebugEnabled()) {
118 | logger.debug("Artifact's type dependencies are: " + artifactClassesRepository.getDependencies());
119 | logger.debug("Classes defined in direct dependencies are: " + dependenciesClassesRepository.getTypes());
120 | }
121 |
122 | final Set unresolvedTypes = new HashSet(artifactClassesRepository.getDependencies());
123 | unresolvedTypes.removeAll(artifactClassesRepository.getTypes());
124 | unresolvedTypes.removeAll(dependenciesClassesRepository.getTypes());
125 |
126 | // traverse transitive dependencies to find the artifact a certain class is loaded from
127 | if (unresolvedTypes.isEmpty()) {
128 | logger.info("No illegal transitive dependencies found in '" + artifact.getId() + "'.");
129 | } else {
130 | final String message = buildOutput(artifact, unresolvedTypes);
131 |
132 | writeOutputFile(artifact, message);
133 |
134 | if (reportOnly) {
135 | logger.error(message);
136 | } else {
137 | throw new EnforcerRuleException(message);
138 | }
139 | }
140 | }
141 |
142 | @SuppressWarnings("unchecked")
143 | private Set resolveTransitiveDependencies(Artifact artifact) throws EnforcerRuleException {
144 | final DependencyNode root;
145 | try {
146 | root = dependencyGraphBuilder.buildDependencyGraph(project, null);
147 | } catch (DependencyGraphBuilderException e) {
148 | throw new EnforcerRuleException("Unable to build the dependency graph!", e);
149 | }
150 | if (logger.isDebugEnabled()) {
151 | logger.debug("Root node is '" + root + "'.");
152 | }
153 |
154 | final Set transitiveDependencies = new HashSet();
155 | traverseDependencyNodes(root, transitiveDependencies);
156 |
157 | final Set directDependencies = resolveDirectDependencies(artifact);
158 | transitiveDependencies.removeAll(directDependencies);
159 | if (logger.isDebugEnabled()) {
160 | logger.debug("Transitive dependencies are '" + transitiveDependencies + "'.");
161 | }
162 | return transitiveDependencies;
163 | }
164 |
165 | private void traverseDependencyNodes(DependencyNode node, Set transitiveDependencies)
166 | throws EnforcerRuleException {
167 | final List children = node.getChildren();
168 | if ((children == null) || children.isEmpty()) {
169 | return;
170 | }
171 | for (DependencyNode child : children) {
172 | final Artifact artifact = child.getArtifact();
173 | enforceArtifactResolution(artifact);
174 | if (logger.isDebugEnabled()) {
175 | logger.debug("Add dependency '" + artifact.getId() + "'");
176 | }
177 | transitiveDependencies.add(artifact);
178 | traverseDependencyNodes(child, transitiveDependencies);
179 | }
180 | }
181 |
182 | @SuppressWarnings("unchecked")
183 | private Set resolveDirectDependencies(Artifact artifact) {
184 | final Set dependencies = new HashSet(project.getDependencyArtifacts());
185 | dependencies.remove(artifact);
186 | if (logger.isDebugEnabled()) {
187 | logger.debug("Direct dependencies are '" + dependencies + "'.");
188 | }
189 | return dependencies;
190 | }
191 |
192 | @SuppressWarnings("unchecked")
193 | private void initializeProject(ExpressionEvaluator helper) throws EnforcerRuleException {
194 | try {
195 | project = (MavenProject) helper.evaluate("${project}");
196 |
197 | localRepository = (ArtifactRepository) helper.evaluate("${localRepository}");
198 | remoteRepositories = (List) helper.evaluate("${project.remoteArtifactRepositories}");
199 |
200 | outputDirectory = (String) helper.evaluate("${project.build.directory}");
201 |
202 | } catch (ExpressionEvaluationException e) {
203 | throw new EnforcerRuleException("Unable to locate Maven project and/or repositories!", e);
204 | }
205 | logger.debug("Analyze project '" + project + "'.");
206 | }
207 |
208 | private void initializeArtifactResolver(EnforcerRuleHelper helper) throws EnforcerRuleException {
209 | try {
210 | resolver = (ArtifactResolver) helper.getComponent(ArtifactResolver.class);
211 | } catch (ComponentLookupException e) {
212 | throw new EnforcerRuleException("Unable to lookup artifact resolver!", e);
213 | }
214 | }
215 |
216 | private void initializeDependencyGraphBuilder(EnforcerRuleHelper helper) throws EnforcerRuleException {
217 | try {
218 | dependencyGraphBuilder = helper.getContainer().lookup(DependencyGraphBuilder.class, "default");
219 | } catch (ComponentLookupException e) {
220 | throw new EnforcerRuleException("Unable to lookup dependency graph builder!", e);
221 | }
222 | }
223 |
224 | private Artifact resolveArtifact() throws EnforcerRuleException {
225 | final Artifact artifact = project.getArtifact();
226 | logger.info("Analyze dependencies of artifact '" + artifact.getId() + "'.");
227 |
228 | // use the current project's target/classes directory as fake artifact..
229 | if (useClassesFromLastBuild) {
230 | final File targetClassesDirectory = getTargetClassesDirectory();
231 | artifact.setFile(targetClassesDirectory);
232 | return artifact;
233 | }
234 |
235 | return enforceArtifactResolution(artifact);
236 | }
237 |
238 | private File getTargetClassesDirectory() {
239 | final Build build = project.getBuild();
240 | if (build != null) {
241 | final String classesOutputDirectory = build.getOutputDirectory();
242 | if (StringUtils.isNotEmpty(classesOutputDirectory)) {
243 | final File targetClasses = new File(classesOutputDirectory);
244 | if (targetClasses.isDirectory() && (targetClasses.list().length > 0)) {
245 | logger.debug("Found valid target/classes directory '" + targetClasses.getAbsolutePath() + "'.");
246 | return targetClasses;
247 | }
248 | }
249 | }
250 | logger.debug("No target/classes directory found.");
251 | return null;
252 | }
253 |
254 | private Artifact enforceArtifactResolution(Artifact artifact) throws EnforcerRuleException {
255 | logger.debug("Enforce artifact resolution for project '" + project + "'.");
256 | try {
257 | resolver.resolve(artifact, remoteRepositories, localRepository);
258 | return artifact;
259 | } catch (AbstractArtifactResolutionException e) {
260 | final String error = "Unable to resolve artifact '" + artifact.getId() + "'!";
261 | logger.error(error, e);
262 | throw new EnforcerRuleException(error, e);
263 | }
264 | }
265 |
266 | private String buildOutput(Artifact artifact, Set unresolvedTypes) throws EnforcerRuleException {
267 | final StringBuilder output = new StringBuilder();
268 | output.append("Found ")
269 | .append(unresolvedTypes.size())
270 | .append(" illegal transitive type dependencies in artifact '")
271 | .append(artifact.getId())
272 | .append("':\n");
273 |
274 | final List illegalTransitiveDependencies;
275 | if (listMissingArtifacts) {
276 | illegalTransitiveDependencies = new ArrayList(
277 | findArtifactsForUnresolvedTypes(artifact, unresolvedTypes));
278 | } else {
279 | illegalTransitiveDependencies = new ArrayList(unresolvedTypes);
280 | }
281 |
282 | Collections.sort(illegalTransitiveDependencies);
283 |
284 | int k = 1;
285 | for (String illegalTransitiveDependency : illegalTransitiveDependencies) {
286 | output.append(k).append(".) ").append(illegalTransitiveDependency).append("\n");
287 | k++;
288 | }
289 | return output.toString();
290 | }
291 |
292 | private Set findArtifactsForUnresolvedTypes(Artifact artifact, Set unresolvedTypes)
293 | throws EnforcerRuleException {
294 | final Set transitiveDependencies = resolveTransitiveDependencies(artifact);
295 | final Set unresolvedTypesWithArtifact = new HashSet();
296 |
297 | for (Artifact transitiveDependency : transitiveDependencies) {
298 | // skip further artifacts if all types have been found
299 | if (unresolvedTypesWithArtifact.size() == unresolvedTypes.size()) {
300 | break;
301 | }
302 |
303 | final Repository repository = ArtifactRepositoryAnalyzer.analyzeArtifacts(logger,
304 | false,
305 | filter)
306 | .analyzeArtifacts(Collections.singleton(transitiveDependency));
307 |
308 | final Set repositoryTypes = repository.getTypes();
309 | for (String unresolvedType : unresolvedTypes) {
310 | if (repositoryTypes.contains(unresolvedType)) {
311 | unresolvedTypesWithArtifact.add(unresolvedType + ", [" + transitiveDependency.getId() + "]");
312 | }
313 | }
314 | }
315 | return unresolvedTypesWithArtifact;
316 | }
317 |
318 | private void writeOutputFile(Artifact artifact, String output) throws EnforcerRuleException {
319 | if (outputDirectory == null) {
320 | logger.warn("Project's output directory has not been set, skip writing!");
321 | return;
322 | }
323 |
324 | final String outputFilePath = determineOutputFilePath(artifact);
325 | final File outputFile = new File(outputFilePath);
326 | final File targetFolder = outputFile.getParentFile();
327 | if (!targetFolder.exists() && !targetFolder.mkdirs()) {
328 | final String error = "Unable to create directory '" + targetFolder + "'!";
329 | logger.error(error);
330 | throw new EnforcerRuleException(error);
331 | }
332 |
333 | FileWriter resultFileWriter = null;
334 | try {
335 | resultFileWriter = new FileWriter(outputFile);
336 | resultFileWriter.write(output);
337 | } catch (IOException e) {
338 | throw logAndWrapIOException(e, outputFilePath);
339 | } finally {
340 | if (resultFileWriter != null) {
341 | try {
342 | resultFileWriter.close();
343 | } catch (IOException e) {
344 | throw logAndWrapIOException(e, outputFilePath);
345 | }
346 | }
347 | }
348 | }
349 |
350 | private EnforcerRuleException logAndWrapIOException(IOException e, String outputFilePath) {
351 | final String error = "Unable to write output file '" + outputFilePath + "'!";
352 | logger.error(error, e);
353 | return new EnforcerRuleException(error, e);
354 | }
355 |
356 | private String determineOutputFilePath(Artifact artifact) {
357 | final String separator = outputDirectory.endsWith("/") ? "" : "/";
358 | final String formattedArtifactId = artifact.getId().replace(':', '-');
359 | return outputDirectory + separator + OUTPUT_FILE_PREFIX + formattedArtifactId + OUTPUT_FILE_EXTENSION;
360 | }
361 |
362 | @Override
363 | public boolean isCacheable() {
364 | return false;
365 | }
366 |
367 | @Override
368 | public boolean isResultValid(EnforcerRule enforcerRule) {
369 | return false;
370 | }
371 |
372 | @Override
373 | public String getCacheId() {
374 | return NO_CACHE_ID_AVAILABLE;
375 | }
376 |
377 | public void setListMissingArtifacts(boolean listMissingArtifacts) {
378 | this.listMissingArtifacts = listMissingArtifacts;
379 | }
380 |
381 | public void setReportOnly(boolean reportOnly) {
382 | this.reportOnly = reportOnly;
383 | }
384 |
385 | public void setRegexIgnoredClasses(String[] regexIgnoredClasses) {
386 | this.regexIgnoredClasses = regexIgnoredClasses;
387 | }
388 |
389 | public void setUseClassesFromLastBuild(boolean useClassesFromLastBuild) {
390 | this.useClassesFromLastBuild = useClassesFromLastBuild;
391 | }
392 |
393 | public void setSuppressTypesFromJavaRuntime(boolean suppressTypesFromJavaRuntime) {
394 | this.suppressTypesFromJavaRuntime = suppressTypesFromJavaRuntime;
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/src/main/java/de/is24/maven/enforcer/rules/Repository.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import java.util.Collections;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | import static java.lang.String.format;
8 |
9 |
10 | final class Repository {
11 |
12 | private final Set types = new HashSet();
13 | private final Set dependencies = new HashSet();
14 |
15 | private final ClassFilter filter;
16 |
17 | Repository(ClassFilter filter) {
18 | this.filter = filter;
19 | }
20 |
21 | Set getTypes() {
22 | return Collections.unmodifiableSet(types);
23 | }
24 |
25 | Set getDependencies() {
26 | return Collections.unmodifiableSet(dependencies);
27 | }
28 |
29 | void addType(String type) {
30 | filter.addFiltered(types, type);
31 | }
32 |
33 | void addDependency(String type) {
34 | filter.addFiltered(dependencies, type);
35 | }
36 |
37 | @Override
38 | public String toString() {
39 | return format("Repository{types=%s, dependencies=%s}", types, dependencies);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/de/is24/maven/enforcer/rules/Types.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.objectweb.asm.Type;
4 |
5 |
6 | final class Types {
7 | private Types() {
8 | }
9 |
10 | static String readType(Type type) {
11 | switch (type.getSort()) {
12 | case Type.ARRAY: {
13 | return readType(type.getElementType());
14 | }
15 |
16 | default: {
17 | return type.getClassName();
18 | }
19 | }
20 | }
21 |
22 | static String readValueType(Object value) {
23 | final Type type;
24 | if (value instanceof Type) {
25 | type = (Type) value;
26 | } else {
27 | type = Type.getType(value.getClass());
28 | }
29 | return readType(type);
30 | }
31 |
32 | static String readTypeDescription(String description) {
33 | final Type type = Type.getType(description);
34 | return readType(type);
35 | }
36 |
37 | static String readInternalTypeName(String internalName) {
38 | final Type type = Type.getObjectType(internalName);
39 | return readType(type);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/de/is24/maven/enforcer/rules/ArtifactRepositoryAnalyzerTest.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.artifact.Artifact;
4 | import org.apache.maven.plugin.testing.stubs.ArtifactStub;
5 | import org.junit.Test;
6 |
7 | import java.io.File;
8 | import java.util.Collections;
9 |
10 | import static org.hamcrest.CoreMatchers.containsString;
11 | import static org.hamcrest.CoreMatchers.is;
12 | import static org.junit.Assert.assertThat;
13 | import static org.junit.Assert.fail;
14 |
15 |
16 | public class ArtifactRepositoryAnalyzerTest {
17 | @Test
18 | public void analyzeEmptyArtifact() {
19 | final LogStub logger = new LogStub();
20 | final ArtifactRepositoryAnalyzer analyzer = ArtifactRepositoryAnalyzer.analyzeArtifacts(
21 | logger,
22 | false,
23 | new ClassFilter(logger, false));
24 |
25 | final Artifact artifact = makeArtifact(null);
26 |
27 | final Repository repository = analyzer.analyzeArtifacts(
28 | Collections.singleton(artifact));
29 |
30 | assertThat(repository.getTypes().isEmpty(), is(true));
31 | assertThat(repository.getDependencies().isEmpty(), is(true));
32 | assertThat(logger.getInfoLog(),
33 | containsString("has no associated file, skip it."));
34 | }
35 |
36 | @Test
37 | public void analyzePomArtifact() {
38 | final LogStub logger = new LogStub();
39 | final ArtifactRepositoryAnalyzer analyzer = ArtifactRepositoryAnalyzer.analyzeArtifacts(
40 | logger,
41 | false,
42 | new ClassFilter(logger, false));
43 |
44 | final Artifact artifact = makeArtifact(new File("pom.xml"));
45 |
46 | final Repository repository = analyzer.analyzeArtifacts(
47 | Collections.singleton(artifact));
48 |
49 | assertThat(repository.getTypes().isEmpty(), is(true));
50 | assertThat(repository.getDependencies().isEmpty(), is(true));
51 | assertThat(logger.getInfoLog(), containsString("pom.xml', is skipped"));
52 | }
53 |
54 | @Test
55 | public void invalidJarArtifact() {
56 | final LogStub logger = new LogStub();
57 | final ArtifactRepositoryAnalyzer analyzer = ArtifactRepositoryAnalyzer.analyzeArtifacts(
58 | logger,
59 | false,
60 | new ClassFilter(logger, false));
61 |
62 | final Artifact artifact = makeArtifact(new File("invalid.jar"));
63 |
64 | final String expectedErrorMessage = "Unable to read class(es) from artifact 'invalid.jar'.";
65 | try {
66 | analyzer.analyzeArtifacts(Collections.singleton(artifact));
67 | fail("IllegalStateException expected!");
68 | } catch (IllegalStateException e) {
69 | assertThat(e.getMessage(), is(expectedErrorMessage));
70 | }
71 |
72 | assertThat(logger.getErrorLog(), containsString(expectedErrorMessage));
73 | }
74 |
75 | @Test
76 | public void readTypesFromClassDirectory() {
77 | final LogStub logger = new LogStub();
78 | final ArtifactRepositoryAnalyzer analyzer = ArtifactRepositoryAnalyzer.analyzeArtifacts(
79 | logger,
80 | false,
81 | new ClassFilter(logger, false));
82 |
83 | final File classFile = getCurrentClassFile();
84 | final File classesDirectory = new File(classFile.getParent());
85 | final Artifact artifact = makeArtifact(classesDirectory);
86 |
87 | final Repository repository = analyzer.analyzeArtifacts(
88 | Collections.singleton(artifact));
89 |
90 | assertThat(repository.getTypes().isEmpty(), is(false));
91 | assertThat(repository.getDependencies().isEmpty(), is(true));
92 | assertThat(logger.getDebugLog(), containsString(classFile.getPath()));
93 | }
94 |
95 | private File getCurrentClassFile() {
96 | final String resourcePath = "/" + ArtifactRepositoryAnalyzerTest.class.getName().replace(".", "/") + ".class";
97 | return new File(ArtifactRepositoryAnalyzerTest.class.getResource(resourcePath).getFile());
98 | }
99 |
100 | private Artifact makeArtifact(File file) {
101 | final Artifact artifact = new ArtifactStub();
102 | artifact.setArtifactId("artifactId");
103 | artifact.setGroupId("groupId");
104 | artifact.setScope("scope");
105 | artifact.setVersion("0.123456789");
106 | artifact.setFile(file);
107 | return artifact;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/test/java/de/is24/maven/enforcer/rules/ClassFileReference.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.artifact.Artifact;
4 | import org.apache.maven.model.Build;
5 | import org.apache.maven.project.MavenProject;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.io.File;
10 | import java.io.FileInputStream;
11 | import java.io.FileOutputStream;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.net.URL;
15 | import java.util.HashSet;
16 | import java.util.Set;
17 | import java.util.zip.ZipEntry;
18 | import java.util.zip.ZipOutputStream;
19 |
20 | import static java.lang.String.format;
21 |
22 | final class ClassFileReference {
23 | private static final Logger LOG = LoggerFactory.getLogger(ClassFileReference.class);
24 |
25 | private static final String CLASS_SUFFIX = ".class";
26 |
27 | private final String resource;
28 | private final File classFile;
29 |
30 | private ClassFileReference(String resource, File classFile) {
31 | this.resource = resource;
32 | this.classFile = classFile;
33 | }
34 |
35 | private static File replaceJarWithPacketClassFile(File jar, Set classFilesInJarReference) {
36 | final String fileName = jar.getAbsolutePath();
37 | jar.delete();
38 |
39 | final File newJar = new File(fileName);
40 |
41 | try {
42 | try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(jar))) {
43 | for (ClassFileReference classFileReference : classFilesInJarReference) {
44 | try(InputStream in = new FileInputStream(classFileReference.getClassFile())) {
45 | final byte[] buffer = new byte[1024];
46 |
47 | zipOutputStream.putNextEntry(new ZipEntry(classFileReference.getResource()));
48 |
49 | int bytesRead = in.read(buffer);
50 | do {
51 | zipOutputStream.write(buffer, 0, bytesRead);
52 | bytesRead = in.read(buffer);
53 | } while (bytesRead > 0);
54 | zipOutputStream.closeEntry();
55 | }
56 | }
57 | }
58 | } catch (IOException e) {
59 | final String error = "Unable to pack class files '" + classFilesInJarReference + "'!";
60 | LOG.error(error, e);
61 | throw new IllegalStateException(error, e);
62 | }
63 | return newJar;
64 | }
65 |
66 | private static Set makeClassFileSet(Class>... classes) {
67 | final Set fileEntries = new HashSet<>();
68 | for (Class> clazz : classes) {
69 | final ClassLoader classLoader = clazz.getClassLoader();
70 | final String resource = clazz.getName().replace('.', '/') + CLASS_SUFFIX;
71 |
72 | // validate that the class file is accessible..
73 | final URL url = classLoader.getResource(resource);
74 | if (url == null) {
75 | final String error = "Test class file '" + resource + "' not readable!";
76 | LOG.error(error);
77 | throw new IllegalStateException(error);
78 | }
79 |
80 | final File classFile = new File(url.getFile());
81 | fileEntries.add(new ClassFileReference(resource, classFile));
82 | }
83 | return fileEntries;
84 | }
85 |
86 | static void prepareArtifactTargetClassesDirectory(MavenProject project, Class> clazz) {
87 | final ClassFileReference classFileReference = makeClassFileSet(clazz).iterator().next();
88 | final Build build = new Build();
89 | project.setBuild(build);
90 | build.setDirectory(classFileReference.getClassFile().getParentFile().getAbsolutePath());
91 | build.setOutputDirectory(classFileReference.getClassFile().getParentFile().getAbsolutePath());
92 | }
93 |
94 | static void makeArtifactJarFromClassFile(Artifact artifact, Class>... classes) {
95 | artifact.setFile(replaceJarWithPacketClassFile(artifact.getFile(), makeClassFileSet(classes)));
96 | }
97 |
98 | String getResource() {
99 | return resource;
100 | }
101 |
102 | File getClassFile() {
103 | return classFile;
104 | }
105 |
106 | @Override
107 | public String toString() {
108 | return format("ClassFileReference{resource='%s', classFile=%s}", resource, classFile);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/test/java/de/is24/maven/enforcer/rules/ClassFilterTest.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.apache.maven.plugin.logging.Log;
5 | import org.junit.Test;
6 |
7 | import javax.sql.DataSource;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | import static org.hamcrest.CoreMatchers.is;
12 | import static org.junit.Assert.assertThat;
13 |
14 | public class ClassFilterTest {
15 | private final Log logger = new LogStub();
16 |
17 |
18 | @Test
19 | public void testSuppressionOfNativeTypes() throws Exception {
20 | final ClassFilter filter = new ClassFilter(logger, true);
21 | assertThat(filter.isConsideredType("byte"), is(false));
22 | assertThat(filter.isConsideredType("int"), is(false));
23 | assertThat(filter.isConsideredType("long"), is(false));
24 |
25 | final Set types = new HashSet<>();
26 | filter.addFiltered(types, "char");
27 | filter.addFiltered(types, "float");
28 | filter.addFiltered(types, "double");
29 | assertThat(types.isEmpty(), is(true));
30 | }
31 |
32 | @Test
33 | public void testSuppressionOfJdkTypes() {
34 | final ClassFilter filter = new ClassFilter(logger, true);
35 | final Set types = new HashSet<>();
36 |
37 | // add a package not in the current JDK
38 | assertThat(filter.isConsideredType(StringUtils.class.getName()), is(true));
39 |
40 | // add a package that is part of all JDKs
41 | assertThat(filter.isConsideredType(DataSource.class.getName()), is(false));
42 |
43 | // the same tests for filtered adding
44 | filter.addFiltered(types, StringUtils.class.getName());
45 | filter.addFiltered(types, DataSource.class.getName());
46 |
47 | assertThat(types.size(), is(1));
48 | assertThat(types.iterator().next(), is(StringUtils.class.getName()));
49 | }
50 |
51 | @Test
52 | public void testSuppressionOfClasses() {
53 | final ClassFilter filter = new ClassFilter(logger, false, "de\\.is24\\.suppress.*", ".*SuppressMe.*");
54 |
55 | assertThat(filter.isConsideredType("de.is24.package.Type"), is(true));
56 | assertThat(filter.isConsideredType("de.is24.package.subpackage.Type"), is(true));
57 | assertThat(filter.isConsideredType("de.is24.package.Type$Subtype"), is(true));
58 |
59 | assertThat(filter.isConsideredType("de.is24.suppress.subpackage.Type"), is(false));
60 | assertThat(filter.isConsideredType("de.is24.suppress.Type"), is(false));
61 | assertThat(filter.isConsideredType("de.is24.suppress.Type$SubType"), is(false));
62 |
63 | assertThat(filter.isConsideredType("de.is24.SuppressMe"), is(false));
64 | assertThat(filter.isConsideredType("de.is24.SuppressMe$Subtype"), is(false));
65 | }
66 | }
--------------------------------------------------------------------------------
/src/test/java/de/is24/maven/enforcer/rules/EnforcerRuleHelperWrapper.java:
--------------------------------------------------------------------------------
1 | package de.is24.maven.enforcer.rules;
2 |
3 | import org.apache.maven.artifact.Artifact;
4 | import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
5 | import org.codehaus.plexus.PlexusContainer;
6 | import org.codehaus.plexus.PlexusContainerException;
7 | import org.codehaus.plexus.classworlds.realm.ClassRealm;
8 | import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
9 | import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
10 | import org.codehaus.plexus.component.discovery.ComponentDiscoveryListener;
11 | import org.codehaus.plexus.component.repository.ComponentDescriptor;
12 | import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
13 | import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
14 | import org.codehaus.plexus.configuration.PlexusConfigurationException;
15 | import org.codehaus.plexus.context.Context;
16 | import java.io.File;
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.Set;
21 |
22 |
23 | final class EnforcerRuleHelperWrapper implements EnforcerRuleHelper {
24 | private final EnforcerRuleHelper wrappedEnforcerRuleHelper;
25 | private final Map components = new HashMap<>();
26 |
27 | private final PlexusContainerWrapper plexusContainerWrapper;
28 | private final LogStub logStub = new LogStub();
29 |
30 | private Artifact projectArtifact;
31 | private Artifact directDependencyArtifact;
32 | private Set transitiveDependencyArtifacts;
33 |
34 | public Artifact getDirectDependencyArtifact() {
35 | return directDependencyArtifact;
36 | }
37 |
38 | public void setDirectDependencyArtifact(Artifact directDependencyArtifact) {
39 | this.directDependencyArtifact = directDependencyArtifact;
40 | }
41 |
42 | public Set getTransitiveDependencyArtifacts() {
43 | return transitiveDependencyArtifacts;
44 | }
45 |
46 | public void setTransitiveDependencyArtifacts(Set transitiveDependencyArtifacts) {
47 | this.transitiveDependencyArtifacts = transitiveDependencyArtifacts;
48 | }
49 |
50 | public Artifact getProjectArtifact() {
51 | return projectArtifact;
52 | }
53 |
54 | public void setProjectArtifact(Artifact projectArtifact) {
55 | this.projectArtifact = projectArtifact;
56 | }
57 |
58 | EnforcerRuleHelperWrapper(EnforcerRuleHelper wrappedEnforcerRuleHelper) {
59 | this.wrappedEnforcerRuleHelper = wrappedEnforcerRuleHelper;
60 | plexusContainerWrapper = new PlexusContainerWrapper(wrappedEnforcerRuleHelper.getContainer());
61 | }
62 |
63 | void addComponent(Object component, Class> key) {
64 | components.put(key.getName(), component);
65 | }
66 |
67 | @Override
68 | public LogStub getLog() {
69 | return logStub;
70 | }
71 |
72 | @Override
73 | public Object getComponent(Class clazz) throws ComponentLookupException {
74 | return getComponent(clazz.getName());
75 | }
76 |
77 | @Override
78 | public Object getComponent(String componentKey) throws ComponentLookupException {
79 | if (components.containsKey(componentKey)) {
80 | return components.get(componentKey);
81 | }
82 | return wrappedEnforcerRuleHelper.getComponent(componentKey);
83 | }
84 |
85 | @Override
86 | public Object getComponent(String role, String roleHint) throws ComponentLookupException {
87 | return wrappedEnforcerRuleHelper.getComponent(role, roleHint);
88 | }
89 |
90 | @Override
91 | public Map getComponentMap(String role) throws ComponentLookupException {
92 | return wrappedEnforcerRuleHelper.getComponentMap(role);
93 | }
94 |
95 | @Override
96 | public List getComponentList(String role) throws ComponentLookupException {
97 | return wrappedEnforcerRuleHelper.getComponentList(role);
98 | }
99 |
100 |
101 | @Override
102 | public PlexusContainerWrapper getContainer() {
103 | return plexusContainerWrapper;
104 | }
105 |
106 | @Override
107 | public Object evaluate(String expression) throws ExpressionEvaluationException {
108 | return wrappedEnforcerRuleHelper.evaluate(expression);
109 | }
110 |
111 | @Override
112 | public File alignToBaseDirectory(File file) {
113 | return wrappedEnforcerRuleHelper.alignToBaseDirectory(file);
114 | }
115 |
116 | public static final class PlexusContainerWrapper implements PlexusContainer {
117 | private final PlexusContainer plexusContainer;
118 |
119 | private final Map objects = new HashMap();
120 |
121 | private PlexusContainerWrapper(PlexusContainer plexusContainer) {
122 | this.plexusContainer = plexusContainer;
123 | }
124 |
125 | @Override
126 | public Object lookup(String role) throws ComponentLookupException {
127 | return plexusContainer.lookup(role);
128 | }
129 |
130 | @Override
131 | public Object lookup(String role, String roleHint) throws ComponentLookupException {
132 | return plexusContainer.lookup(role, roleHint);
133 | }
134 |
135 | @Override
136 | public T lookup(Class type) throws ComponentLookupException {
137 | return plexusContainer.lookup(type);
138 | }
139 |
140 | @Override
141 | @SuppressWarnings("unchecked")
142 | public T lookup(Class type, String roleHint) throws ComponentLookupException {
143 | final T object = (T) objects.get(type.getCanonicalName() + "#" + roleHint);
144 | return (object != null) ? object : plexusContainer.lookup(type, roleHint);
145 | }
146 |
147 | @Override
148 | public T lookup(Class type, String role, String roleHint) throws ComponentLookupException {
149 | return plexusContainer.lookup(type, role, roleHint);
150 | }
151 |
152 | @Override
153 | public T lookup(ComponentDescriptor componentDescriptor) throws ComponentLookupException {
154 | return plexusContainer.lookup(componentDescriptor);
155 | }
156 |
157 | @Override
158 | public List