├── .coveralls.yml ├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENCE ├── NOTICE ├── README.md ├── RELEASE ├── benchmarks ├── capture_flight_recording.sh ├── pom.xml ├── profile.jfc └── src │ └── main │ └── java │ └── com │ └── spotify │ └── missinglink │ └── benchmarks │ └── PrimitiveTypeDescriptorBenchmark.java ├── catalog-info.yaml ├── core ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── spotify │ │ └── missinglink │ │ ├── ArtifactLoader.java │ │ ├── CheckerState.java │ │ ├── ClassLoader.java │ │ ├── Conflict.java │ │ ├── ConflictChecker.java │ │ ├── Java9ModuleLoader.java │ │ ├── MissingLinkException.java │ │ ├── PrettyPrinter.java │ │ └── datamodel │ │ ├── AccessedField.java │ │ ├── ArrayTypeDescriptor.java │ │ ├── Artifact.java │ │ ├── ArtifactName.java │ │ ├── CalledMethod.java │ │ ├── ClassTypeDescriptor.java │ │ ├── DeclaredClass.java │ │ ├── DeclaredField.java │ │ ├── DeclaredMethod.java │ │ ├── Dependency.java │ │ ├── FieldDependency.java │ │ ├── MethodDependency.java │ │ ├── MethodDescriptor.java │ │ ├── MethodDescriptors.java │ │ ├── PrimitiveTypeDescriptor.java │ │ ├── TypeDescriptor.java │ │ ├── TypeDescriptors.java │ │ └── VoidTypeDescriptor.java │ └── test │ ├── java │ └── com │ │ └── spotify │ │ └── missinglink │ │ ├── ArtifactLoaderTest.java │ │ ├── ClassLoaderTest.java │ │ ├── ClassLoadingUtil.java │ │ ├── ConflictCheckerTest.java │ │ ├── FeatureTest.java │ │ ├── FilePathHelper.java │ │ ├── PrettyPrinterTest.java │ │ ├── ReachableTest.java │ │ ├── Simple.java │ │ ├── TypeDescriptorTest.java │ │ ├── datamodel │ │ └── MethodDescriptorsTest.java │ │ └── nested │ │ └── ClassWithNestedClass.java │ └── resources │ ├── ArtifactLoaderTest.jar │ ├── asm-5.0.4.jar │ └── bcprov-jdk15on-1.68.jar ├── examples ├── README.md ├── has-problematic-dependency │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── spotify │ │ └── javadeps │ │ └── examples │ │ └── ProblematicDependency.java ├── pom.xml └── uses-problematic-dependency │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── spotify │ └── javadeps │ └── examples │ └── UsesProblematicDependency.java ├── execute-maven-plugin.sh ├── install-maven-plugin.sh ├── maven-plugin ├── pom.xml └── src │ ├── it │ ├── 00-test-harness │ │ ├── a-class-duplicate │ │ │ ├── pom.xml │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── A.java │ │ ├── a-v1 │ │ │ ├── pom.xml │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── A.java │ │ │ │ ├── AnInterface.java │ │ │ │ ├── MethodWillBeRemoved.java │ │ │ │ ├── Misc.java │ │ │ │ ├── SpecificReturnType.java │ │ │ │ ├── WillGoAway.java │ │ │ │ └── com │ │ │ │ └── spotify │ │ │ │ └── missinglink │ │ │ │ └── d │ │ │ │ └── WillGoAway.java │ │ ├── a-v2 │ │ │ ├── pom.xml │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── A.java │ │ │ │ ├── AnInterface.java │ │ │ │ ├── MethodWillBeRemoved.java │ │ │ │ └── SpecificReturnType.java │ │ ├── b │ │ │ ├── pom.xml │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── AnInterfaceImplementation.java │ │ │ │ ├── B.java │ │ │ │ ├── C.java │ │ │ │ └── com │ │ │ │ └── spotify │ │ │ │ └── missinglink │ │ │ │ └── e │ │ │ │ └── E.java │ │ └── pom.xml │ ├── class-missing-target-destination │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── spotify │ │ │ │ └── missinglink │ │ │ │ └── ClassMissingAllowDestination.java │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── spotify │ │ │ └── missinglink │ │ │ └── ClassMissingAllowDestinationTest.java │ ├── class-missing │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── ClassMissing.java │ │ │ └── test │ │ │ └── java │ │ │ └── ClassMissingTest.java │ ├── field-removed │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── FieldRemoved.java │ │ │ └── test │ │ │ └── java │ │ │ └── FieldRemovedTest.java │ ├── instantiate-with-method-missing │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── InstantiateWithMethodMissing.java │ │ │ └── test │ │ │ └── java │ │ │ └── InstantiateWithMethodMissingTest.java │ ├── library-invokes-removed-method │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── LibraryInvokesRemovedMethod.java │ │ │ └── test │ │ │ └── java │ │ │ └── LibraryInvokesRemovedMethodTest.java │ ├── method-became-inaccessible │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── MethodBecameInaccessible.java │ │ │ └── test │ │ │ └── java │ │ │ └── MethodBecameInaccessibleTest.java │ ├── method-removed │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── MethodRemoved.java │ │ │ └── test │ │ │ └── java │ │ │ └── MethodRemovedTest.java │ ├── return-type-change │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── ReturnTypeChange.java │ │ │ └── test │ │ │ └── java │ │ │ └── ReturnTypeChangeTest.java │ ├── same-class-different-artifact │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── SameClassDifferentArtifact.java │ │ │ └── test │ │ │ └── java │ │ │ └── SameClassDifferentArtifactTest.java │ ├── settings.xml │ ├── static-method-became-instance │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── StaticMethodBecameInstance.java │ │ │ └── test │ │ │ └── java │ │ │ └── StaticMethodBecameInstanceTest.java │ └── superclass-missing │ │ ├── pom.xml │ │ └── src │ │ ├── main │ │ └── java │ │ │ └── SuperClassMissing.java │ │ └── test │ │ └── java │ │ └── SuperClassMissingTest.java │ ├── main │ └── java │ │ └── com │ │ └── spotify │ │ └── missinglink │ │ └── maven │ │ ├── CheckMojo.java │ │ ├── MavenArtifactName.java │ │ ├── PackageFilter.java │ │ └── Scope.java │ └── test │ ├── java │ └── com │ │ └── spotify │ │ └── missinglink │ │ └── maven │ │ ├── CheckMojoTest.java │ │ └── MavenArtifactNameTest.java │ └── projects │ ├── exclude-dependencies │ └── pom.xml │ ├── fail-on-warning │ └── pom.xml │ ├── ignore-destination-packages │ └── pom.xml │ ├── ignore-source-packages │ └── pom.xml │ ├── include-categories-bad-values │ └── pom.xml │ ├── include-categories │ └── pom.xml │ ├── simple-test │ └── pom.xml │ ├── skip │ └── pom.xml │ ├── target-destination-packages │ └── pom.xml │ └── target-source-packages │ └── pom.xml ├── pom.xml └── system-tests └── b └── src └── main └── java └── C.java /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Build/Test 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | java_version: [8, 11, 17, 21] 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 2 24 | - name: Set up JDK ${{ matrix.java_version }} 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: 'zulu' 28 | java-version: ${{ matrix.java_version }} 29 | - name: Cache Maven packages 30 | uses: actions/cache@v4.0.2 31 | with: 32 | path: ~/.m2 33 | key: ${{ runner.os }}-java-${{ matrix.java_version }}-m2-${{ hashFiles('**/pom.xml') }} 34 | restore-keys: ${{ runner.os }}-java-${{ matrix.java_version }}-m2 35 | - name: Build with Maven 36 | run: mvn --batch-mode --update-snapshots fmt:check verify 37 | - name: Codecov 38 | uses: codecov/codecov-action@v4 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/*.iml 3 | **/target 4 | release.properties 5 | *.releaseBackup 6 | benchmarks/dependency-reduced-pom.xml 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.2.6 2 | - Added `targetSourcePackages` and `targetDestinationPackages` configuration options 3 | - Renamed `ignoreSubpackages` configuration option to `filterSubpackages` 4 | 5 | ### 0.2.5 6 | 7 | - Added `java.lang.invoke.VarHandle` to the list of classes with 8 | `@HotSpotIntrinsicCandidate` annotated methods which are excluded from analysis. 9 | Calls to `VarHandle` methods will no longer result in missinglink generating 10 | false warnings about methods like `getAndSet` not existing. 11 | 12 | ### 0.2.4 13 | 14 | - Handle EA versions of Java 15 | 16 | ### 0.2.3 17 | 18 | Upgraded a number of dependencies: 19 | 20 | - asm-tree updated to 9.1 21 | - auto-matter-annotation updated to 0.16.0 22 | - Guava updated to 30.1.1-jre 23 | - maven-plugin-api updated to 3.8.1 24 | - maven-artifact updated to 3.8.1 25 | - maven-compat updated to 3.8.1 26 | - maven-core updated to 3.8.1 27 | - maven-plugin-plugin updated to 3.6.1 28 | 29 | Internal changes: 30 | 31 | - moved to Github Actions for CI 32 | - replaced Checkstyle with com.coveo:fmt-maven-plugin 33 | - jmh.version updated to 1.32 34 | - mockito-core updated to 3.11.0 35 | - junit updated to 4.13.2 36 | 37 | ### 0.2.2 38 | - Handle Multi-Release JARs 39 | - Use thread-safe caches to enable using Missinglink on multiple projects concurrently 40 | 41 | ### 0.2.1 42 | - Fix bug with false positives for calls to MethodHandle.invoke() 43 | 44 | ### 0.2.0 45 | - Remove dependencies on Guava 46 | 47 | ### 0.1.3 48 | - Plugin now checks if exceptions that would get thrown are explicitly caught to avoid false positives 49 | 50 | ### 0.1.2 51 | - if no classes found in build directory, log a suggestion to run `mvn compile` 52 | first. 53 | - internal refactoring: add integration tests to Maven build via 54 | maven-invoker-plugin (thanks @dflemstr) 55 | - made plugin java9 compatible 56 | 57 | ### 0.1.1 58 | - rename goal in maven-plugin from `check-conflicts` to `check` 59 | - performance improvements - missinglink allocates less objects during 60 | analysis, therefore creating less pressure and spending less time in GC, and 61 | overall operating faster. 62 | 63 | ### 0.1.0 (initial release) 64 | - core project and maven plugin 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to missinglink 2 | 3 | First of all, thanks for the interest in missinglink! :clap: 4 | 5 | As mentioned in the README, this project is very young and not mature yet. 6 | 7 | It was built during a Hack Week at Spotify to attempt to catch a certain type 8 | of dependency conflict with Maven projects that the authors had bad experiences 9 | with. 10 | 11 | ## Submitting issues 12 | 13 | We'd like for it to be able to catch lots of other types of conflicts but there 14 | are a lot of scenarios we have not encountered or will be able to test, so any 15 | and all feedback is appreciated. 16 | 17 | To report any issues or share feedback you have, please create [an issue on 18 | Github][github-issues]. We'd like to hear about false positives that this tool 19 | reports for you, or conflicts that it seems like *should* be caught but 20 | weren't. 21 | 22 | [github-issues]: https://github.com/spotify/missinglink/issues 23 | 24 | ## Building the project 25 | 26 | With Java 8+ and Maven 3+, simply run `mvn install` in the project directory. 27 | 28 | For any patches, please make sure that they pass all tests (and that new tests 29 | are added). 30 | 31 | Code will be automatically formatted by 32 | when you run `mvn compile`. The CI build will fail if any code is not formatted 33 | according to the plugin's rules. 34 | 35 | New code should be accompanied by tests that increase the overall code coverage 36 | of the project. 37 | 38 | ## Additional documentation 39 | 40 | - [ASM][asm] is used to read the bytecode of Java classfiles 41 | - [auto-matter][] is used to generate "value classes" for missinglink's data model 42 | - [Reference on Maven plugin development][maven-plugins] 43 | 44 | [asm]: http://asm.ow2.org/ 45 | [maven-plugins]: https://maven.apache.org/guides/plugin/guide-java-plugin-development.html 46 | [auto-matter]: https://github.com/danielnorberg/auto-matter 47 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | folsom 2 | Copyright 2015 Spotify AB 3 | 4 | This product includes software developed at 5 | Spotify AB (http://www.spotify.com/). 6 | 7 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | How to release: 2 | 3 | Add this to ~/.m2/settings.xml 4 | 5 | Needed: 6 | 7 | 8 | 9 | ossrh 10 | your-sonatype-jira-username 11 | your-sonatype-jira-pwd 12 | 13 | 14 | 15 | 16 | 17 | Optional: 18 | 19 | 20 | ossrh 21 | 22 | true 23 | 24 | 25 | gpg 26 | PASSPHRASE 27 | 28 | 29 | 30 | 31 | then to release: 32 | 33 | $ mvn release:clean release:prepare 34 | $ mvn release:perform 35 | 36 | -------------------------------------------------------------------------------- /benchmarks/capture_flight_recording.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | if [[ $# -lt 2 ]]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | RECORDING_FILENAME=$1 9 | POM_PATH=$2 10 | 11 | if [[ ! -e $POM_PATH ]]; then 12 | echo "Error: $POM_PATH does not exist!" 13 | exit 2 14 | fi 15 | 16 | PLUGIN=com.spotify:missinglink-maven-plugin:0.1.1-SNAPSHOT 17 | # use a modified "profile" template for flight recorder, the default won't have some stuff enabled 18 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 19 | JFR_SETTINGS=$DIR/profile.jfc 20 | 21 | # launch maven 22 | JFR_OPTS="-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=$RECORDING_FILENAME,settings=$JFR_SETTINGS" 23 | MAVEN_OPTS=$JFR_OPTS mvn $PLUGIN:check -f "$POM_PATH" 24 | -------------------------------------------------------------------------------- /benchmarks/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.spotify 7 | missinglink-parent 8 | 0.2.12-SNAPSHOT 9 | 10 | 11 | missinglink-benchmarks 12 | jar 13 | 14 | 15 | 17 | true 18 | 1.37 19 | benchmarks 20 | false 21 | 22 | 23 | 24 | 25 | com.spotify 26 | missinglink-core 27 | ${project.version} 28 | 29 | 30 | org.openjdk.jmh 31 | jmh-core 32 | ${jmh.version} 33 | 34 | 35 | org.openjdk.jmh 36 | jmh-generator-annprocess 37 | ${jmh.version} 38 | provided 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-shade-plugin 48 | 3.6.0 49 | 50 | 51 | package 52 | 53 | shade 54 | 55 | 56 | ${uberjar.name} 57 | 58 | 59 | org.openjdk.jmh.Main 60 | 61 | 62 | 63 | 64 | *:* 65 | 66 | META-INF/*.SF 67 | META-INF/*.DSA 68 | META-INF/*.RSA 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-deploy-plugin 81 | 82 | true 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/com/spotify/missinglink/benchmarks/PrimitiveTypeDescriptorBenchmark.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-benchmarks 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | 37 | package com.spotify.missinglink.benchmarks; 38 | 39 | import com.spotify.missinglink.datamodel.PrimitiveTypeDescriptor; 40 | import java.util.HashMap; 41 | import java.util.Map; 42 | import java.util.concurrent.ThreadLocalRandom; 43 | import org.openjdk.jmh.annotations.Benchmark; 44 | import org.openjdk.jmh.annotations.Level; 45 | import org.openjdk.jmh.annotations.Scope; 46 | import org.openjdk.jmh.annotations.Setup; 47 | import org.openjdk.jmh.annotations.State; 48 | 49 | @State(Scope.Benchmark) 50 | public class PrimitiveTypeDescriptorBenchmark { 51 | 52 | /** 53 | * This class holds the raw String value to use to pass to {@link 54 | * PrimitiveTypeDescriptor#fromRaw(String)}. The annotations used set up JMH to use the one holder 55 | * per test thread, and set up a new value for each iteration of the benchmark (not each 56 | * invocation). 57 | */ 58 | @State(Scope.Thread) 59 | public static class RawStringHolder { 60 | 61 | private String value; 62 | 63 | @Setup(Level.Iteration) 64 | public void assignValue() { 65 | final int size = PrimitiveTypeDescriptor.values().length; 66 | int ix = ThreadLocalRandom.current().nextInt(size); 67 | this.value = PrimitiveTypeDescriptor.values()[ix].getRaw(); 68 | } 69 | } 70 | 71 | /** Test the ImmutableMap lookup method in the current source code. */ 72 | @Benchmark 73 | public PrimitiveTypeDescriptor originalMethod(RawStringHolder holder) { 74 | return PrimitiveTypeDescriptor.fromRaw(holder.value); 75 | } 76 | 77 | // ------------------------------------------------------------------------------------ 78 | // test using a hashmap instead of ImmutableMap 79 | 80 | private Map hashMap; 81 | 82 | @Setup(Level.Trial) 83 | public void setup() { 84 | this.hashMap = new HashMap<>(); 85 | for (PrimitiveTypeDescriptor ptd : PrimitiveTypeDescriptor.values()) { 86 | this.hashMap.put(ptd.getRaw(), ptd); 87 | } 88 | } 89 | 90 | @Benchmark 91 | public PrimitiveTypeDescriptor fromHashMap(RawStringHolder holder) { 92 | return hashMap.get(holder.value); 93 | } 94 | 95 | // ------------------------------------------------------------------------------------ 96 | // what if we just iterated over all 8 enums each time? 97 | 98 | @Benchmark 99 | public PrimitiveTypeDescriptor dumbIteration(RawStringHolder holder) { 100 | for (PrimitiveTypeDescriptor ptd : PrimitiveTypeDescriptor.values()) { 101 | if (ptd.getRaw().equals(holder.value)) { 102 | return ptd; 103 | } 104 | } 105 | return null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Resource 3 | metadata: 4 | name: missinglink 5 | spec: 6 | type: library 7 | owner: weaver 8 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.spotify 7 | missinglink-parent 8 | 0.2.12-SNAPSHOT 9 | 10 | 11 | missinglink-core 12 | jar 13 | 14 | 15 | 16 | javax.annotation 17 | javax.annotation-api 18 | 1.3.2 19 | 20 | 21 | org.ow2.asm 22 | asm-tree 23 | 9.7 24 | 25 | 26 | io.norberg 27 | auto-matter-annotation 28 | ${auto-matter.version} 29 | 30 | 31 | junit 32 | junit 33 | 4.13.2 34 | test 35 | 36 | 37 | org.assertj 38 | assertj-core 39 | 3.26.3 40 | test 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-compiler-plugin 49 | 50 | 51 | 52 | io.norberg 53 | auto-matter 54 | ${auto-matter.version} 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/ArtifactLoader.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import static java.util.stream.Collectors.toList; 39 | 40 | import com.spotify.missinglink.datamodel.Artifact; 41 | import com.spotify.missinglink.datamodel.ArtifactBuilder; 42 | import com.spotify.missinglink.datamodel.ArtifactName; 43 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 44 | import com.spotify.missinglink.datamodel.DeclaredClass; 45 | import java.io.File; 46 | import java.io.FileInputStream; 47 | import java.io.IOException; 48 | import java.nio.file.Files; 49 | import java.nio.file.Path; 50 | import java.util.Collection; 51 | import java.util.Collections; 52 | import java.util.HashMap; 53 | import java.util.List; 54 | import java.util.Map; 55 | import java.util.SortedMap; 56 | import java.util.TreeMap; 57 | import java.util.jar.JarEntry; 58 | import java.util.jar.JarFile; 59 | import java.util.regex.Matcher; 60 | import java.util.regex.Pattern; 61 | 62 | public class ArtifactLoader { 63 | 64 | /** Load artifact at path, using path name as artifactId */ 65 | public Artifact load(File path) throws IOException { 66 | return load(new ArtifactName(path.getName()), path); 67 | } 68 | 69 | /** Load artifact at path with a custom artifactId */ 70 | public Artifact load(ArtifactName artifactName, File path) throws IOException { 71 | if (!path.exists()) { 72 | throw new IllegalArgumentException("Path must exist: " + path); 73 | } 74 | if (!path.isFile() && !path.isDirectory()) { 75 | throw new IllegalArgumentException("Path must be a file or directory: " + path); 76 | } 77 | if (path.isFile()) { 78 | return loadFromJar(artifactName, path); 79 | } 80 | return loadFromDirectory(artifactName, path); 81 | } 82 | 83 | // This is designed to handle Multi-Release JAR files, where there are class files for multiple 84 | // versions of JVM in one jar. 85 | // You don't want to end up trying to parse a new class file when running on an old JVM. 86 | // https://openjdk.java.net/jeps/238 87 | private Collection getClassesForCurrentJavaVersion(Iterable entries) { 88 | // First categorize all the found class files by their target JVM 89 | // classFilesPerJavaVersion: target JVM version -> normalized full name -> JarEntry 90 | SortedMap> classFilesPerJavaVersion = new TreeMap<>(); 91 | String patternString = "META-INF/versions/(\\d+)/"; 92 | Pattern pattern = Pattern.compile(patternString); 93 | for (JarEntry entry : entries) { 94 | String fileFullName = entry.getName(); 95 | if (fileFullName.endsWith(".class")) { 96 | Matcher m = pattern.matcher(fileFullName); 97 | if (m.find()) { 98 | Integer targetJavaVersion = Integer.parseInt(m.group(1)); 99 | String normalizedFileFullName = fileFullName.replaceAll(patternString, ""); 100 | 101 | if (!classFilesPerJavaVersion.containsKey(targetJavaVersion)) { 102 | classFilesPerJavaVersion.put(targetJavaVersion, new HashMap<>()); 103 | } 104 | classFilesPerJavaVersion.get(targetJavaVersion).put(normalizedFileFullName, entry); 105 | } else { 106 | Integer withoutVersion = 0; 107 | if (!classFilesPerJavaVersion.containsKey(withoutVersion)) { 108 | classFilesPerJavaVersion.put(withoutVersion, new HashMap<>()); 109 | } 110 | classFilesPerJavaVersion.get(withoutVersion).put(fileFullName, entry); 111 | } 112 | } 113 | } 114 | 115 | // We have to figure out what JVM version we're running on. 116 | Integer currentJavaVersion; 117 | String[] javaVersionElements = 118 | System.getProperty("java.version").replaceAll("\\-ea", "").split("\\."); 119 | if (javaVersionElements[0].equals("1")) { 120 | currentJavaVersion = Integer.parseInt(javaVersionElements[1]); 121 | } else { 122 | currentJavaVersion = Integer.parseInt(javaVersionElements[0]); 123 | } 124 | 125 | // Start layering the class files from old JVM version to new and thus effectively override the 126 | // old files by the new ones. 127 | Map selectedClassFiles = new HashMap<>(); 128 | for (Map.Entry> entry : classFilesPerJavaVersion.entrySet()) { 129 | Integer targetJavaVersion = entry.getKey(); 130 | if (targetJavaVersion > currentJavaVersion) { 131 | break; 132 | } 133 | Map pathToClassfile = entry.getValue(); 134 | selectedClassFiles.putAll(pathToClassfile); 135 | } 136 | 137 | return selectedClassFiles.values(); 138 | } 139 | 140 | private Artifact loadFromJar(ArtifactName artifactName, File path) { 141 | try (JarFile jarFile = new JarFile(path)) { 142 | Map classes = new HashMap<>(); 143 | Iterable classFiles = 144 | getClassesForCurrentJavaVersion(Collections.list(jarFile.entries())); 145 | for (JarEntry entry : classFiles) { 146 | try { 147 | DeclaredClass cl = ClassLoader.load(jarFile.getInputStream(entry)); 148 | classes.put(cl.className(), cl); 149 | } catch (MissingLinkException e) { 150 | throw e; 151 | } catch (Exception e) { 152 | throw new MissingLinkException("Could not load " + entry.getName() + " from " + path, e); 153 | } 154 | } 155 | return artifact(artifactName, classes); 156 | } catch (IOException e) { 157 | throw new RuntimeException("Could not load " + path, e); 158 | } 159 | } 160 | 161 | private Artifact loadFromDirectory(ArtifactName artifactName, File dir) throws IOException { 162 | Map classes = new HashMap<>(); 163 | 164 | List classFilesInDir = 165 | Files.walk(dir.toPath()) 166 | .map(Path::toFile) 167 | .filter(file -> file.isFile() && file.getName().endsWith(".class")) 168 | .collect(toList()); 169 | 170 | for (File file : classFilesInDir) { 171 | try (FileInputStream fis = new FileInputStream(file)) { 172 | DeclaredClass cl = ClassLoader.load(fis); 173 | classes.put(cl.className(), cl); 174 | } 175 | } 176 | return artifact(artifactName, classes); 177 | } 178 | 179 | private static Artifact artifact( 180 | ArtifactName name, Map classes) { 181 | return new ArtifactBuilder().name(name).classes(classes).build(); 182 | } 183 | 184 | public static void main(String[] args) throws Exception { 185 | ArtifactLoader l = new ArtifactLoader(); 186 | System.out.println(l.load(new File("core/src/test/resources/ArtifactLoaderTest.jar"))); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/CheckerState.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import com.spotify.missinglink.datamodel.ArtifactName; 39 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 40 | import com.spotify.missinglink.datamodel.DeclaredClass; 41 | import io.norberg.automatter.AutoMatter; 42 | import java.util.Map; 43 | 44 | @AutoMatter 45 | interface CheckerState { 46 | Map sourceMappings(); 47 | 48 | Map knownClasses(); 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/Conflict.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import com.spotify.missinglink.datamodel.ArtifactName; 39 | import com.spotify.missinglink.datamodel.Dependency; 40 | import io.norberg.automatter.AutoMatter; 41 | 42 | @AutoMatter 43 | public interface Conflict { 44 | Dependency dependency(); 45 | 46 | ArtifactName existsIn(); 47 | 48 | ArtifactName usedBy(); 49 | 50 | ConflictCategory category(); 51 | 52 | String reason(); 53 | 54 | enum ConflictCategory { 55 | CLASS_NOT_FOUND, 56 | METHOD_SIGNATURE_NOT_FOUND, 57 | FIELD_NOT_FOUND 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/Java9ModuleLoader.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2019 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import com.spotify.missinglink.datamodel.Artifact; 39 | import com.spotify.missinglink.datamodel.ArtifactBuilder; 40 | import com.spotify.missinglink.datamodel.ArtifactName; 41 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 42 | import com.spotify.missinglink.datamodel.DeclaredClass; 43 | import com.spotify.missinglink.datamodel.TypeDescriptors; 44 | import java.io.InputStream; 45 | import java.lang.reflect.InvocationTargetException; 46 | import java.util.ArrayList; 47 | import java.util.HashMap; 48 | import java.util.List; 49 | import java.util.Map; 50 | import java.util.Optional; 51 | import java.util.Set; 52 | import java.util.function.BiConsumer; 53 | import java.util.stream.Collectors; 54 | import java.util.stream.Stream; 55 | 56 | public class Java9ModuleLoader { 57 | 58 | public static List getJava9ModuleArtifacts(BiConsumer log) { 59 | List artifacts = new ArrayList<>(); 60 | try { 61 | 62 | final Class moduleFinderClass = Class.forName("java.lang.module.ModuleFinder"); 63 | final Object systemModuleFinder = moduleFinderClass.getMethod("ofSystem").invoke(null); 64 | final Set moduleReferences = 65 | (Set) moduleFinderClass.getMethod("findAll").invoke(systemModuleFinder); 66 | final Class moduleReferenceClass = Class.forName("java.lang.module.ModuleReference"); 67 | final Class moduleReaderClass = Class.forName("java.lang.module.ModuleReader"); 68 | for (final Object moduleReference : moduleReferences) { 69 | final Object descriptor = 70 | moduleReferenceClass.getMethod("descriptor").invoke(moduleReference); 71 | final String moduleName = 72 | String.valueOf(descriptor.getClass().getMethod("name").invoke(descriptor)); 73 | Object reader = moduleReferenceClass.getMethod("open").invoke(moduleReference); 74 | try { 75 | final ArtifactName name = new ArtifactName(moduleName); 76 | Map classes = new HashMap<>(); 77 | final List readerList = 78 | ((Stream) moduleReaderClass.getMethod("list").invoke(reader)) 79 | .filter(className -> className.endsWith(".class")) 80 | .collect(Collectors.toList()); 81 | 82 | for (String className : readerList) { 83 | final Optional opened = 84 | (Optional) 85 | moduleReaderClass.getMethod("open", String.class).invoke(reader, className); 86 | if (!opened.isPresent()) { 87 | continue; 88 | } 89 | try (InputStream inputStream = opened.get()) { 90 | DeclaredClass declaredClass = ClassLoader.load(inputStream); 91 | classes.put(TypeDescriptors.fromClassName(className), declaredClass); 92 | } catch (Exception e) { 93 | log.accept("Could not read class " + className, e); 94 | } 95 | } 96 | artifacts.add(new ArtifactBuilder().name(name).classes(classes).build()); 97 | } finally { 98 | try { 99 | moduleReaderClass.getMethod("close").invoke(reader); 100 | } catch (InvocationTargetException 101 | | NoSuchMethodException 102 | | SecurityException 103 | | IllegalAccessException e) { 104 | log.accept("Could not close reader", e); 105 | } 106 | } 107 | } 108 | } catch (InvocationTargetException 109 | | NoSuchMethodException 110 | | IllegalAccessException 111 | | ClassNotFoundException e) { 112 | log.accept("Could not read java 9 modules", e); 113 | } 114 | return artifacts; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/MissingLinkException.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | /** 39 | * Thrown in case of a problem running missinglink that should be reported to the caller, aborting 40 | * processing. 41 | */ 42 | public class MissingLinkException extends RuntimeException { 43 | 44 | public MissingLinkException(String message) { 45 | super(message); 46 | } 47 | 48 | public MissingLinkException(String message, Throwable cause) { 49 | super(message, cause); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/PrettyPrinter.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | public class PrettyPrinter { 39 | public static String className(String className) { 40 | return className.replace('/', '.'); 41 | } 42 | 43 | public static String typeDescriptor(String typeDescriptor) { 44 | int arrayLevels = 0; 45 | 46 | StringBuilder sb = new StringBuilder(); 47 | 48 | int i = 0; 49 | for (; i < typeDescriptor.length(); i++) { 50 | char c = typeDescriptor.charAt(i); 51 | switch (c) { 52 | case '[': 53 | arrayLevels++; 54 | break; 55 | case 'L': 56 | while (true) { 57 | i++; 58 | c = typeDescriptor.charAt(i); 59 | if (c == ';') { 60 | break; 61 | } 62 | if (c == '/') { 63 | c = '.'; 64 | } 65 | sb.append(c); 66 | } 67 | break; 68 | case 'V': 69 | sb.append("void"); 70 | break; 71 | case 'I': 72 | sb.append("int"); 73 | break; 74 | case 'S': 75 | sb.append("short"); 76 | break; 77 | case 'Z': 78 | sb.append("long"); 79 | break; 80 | case 'B': 81 | sb.append("boolean"); 82 | break; 83 | case 'D': 84 | sb.append("double"); 85 | break; 86 | case 'F': 87 | sb.append("float"); 88 | break; 89 | default: 90 | // TODO: remove this once we have fixed the semantics for classname vs type descriptor 91 | return className(typeDescriptor); 92 | // throw new RuntimeException("Unhandled type: " + typeDescriptor); 93 | } 94 | } 95 | for (int j = 0; j < arrayLevels; j++) { 96 | sb.append("[]"); 97 | } 98 | return sb.toString(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/AccessedField.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | import java.util.List; 40 | 41 | /** Information about a field accessed in a method */ 42 | @AutoMatter 43 | public interface AccessedField { 44 | 45 | ClassTypeDescriptor owner(); 46 | 47 | TypeDescriptor descriptor(); 48 | 49 | String name(); 50 | 51 | int lineNumber(); 52 | 53 | /** List of exception classes that protect this method call * */ 54 | List caughtExceptions(); 55 | 56 | default String pretty() { 57 | return descriptor().toString() + " " + owner().toString() + "." + name(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/ArrayTypeDescriptor.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import java.util.Objects; 39 | 40 | public class ArrayTypeDescriptor implements TypeDescriptor { 41 | 42 | private final TypeDescriptor subType; 43 | private final int dimensions; 44 | 45 | public ArrayTypeDescriptor(TypeDescriptor subType, int dimensions) { 46 | this.dimensions = dimensions; 47 | this.subType = Objects.requireNonNull(subType); 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | StringBuilder sb = new StringBuilder(); 53 | sb.append(subType.toString()); 54 | for (int i = 0; i < dimensions; i++) { 55 | sb.append("[]"); 56 | } 57 | return sb.toString(); 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) { 63 | return true; 64 | } 65 | if (o == null || getClass() != o.getClass()) { 66 | return false; 67 | } 68 | 69 | ArrayTypeDescriptor that = (ArrayTypeDescriptor) o; 70 | 71 | return subType.equals(that.subType) && dimensions == that.dimensions; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return subType.hashCode(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/Artifact.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | import java.util.Map; 40 | 41 | @AutoMatter 42 | public interface Artifact { 43 | 44 | /** "Name" of artifact, for example a directory path or jar file or something else symbolic. */ 45 | ArtifactName name(); 46 | 47 | /** Map of classname to class object. Names are com/foo/bar/Baz. Returned map is immutable. */ 48 | Map classes(); 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/ArtifactName.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | public class ArtifactName { 39 | private final String name; 40 | 41 | public ArtifactName(String name) { 42 | this.name = name; 43 | } 44 | 45 | public String name() { 46 | return name; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "ArtifactName:" + name; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) { 57 | return true; 58 | } 59 | if (o == null || getClass() != o.getClass()) { 60 | return false; 61 | } 62 | 63 | ArtifactName that = (ArtifactName) o; 64 | 65 | return name.equals(that.name); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return name.hashCode(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/CalledMethod.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | import java.util.List; 40 | 41 | /** Information about a method called by a DeclaredMethod */ 42 | @AutoMatter 43 | public interface CalledMethod { 44 | 45 | /** Owning class of method being called */ 46 | ClassTypeDescriptor owner(); 47 | 48 | MethodDescriptor descriptor(); 49 | 50 | boolean isStatic(); 51 | 52 | int lineNumber(); 53 | 54 | /** List of exception classes that protect this method call * */ 55 | List caughtExceptions(); 56 | 57 | default String pretty() { 58 | return owner().toString() + "." + descriptor().prettyWithoutReturnType(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/ClassTypeDescriptor.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import java.util.InputMismatchException; 39 | import java.util.Objects; 40 | 41 | public class ClassTypeDescriptor implements TypeDescriptor { 42 | 43 | private final String className; 44 | 45 | ClassTypeDescriptor(String className) { 46 | this.className = Objects.requireNonNull(className).replace('/', '.'); 47 | if (className.endsWith(";")) { 48 | throw new InputMismatchException( 49 | "Got a signature where a class name was expected: " + className); 50 | } 51 | } 52 | 53 | public String getClassName() { 54 | return className; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return className; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) { 65 | return true; 66 | } 67 | if (o == null || getClass() != o.getClass()) { 68 | return false; 69 | } 70 | 71 | ClassTypeDescriptor that = (ClassTypeDescriptor) o; 72 | 73 | if (!className.equals(that.className)) { 74 | return false; 75 | } 76 | 77 | return true; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return className.hashCode(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/DeclaredClass.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | import java.util.Map; 40 | import java.util.Set; 41 | 42 | @AutoMatter 43 | public interface DeclaredClass { 44 | 45 | // names are com/foo/bar/Baz 46 | ClassTypeDescriptor className(); 47 | 48 | // parent are class names: com/foo/bar/Baz 49 | Set parents(); 50 | // also includes other classes that are loaded by this class, even though 51 | // no methods on those classes are explicitly called 52 | Set loadedClasses(); 53 | 54 | Map methods(); 55 | 56 | Set fields(); 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/DeclaredField.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | 40 | @AutoMatter 41 | /** Represents field in a class */ 42 | public interface DeclaredField { 43 | String name(); 44 | 45 | TypeDescriptor descriptor(); 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/DeclaredMethod.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | import java.util.Set; 40 | 41 | @AutoMatter 42 | // TODO: rename to something better - ImplementedMethod ? DefinedMethod ? 43 | /** Represents methods in a class */ 44 | public interface DeclaredMethod { 45 | MethodDescriptor descriptor(); 46 | 47 | boolean isStatic(); 48 | 49 | int lineNumber(); 50 | 51 | /** Calls that this method makes to other methods */ 52 | Set methodCalls(); 53 | 54 | Set fieldAccesses(); 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/Dependency.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | /** 39 | * Represents a dependency between a method and the method that it calls, used in Conflict when 40 | * reporting problems 41 | */ 42 | public interface Dependency { 43 | 44 | ClassTypeDescriptor fromClass(); 45 | 46 | MethodDescriptor fromMethod(); 47 | 48 | int fromLineNumber(); 49 | 50 | ClassTypeDescriptor targetClass(); 51 | 52 | String describe(); 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/FieldDependency.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | 40 | /** 41 | * Represents a dependency between a method and the method that it calls, used in Conflict when 42 | * reporting problems 43 | */ 44 | @AutoMatter 45 | public interface FieldDependency extends Dependency { 46 | 47 | ClassTypeDescriptor fromClass(); 48 | 49 | MethodDescriptor fromMethod(); 50 | 51 | int fromLineNumber(); 52 | 53 | ClassTypeDescriptor targetClass(); 54 | 55 | TypeDescriptor fieldType(); 56 | 57 | String fieldName(); 58 | 59 | @Override 60 | default String describe() { 61 | return "Access to: " + targetClass().toString() + "." + fieldName(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/MethodDependency.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | 40 | /** 41 | * Represents a dependency between a method and the method that it calls, used in Conflict when 42 | * reporting problems 43 | */ 44 | @AutoMatter 45 | public interface MethodDependency extends Dependency { 46 | 47 | ClassTypeDescriptor fromClass(); 48 | 49 | MethodDescriptor fromMethod(); 50 | 51 | int fromLineNumber(); 52 | 53 | ClassTypeDescriptor targetClass(); 54 | 55 | MethodDescriptor targetMethod(); 56 | 57 | @Override 58 | default String describe() { 59 | return "Call to: " + targetClass().toString() + "." + targetMethod().prettyWithoutReturnType(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/MethodDescriptor.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import io.norberg.automatter.AutoMatter; 39 | import java.util.List; 40 | import java.util.StringJoiner; 41 | 42 | @AutoMatter 43 | public interface MethodDescriptor { 44 | 45 | TypeDescriptor returnType(); 46 | 47 | String name(); 48 | 49 | List parameterTypes(); 50 | 51 | // TODO: add modifiers 52 | 53 | default String pretty() { 54 | return returnType().toString() + " " + name() + prettyParameters(); 55 | } 56 | 57 | default String prettyWithoutReturnType() { 58 | return name() + prettyParameters(); 59 | } 60 | 61 | default String prettyParameters() { 62 | StringJoiner joiner = new StringJoiner(", ", "(", ")"); 63 | 64 | parameterTypes().stream().map(TypeDescriptor::toString).forEach(joiner::add); 65 | 66 | return joiner.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/MethodDescriptors.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import static java.util.stream.Collectors.toList; 39 | 40 | import java.util.Arrays; 41 | import java.util.List; 42 | import java.util.Map; 43 | import java.util.Objects; 44 | import java.util.concurrent.ConcurrentHashMap; 45 | import org.objectweb.asm.Type; 46 | 47 | public final class MethodDescriptors { 48 | 49 | private MethodDescriptors() {} 50 | 51 | public static MethodDescriptor fromDesc(String desc, String name) { 52 | final MethodKey key = new MethodKey(name, desc); 53 | return methodDescriptorCache.computeIfAbsent(key, MethodDescriptors::newDescriptor); 54 | } 55 | 56 | private static MethodDescriptor newDescriptor(MethodKey key) { 57 | Type type = Type.getMethodType(key.desc); 58 | 59 | List params = 60 | Arrays.stream(type.getArgumentTypes()) 61 | .map(Type::getDescriptor) 62 | .map(TypeDescriptors::fromRaw) 63 | .collect(toList()); 64 | 65 | return new MethodDescriptorBuilder() 66 | .returnType(TypeDescriptors.fromRaw(type.getReturnType().getDescriptor())) 67 | .name(key.name) 68 | .parameterTypes(params) 69 | .build(); 70 | } 71 | 72 | private static final Map methodDescriptorCache = 73 | new ConcurrentHashMap<>(); 74 | 75 | private static class MethodKey { 76 | private final String name; 77 | private final String desc; 78 | 79 | MethodKey(String name, String desc) { 80 | this.name = Objects.requireNonNull(name); 81 | this.desc = Objects.requireNonNull(desc); 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | if (this == o) { 87 | return true; 88 | } 89 | if (o == null || getClass() != o.getClass()) { 90 | return false; 91 | } 92 | 93 | MethodKey key = (MethodKey) o; 94 | 95 | if (!name.equals(key.name)) { 96 | return false; 97 | } 98 | return desc.equals(key.desc); 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | int result = name.hashCode(); 104 | result = 31 * result + desc.hashCode(); 105 | return result; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/PrimitiveTypeDescriptor.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import java.util.Collections; 39 | import java.util.HashMap; 40 | import java.util.Map; 41 | 42 | public enum PrimitiveTypeDescriptor implements TypeDescriptor { 43 | BYTE('B', "byte"), 44 | SHORT('S', "short"), 45 | INT('I', "int"), 46 | LONG('J', "long"), 47 | FLOAT('F', "float"), 48 | DOUBLE('D', "double"), 49 | BOOLEAN('Z', "boolean"), 50 | CHAR('C', "char"); 51 | 52 | private final char raw; 53 | private final String pretty; 54 | 55 | PrimitiveTypeDescriptor(char raw, String pretty) { 56 | this.raw = raw; 57 | this.pretty = pretty; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return pretty; 63 | } 64 | 65 | public String getRaw() { 66 | return Character.toString(raw); 67 | } 68 | 69 | private static final Map mapping = createMapping(); 70 | 71 | private static Map createMapping() { 72 | final Map map = new HashMap<>(); 73 | for (PrimitiveTypeDescriptor type : PrimitiveTypeDescriptor.values()) { 74 | map.put(Character.toString(type.raw), type); 75 | } 76 | return Collections.unmodifiableMap(map); 77 | } 78 | 79 | public static PrimitiveTypeDescriptor fromRaw(String typeDescriptor) { 80 | final PrimitiveTypeDescriptor descriptor = mapping.get(typeDescriptor); 81 | if (descriptor == null) { 82 | return null; 83 | } 84 | return descriptor; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/TypeDescriptor.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | public interface TypeDescriptor {} 39 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/TypeDescriptors.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import java.util.InputMismatchException; 39 | import java.util.Map; 40 | import java.util.concurrent.ConcurrentHashMap; 41 | 42 | public class TypeDescriptors { 43 | 44 | private TypeDescriptors() {} 45 | 46 | // Keep a cache of all the created ClassTypeDescriptor instances to prevent unnecessary 47 | // duplication of instances. ClassTypeDescriptor is immutable once constructed. 48 | // Since the CTD constructor involves string replacement, and there are several places where we 49 | // want to turn a String into a ClassTypeDescriptor, we can optimize how many strings are 50 | // created/replaced with this map. 51 | private static Map classTypeDescriptorCache = 52 | new ConcurrentHashMap<>(); 53 | 54 | public static ClassTypeDescriptor fromClassName(String className) { 55 | return classTypeDescriptorCache.computeIfAbsent(className, ClassTypeDescriptor::new); 56 | } 57 | 58 | public static TypeDescriptor fromRaw(String raw) { 59 | final int length = raw.length(); 60 | 61 | int dimensions = raw.lastIndexOf('[') + 1; 62 | 63 | final String subType = raw.substring(dimensions); 64 | 65 | final TypeDescriptor simpleType; 66 | if (subType.equals("V")) { 67 | simpleType = VoidTypeDescriptor.voidTypeDescriptor; 68 | } else if (subType.startsWith("L") && subType.endsWith(";")) { 69 | simpleType = fromClassName(subType.substring(1, length - dimensions - 1)); 70 | } else { 71 | simpleType = PrimitiveTypeDescriptor.fromRaw(subType); 72 | if (simpleType == null) { 73 | throw new InputMismatchException("Invalid type descriptor: " + raw); 74 | } 75 | } 76 | 77 | if (dimensions > 0) { 78 | return new ArrayTypeDescriptor(simpleType, dimensions); 79 | } 80 | return simpleType; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /core/src/main/java/com/spotify/missinglink/datamodel/VoidTypeDescriptor.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | public class VoidTypeDescriptor implements TypeDescriptor { 39 | 40 | public static final VoidTypeDescriptor voidTypeDescriptor = new VoidTypeDescriptor(); 41 | 42 | private VoidTypeDescriptor() {} 43 | 44 | @Override 45 | public String toString() { 46 | return "void"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/ClassLoaderTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import static com.spotify.missinglink.ClassLoadingUtil.findClass; 39 | import static org.assertj.core.api.Assertions.assertThat; 40 | 41 | import com.spotify.missinglink.datamodel.DeclaredClass; 42 | import com.spotify.missinglink.datamodel.TypeDescriptors; 43 | import java.io.File; 44 | import java.io.FileInputStream; 45 | import java.nio.file.Files; 46 | import java.nio.file.Path; 47 | import org.junit.After; 48 | import org.junit.Before; 49 | import org.junit.Test; 50 | 51 | public class ClassLoaderTest { 52 | 53 | private FileInputStream inputStream; 54 | 55 | @Before 56 | public void setUp() throws Exception { 57 | final File outputDir = FilePathHelper.getPath("target/classes"); 58 | File someClass = 59 | Files.walk(outputDir.toPath()) 60 | .map(Path::toFile) 61 | .filter(file -> file.isFile() && file.getName().endsWith(".class")) 62 | .findFirst() 63 | .orElseThrow(() -> new IllegalStateException("no classfiles in " + outputDir + " ?")); 64 | 65 | inputStream = new FileInputStream(someClass); 66 | } 67 | 68 | @After 69 | public void tearDown() throws Exception { 70 | inputStream.close(); 71 | } 72 | 73 | /** Simple test that load() doesn't blow up */ 74 | @Test 75 | public void testLoad() throws Exception { 76 | final DeclaredClass declaredClass = ClassLoader.load(inputStream); 77 | assertThat(declaredClass).isNotNull(); 78 | assertThat(declaredClass.methods()).isNotEmpty(); 79 | } 80 | 81 | @Test 82 | public void shouldHandleLoadingOfType() throws Exception { 83 | try (FileInputStream inputStream = findClass(LdcLoadType.class)) { 84 | DeclaredClass loaded = ClassLoader.load(inputStream); 85 | 86 | assertThat(loaded.className().getClassName()).contains("LdcLoadType"); 87 | } 88 | } 89 | 90 | @Test 91 | public void shouldHandleLoadingOfArrayOfType() throws Exception { 92 | try (FileInputStream inputStream = findClass(LdcLoadArrayOfType.class)) { 93 | DeclaredClass loaded = ClassLoader.load(inputStream); 94 | 95 | assertThat(loaded.className().getClassName()).contains("LdcLoadArrayOfType"); 96 | assertThat(loaded.loadedClasses()) 97 | .containsExactly(TypeDescriptors.fromClassName(Object.class.getName())); 98 | } 99 | } 100 | 101 | @Test 102 | public void shouldHandleLoadingOfArrayOfPrimitive() throws Exception { 103 | try (FileInputStream inputStream = findClass(LdcLoadArrayOfPrimitive.class)) { 104 | DeclaredClass loaded = ClassLoader.load(inputStream); 105 | 106 | assertThat(loaded.className().getClassName()).contains("LdcLoadArrayOfPrimitive"); 107 | assertThat(loaded.loadedClasses()).isEmpty(); 108 | } 109 | } 110 | 111 | static class LdcLoadType { 112 | static void test() { 113 | System.out.println(FileInputStream.class.toString()); 114 | } 115 | } 116 | 117 | static class LdcLoadArrayOfType { 118 | static void test() { 119 | System.out.println(Object[].class); 120 | } 121 | } 122 | 123 | static class LdcLoadArrayOfPrimitive { 124 | static void test() { 125 | System.out.println(long[].class); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/ClassLoadingUtil.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import com.spotify.missinglink.datamodel.Artifact; 39 | import java.io.File; 40 | import java.io.FileInputStream; 41 | import java.io.IOException; 42 | import java.nio.file.Files; 43 | import java.nio.file.Path; 44 | import java.util.Arrays; 45 | import java.util.List; 46 | import java.util.concurrent.atomic.AtomicReference; 47 | import java.util.stream.Collectors; 48 | import java.util.stream.StreamSupport; 49 | 50 | public class ClassLoadingUtil { 51 | 52 | private static final ArtifactLoader artifactLoader = new ArtifactLoader(); 53 | private static final AtomicReference> bootstrapArtifacts = new AtomicReference<>(); 54 | 55 | public static FileInputStream findClass(Class aClass) throws Exception { 56 | final String name = aClass.getName().replace('.', '/') + ".class"; 57 | final File outputDir = FilePathHelper.getPath("target/test-classes"); 58 | List files = 59 | Files.walk(outputDir.toPath()) 60 | .map(Path::toFile) 61 | .filter(file -> file.isFile() && file.getAbsolutePath().endsWith(name)) 62 | .collect(Collectors.toList()); 63 | if (files.isEmpty()) { 64 | throw new IllegalStateException( 65 | "no file matching " + aClass + " found in " + outputDir + " ?"); 66 | } 67 | if (files.size() >= 2) { 68 | throw new IllegalStateException( 69 | "too many files matching " + aClass + " found in " + outputDir + ": " + files); 70 | } 71 | return new FileInputStream(files.get(0)); 72 | } 73 | 74 | public static List bootstrapArtifacts() { 75 | if (bootstrapArtifacts.get() == null) { 76 | 77 | String bootstrapClasspath = System.getProperty("sun.boot.class.path"); 78 | 79 | if (bootstrapClasspath != null) { 80 | List artifacts = 81 | constructArtifacts( 82 | Arrays.asList(bootstrapClasspath.split(System.getProperty("path.separator")))) 83 | .stream() 84 | .filter(c -> !c.name().name().equals("test-classes")) 85 | .collect(Collectors.toList()); 86 | bootstrapArtifacts.set(artifacts); 87 | } else { 88 | List artifacts = 89 | Java9ModuleLoader.getJava9ModuleArtifacts((s, ex) -> ex.printStackTrace()); 90 | bootstrapArtifacts.set(artifacts); 91 | } 92 | } 93 | return bootstrapArtifacts.get(); 94 | } 95 | 96 | private static List constructArtifacts(Iterable entries) { 97 | final List list = 98 | StreamSupport.stream(entries.spliterator(), false) 99 | // don't inspect paths that don't exist. 100 | // some bootclasspath entries, like sunrsasign.jar, are reported even if they 101 | // don't exist on disk - ¯\_(ツ)_/¯ 102 | .distinct() 103 | .filter(ClassLoadingUtil::filterValidClasspathEntries) 104 | .map(ClassLoadingUtil::filepathToArtifact) 105 | .collect(Collectors.toList()); 106 | return list; 107 | } 108 | 109 | private static boolean filterValidClasspathEntries(String element) { 110 | return filterValid(new File(element)); 111 | } 112 | 113 | private static boolean filterValid(File file) { 114 | if (file == null) { 115 | return false; 116 | } 117 | final boolean isJarFile = file.isFile() && file.getName().endsWith(".jar"); 118 | final boolean isClassDirectory = file.isDirectory(); 119 | return isClassDirectory || isJarFile; 120 | } 121 | 122 | private static Artifact filepathToArtifact(String path) { 123 | try { 124 | return artifactLoader.load(new File(path)); 125 | } catch (IOException e) { 126 | throw new MissingLinkException("failed to load path: " + path, e); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/ConflictCheckerTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import static org.assertj.core.api.Assertions.assertThat; 39 | 40 | import com.spotify.missinglink.datamodel.Artifact; 41 | import com.spotify.missinglink.datamodel.ArtifactBuilder; 42 | import com.spotify.missinglink.datamodel.ArtifactName; 43 | import com.spotify.missinglink.datamodel.CalledMethodBuilder; 44 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 45 | import com.spotify.missinglink.datamodel.DeclaredClassBuilder; 46 | import com.spotify.missinglink.datamodel.DeclaredMethodBuilder; 47 | import com.spotify.missinglink.datamodel.MethodDescriptor; 48 | import com.spotify.missinglink.datamodel.MethodDescriptorBuilder; 49 | import com.spotify.missinglink.datamodel.TypeDescriptors; 50 | import java.util.Arrays; 51 | import java.util.Collections; 52 | import java.util.List; 53 | import org.junit.Before; 54 | import org.junit.Test; 55 | 56 | public class ConflictCheckerTest { 57 | 58 | private Artifact projectArtifact; 59 | private Artifact rt; 60 | private Artifact libraryArtifact; 61 | 62 | @Before 63 | public void setUp() throws Exception { 64 | final MethodDescriptor cloneDescriptor = 65 | new MethodDescriptorBuilder() 66 | .name("clone") 67 | .returnType(TypeDescriptors.fromClassName("java/lang/Object")) 68 | .build(); 69 | 70 | rt = 71 | new ArtifactBuilder() 72 | .name(new ArtifactName("rt")) 73 | .classes( 74 | Collections.singletonMap( 75 | TypeDescriptors.fromClassName("java/lang/Object"), 76 | new DeclaredClassBuilder() 77 | .className(TypeDescriptors.fromClassName("java/lang/Object")) 78 | .methods( 79 | Collections.singletonMap( 80 | cloneDescriptor, 81 | new DeclaredMethodBuilder().descriptor(cloneDescriptor).build())) 82 | // TODO: maybe add more methods here 83 | .build())) 84 | .build(); 85 | 86 | projectArtifact = 87 | new ArtifactBuilder() 88 | .name(new ArtifactName("foo")) 89 | .classes( 90 | Collections.singletonMap( 91 | TypeDescriptors.fromClassName("com/spotify/ClassName"), 92 | Simple.newClass("com/spotify/ClassName") 93 | .parents( 94 | Collections.singleton( 95 | TypeDescriptors.fromClassName("java/lang/Object"))) 96 | .methods( 97 | Simple.methodMap( 98 | Simple.newMethod(false, Simple.OBJECT, "something") 99 | .methodCalls( 100 | Collections.singleton( 101 | new CalledMethodBuilder() 102 | .owner( 103 | TypeDescriptors.fromClassName( 104 | "java/lang/Object")) 105 | .descriptor(cloneDescriptor) 106 | .build())) 107 | .build())) 108 | .build())) 109 | .build(); 110 | 111 | ClassTypeDescriptor libClass1 = TypeDescriptors.fromClassName("org/library/ClassName"); 112 | MethodDescriptor brokenMethodDescriptor = 113 | new MethodDescriptorBuilder() 114 | .name("broken") 115 | .returnType(TypeDescriptors.fromClassName("java/lang/Object")) 116 | .build(); 117 | 118 | libraryArtifact = 119 | new ArtifactBuilder() 120 | .name(new ArtifactName("lib")) 121 | .classes( 122 | Collections.singletonMap( 123 | libClass1, 124 | new DeclaredClassBuilder() 125 | .className(libClass1) 126 | .parents( 127 | Collections.singleton( 128 | TypeDescriptors.fromClassName("java/lang/Object"))) 129 | .methods( 130 | Simple.methodMap( 131 | Simple.newMethod(false, Simple.OBJECT, "broken") 132 | .methodCalls( 133 | Collections.singleton( 134 | new CalledMethodBuilder() 135 | .owner( 136 | TypeDescriptors.fromClassName( 137 | "java/lang/Object")) 138 | .descriptor(brokenMethodDescriptor) 139 | .build())) 140 | .build())) 141 | .build())) 142 | .build(); 143 | } 144 | 145 | @Test 146 | public void shouldSupportInvocationsOnArrayTypes() throws Exception { 147 | ConflictChecker checker = new ConflictChecker(); 148 | 149 | final List conflicts = 150 | checker.check( 151 | projectArtifact, 152 | Arrays.asList(projectArtifact, rt), 153 | Arrays.asList(projectArtifact, rt)); 154 | assertThat(conflicts).isEmpty(); 155 | } 156 | 157 | @Test 158 | public void shouldNotReportUnreachableClassConflicts() throws Exception { 159 | List artifacts = Arrays.asList(projectArtifact, libraryArtifact, rt); 160 | 161 | ConflictChecker checker = new ConflictChecker(); 162 | 163 | assertThat(checker.check(projectArtifact, artifacts, artifacts)).isEmpty(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/FilePathHelper.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import java.io.File; 39 | import java.util.Arrays; 40 | 41 | /** 42 | * helper method for loading a file path relative to this project in a way that works in both 43 | * IntelliJ and with maven from the CLI. 44 | */ 45 | class FilePathHelper { 46 | 47 | static File getPath(String pathRelativeToProject) { 48 | final File baseDir; 49 | final File currentDir = new File("."); 50 | if (Arrays.asList(currentDir.list()).stream().anyMatch(x -> x.equals("core"))) { 51 | baseDir = new File(currentDir, "core"); 52 | } else { 53 | baseDir = currentDir; 54 | } 55 | 56 | return new File(baseDir, pathRelativeToProject); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/PrettyPrinterTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import static org.junit.Assert.assertEquals; 39 | 40 | import org.junit.Test; 41 | 42 | public class PrettyPrinterTest { 43 | 44 | @Test 45 | public void testPrimitives() throws Exception { 46 | assertEquals("int", PrettyPrinter.typeDescriptor("I")); 47 | assertEquals("short", PrettyPrinter.typeDescriptor("S")); 48 | assertEquals("long", PrettyPrinter.typeDescriptor("Z")); 49 | assertEquals("double", PrettyPrinter.typeDescriptor("D")); 50 | assertEquals("float", PrettyPrinter.typeDescriptor("F")); 51 | assertEquals("boolean", PrettyPrinter.typeDescriptor("B")); 52 | assertEquals("void", PrettyPrinter.typeDescriptor("V")); 53 | } 54 | 55 | @Test 56 | public void testPrimitiveArrays() throws Exception { 57 | assertEquals("int[]", PrettyPrinter.typeDescriptor("[I")); 58 | assertEquals("short[]", PrettyPrinter.typeDescriptor("[S")); 59 | } 60 | 61 | @Test 62 | public void testClasses() throws Exception { 63 | assertEquals("com.spotify.Foo", PrettyPrinter.typeDescriptor("Lcom/spotify/Foo;")); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/ReachableTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import static com.spotify.missinglink.Simple.INT; 39 | import static com.spotify.missinglink.Simple.VOID; 40 | import static com.spotify.missinglink.Simple.classMap; 41 | import static com.spotify.missinglink.Simple.methodMap; 42 | import static com.spotify.missinglink.Simple.newCall; 43 | import static com.spotify.missinglink.Simple.newClass; 44 | import static com.spotify.missinglink.Simple.newField; 45 | import static com.spotify.missinglink.Simple.newMethod; 46 | import static org.junit.Assert.assertEquals; 47 | 48 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 49 | import com.spotify.missinglink.datamodel.DeclaredClass; 50 | import com.spotify.missinglink.datamodel.DeclaredMethod; 51 | import com.spotify.missinglink.datamodel.MethodDescriptor; 52 | import com.spotify.missinglink.datamodel.TypeDescriptor; 53 | import com.spotify.missinglink.datamodel.TypeDescriptors; 54 | import java.util.Arrays; 55 | import java.util.Collections; 56 | import java.util.HashSet; 57 | import java.util.Map; 58 | import java.util.Set; 59 | import org.junit.Test; 60 | 61 | public class ReachableTest { 62 | 63 | @Test 64 | public void testUnreachable() { 65 | DeclaredClass root = newClass("my/Root").build(); 66 | Set myClasses = Collections.singleton(root); 67 | Map world = 68 | classMap(root, newClass("other/Unknown").build()); 69 | Set reachable = ConflictChecker.reachableFrom(myClasses, world); 70 | assertEquals(Collections.singleton(root.className()), reachable); 71 | } 72 | 73 | @Test 74 | public void testReachableViaCall() { 75 | DeclaredMethod remoteMethod = newMethod(false, VOID, "called").build(); 76 | DeclaredClass remote = newClass("other/Unknown").methods(methodMap(remoteMethod)).build(); 77 | DeclaredClass root = 78 | newClass("my/Root") 79 | .methods( 80 | methodMap( 81 | newMethod(false, VOID, "foo") 82 | .methodCalls( 83 | Collections.singleton(newCall(remote, remoteMethod, false, true))) 84 | .build())) 85 | .build(); 86 | Set myClasses = Collections.singleton(root); 87 | Map world = classMap(root, remote); 88 | Set reachable = ConflictChecker.reachableFrom(myClasses, world); 89 | Set expected = 90 | new HashSet<>(Arrays.asList(root.className(), remote.className())); 91 | assertEquals(expected, reachable); 92 | } 93 | 94 | @Test 95 | public void testReachableViaInheritance() { 96 | DeclaredClass remote = newClass("other/Unknown").build(); 97 | DeclaredClass root = 98 | newClass("my/Root") 99 | .parents(Collections.singleton(TypeDescriptors.fromClassName("other/Unknown"))) 100 | .build(); 101 | Set myClasses = Collections.singleton(root); 102 | Map world = classMap(root, remote); 103 | Set reachable = ConflictChecker.reachableFrom(myClasses, world); 104 | Set expected = 105 | new HashSet<>(Arrays.asList(root.className(), remote.className())); 106 | assertEquals(expected, reachable); 107 | } 108 | 109 | @Test 110 | public void testReachableViaLdcLoad() { 111 | DeclaredClass remote = newClass("other/Unknown").build(); 112 | DeclaredClass root = 113 | newClass("my/Root") 114 | .loadedClasses(Collections.singleton(TypeDescriptors.fromClassName("other/Unknown"))) 115 | .build(); 116 | Set myClasses = Collections.singleton(root); 117 | Map world = classMap(root, remote); 118 | Set reachable = ConflictChecker.reachableFrom(myClasses, world); 119 | Set expected = 120 | new HashSet<>(Arrays.asList(root.className(), remote.className())); 121 | assertEquals(expected, reachable); 122 | } 123 | 124 | @Test 125 | public void testReachableViaField() { 126 | DeclaredClass remote = 127 | newClass("other/Unknown") 128 | .fields(Collections.singleton(newField(INT, "remoteField"))) 129 | .build(); 130 | Map methods = 131 | methodMap( 132 | newMethod(false, VOID, "foo") 133 | .fieldAccesses( 134 | Collections.singleton( 135 | Simple.newAccess(INT, "remoteField", "other/Unknown", 12))) 136 | .build()); 137 | DeclaredClass root = newClass("my/Root").methods(methods).build(); 138 | Set myClasses = Collections.singleton(root); 139 | Map world = classMap(root, remote); 140 | Set reachable = ConflictChecker.reachableFrom(myClasses, world); 141 | Set expected = 142 | new HashSet<>(Arrays.asList(root.className(), remote.className())); 143 | assertEquals(expected, reachable); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/Simple.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import com.spotify.missinglink.datamodel.AccessedField; 39 | import com.spotify.missinglink.datamodel.AccessedFieldBuilder; 40 | import com.spotify.missinglink.datamodel.Artifact; 41 | import com.spotify.missinglink.datamodel.ArtifactBuilder; 42 | import com.spotify.missinglink.datamodel.ArtifactName; 43 | import com.spotify.missinglink.datamodel.CalledMethod; 44 | import com.spotify.missinglink.datamodel.CalledMethodBuilder; 45 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 46 | import com.spotify.missinglink.datamodel.DeclaredClass; 47 | import com.spotify.missinglink.datamodel.DeclaredClassBuilder; 48 | import com.spotify.missinglink.datamodel.DeclaredField; 49 | import com.spotify.missinglink.datamodel.DeclaredFieldBuilder; 50 | import com.spotify.missinglink.datamodel.DeclaredMethod; 51 | import com.spotify.missinglink.datamodel.DeclaredMethodBuilder; 52 | import com.spotify.missinglink.datamodel.MethodDescriptor; 53 | import com.spotify.missinglink.datamodel.MethodDescriptorBuilder; 54 | import com.spotify.missinglink.datamodel.TypeDescriptor; 55 | import com.spotify.missinglink.datamodel.TypeDescriptors; 56 | import java.util.Arrays; 57 | import java.util.HashMap; 58 | import java.util.List; 59 | import java.util.Map; 60 | import java.util.stream.Collectors; 61 | 62 | /** Created by axel on 04/06/15. */ 63 | public class Simple { 64 | 65 | public static final String INT = "I"; 66 | public static final String VOID = "V"; 67 | public static final String STRING = "Ljava/lang/String;"; 68 | public static final String OBJECT = "Ljava/lang/Object;"; 69 | 70 | public static String array(String of) { 71 | return "[" + of; 72 | } 73 | 74 | /** 75 | * Empty new DeclaredClass, all collections filled in with empty values 76 | * 77 | * @param className 78 | * @return 79 | */ 80 | public static DeclaredClassBuilder newClass(String className) { 81 | return new DeclaredClassBuilder().className(TypeDescriptors.fromClassName(className)); 82 | } 83 | 84 | public static DeclaredMethodBuilder newMethod( 85 | boolean isStatic, String returnDesc, String name, String... parameterDesc) { 86 | List param = 87 | Arrays.stream(parameterDesc).map(TypeDescriptors::fromRaw).collect(Collectors.toList()); 88 | 89 | return new DeclaredMethodBuilder() 90 | .isStatic(isStatic) 91 | .descriptor( 92 | new MethodDescriptorBuilder() 93 | .name(name) 94 | .parameterTypes(param) 95 | .returnType(TypeDescriptors.fromRaw(returnDesc)) 96 | .build()); 97 | } 98 | 99 | public static CalledMethod newCall( 100 | DeclaredClass owner, DeclaredMethod method, boolean isStatic, boolean isVirtual) { 101 | return newCall(owner.className(), method, isStatic); 102 | } 103 | 104 | public static CalledMethod newCall( 105 | ClassTypeDescriptor owner, DeclaredMethod method, boolean isStatic) { 106 | return new CalledMethodBuilder() 107 | .owner(owner) 108 | .descriptor(method.descriptor()) 109 | .isStatic(isStatic) 110 | .build(); 111 | } 112 | 113 | public static Map classMap(DeclaredClass... classes) { 114 | Map map = new HashMap<>(); 115 | for (DeclaredClass clazz : classes) { 116 | map.put(clazz.className(), clazz); 117 | } 118 | return map; 119 | } 120 | 121 | public static Map methodMap(DeclaredMethod... methods) { 122 | Map map = new HashMap<>(); 123 | for (DeclaredMethod method : methods) { 124 | map.put(method.descriptor(), method); 125 | } 126 | return map; 127 | } 128 | 129 | public static DeclaredField newField(String desc, String name) { 130 | return new DeclaredFieldBuilder().name(name).descriptor(TypeDescriptors.fromRaw(desc)).build(); 131 | } 132 | 133 | public static AccessedField newAccess(String desc, String name, String owner, int lineNumber) { 134 | return new AccessedFieldBuilder() 135 | .name(name) 136 | .descriptor(TypeDescriptors.fromRaw(desc)) 137 | .owner(TypeDescriptors.fromClassName(owner)) 138 | .lineNumber(lineNumber) 139 | .build(); 140 | } 141 | 142 | public static Artifact newArtifact(String name, DeclaredClass... classes) { 143 | final Map map = new HashMap<>(); 144 | for (DeclaredClass clazz : classes) { 145 | map.put(clazz.className(), clazz); 146 | } 147 | 148 | return new ArtifactBuilder().name(new ArtifactName(name)).classes(map).build(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/TypeDescriptorTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink; 37 | 38 | import static org.junit.Assert.assertEquals; 39 | import static org.junit.Assert.assertNotEquals; 40 | 41 | import com.spotify.missinglink.datamodel.ArrayTypeDescriptor; 42 | import com.spotify.missinglink.datamodel.ClassTypeDescriptor; 43 | import com.spotify.missinglink.datamodel.PrimitiveTypeDescriptor; 44 | import com.spotify.missinglink.datamodel.TypeDescriptor; 45 | import com.spotify.missinglink.datamodel.TypeDescriptors; 46 | import java.util.HashMap; 47 | import java.util.InputMismatchException; 48 | import java.util.Map; 49 | import org.junit.Test; 50 | 51 | public class TypeDescriptorTest { 52 | 53 | @Test 54 | public void testEquality() { 55 | String[] signatures = { 56 | "B", 57 | "S", 58 | "I", 59 | "J", 60 | "F", 61 | "D", 62 | "Z", 63 | "C", 64 | "[D", 65 | "[[D", 66 | "[[[D", 67 | "LFoo;", 68 | "LBar;", 69 | "[LFoo;", 70 | "Lfoo/bar/Baz;" 71 | }; 72 | for (String signature1 : signatures) { 73 | for (String signature2 : signatures) { 74 | if (signature1.equals(signature2)) { 75 | assertEquals(TypeDescriptors.fromRaw(signature1), TypeDescriptors.fromRaw(signature2)); 76 | assertEquals( 77 | TypeDescriptors.fromRaw(signature1).hashCode(), 78 | TypeDescriptors.fromRaw(signature2).hashCode()); 79 | } else { 80 | assertNotEquals(TypeDescriptors.fromRaw(signature1), TypeDescriptors.fromRaw(signature2)); 81 | } 82 | } 83 | } 84 | } 85 | 86 | @Test 87 | public void testDescriptions() { 88 | Map desc = new HashMap<>(); 89 | desc.put("B", "byte"); 90 | desc.put("S", "short"); 91 | desc.put("I", "int"); 92 | desc.put("J", "long"); 93 | desc.put("F", "float"); 94 | desc.put("D", "double"); 95 | desc.put("Z", "boolean"); 96 | desc.put("C", "char"); 97 | desc.put("[D", "double[]"); 98 | desc.put("[[D", "double[][]"); 99 | desc.put("[[[D", "double[][][]"); 100 | desc.put("LFoo;", "Foo"); 101 | desc.put("[LFoo;", "Foo[]"); 102 | desc.put("[[LFoo;", "Foo[][]"); 103 | desc.put("Lfoo/bar/Baz;", "foo.bar.Baz"); 104 | 105 | for (Map.Entry entry : desc.entrySet()) { 106 | assertEquals(entry.getValue(), TypeDescriptors.fromRaw(entry.getKey()).toString()); 107 | } 108 | } 109 | 110 | @Test 111 | public void testTypes() { 112 | Map desc = new HashMap<>(); 113 | desc.put("B", PrimitiveTypeDescriptor.class); 114 | desc.put("S", PrimitiveTypeDescriptor.class); 115 | desc.put("I", PrimitiveTypeDescriptor.class); 116 | desc.put("J", PrimitiveTypeDescriptor.class); 117 | desc.put("F", PrimitiveTypeDescriptor.class); 118 | desc.put("D", PrimitiveTypeDescriptor.class); 119 | desc.put("Z", PrimitiveTypeDescriptor.class); 120 | desc.put("C", PrimitiveTypeDescriptor.class); 121 | desc.put("[D", ArrayTypeDescriptor.class); 122 | desc.put("LFoo;", ClassTypeDescriptor.class); 123 | desc.put("[LFoo;", ArrayTypeDescriptor.class); 124 | desc.put("Lfoo/bar/Baz;", ClassTypeDescriptor.class); 125 | 126 | for (Map.Entry entry : desc.entrySet()) { 127 | assertEquals(entry.getValue(), TypeDescriptors.fromRaw(entry.getKey()).getClass()); 128 | } 129 | } 130 | 131 | @Test(expected = InputMismatchException.class) 132 | public void testInvalid() { 133 | TypeDescriptors.fromRaw("X"); 134 | } 135 | 136 | @Test(expected = InputMismatchException.class) 137 | public void testMoarInvalid() { 138 | TypeDescriptors.fromRaw("LFoo"); 139 | } 140 | 141 | @Test(expected = InputMismatchException.class) 142 | public void testMoastInvalid() { 143 | TypeDescriptors.fromClassName("LFoo;"); 144 | } 145 | 146 | @Test(expected = InputMismatchException.class) 147 | public void testMoarDifferentInvalid() { 148 | TypeDescriptors.fromRaw("JJ"); 149 | } 150 | 151 | @Test 152 | public void testCanonicalNames() throws Exception { 153 | final TypeDescriptor expected = TypeDescriptors.fromClassName("foo.Bar"); 154 | final TypeDescriptor actual = TypeDescriptors.fromClassName("foo/Bar"); 155 | assertEquals(expected, actual); 156 | } 157 | 158 | @Test 159 | public void testNewClassTypeDescriptor() throws Exception { 160 | final ClassTypeDescriptor a = TypeDescriptors.fromClassName("foo.Bar"); 161 | final ClassTypeDescriptor b = TypeDescriptors.fromClassName("foo/Bar"); 162 | assertEquals(a, b); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/datamodel/MethodDescriptorsTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.datamodel; 37 | 38 | import static org.junit.Assert.assertEquals; 39 | 40 | import org.junit.Test; 41 | 42 | public class MethodDescriptorsTest { 43 | 44 | @Test 45 | public void testMethodDescriptionParser() { 46 | MethodDescriptor desc1 = MethodDescriptors.fromDesc("([I[[Lfoo/Bar;Z)V", "baz"); 47 | MethodDescriptor desc2 = 48 | new MethodDescriptorBuilder() 49 | .returnType(TypeDescriptors.fromRaw("V")) 50 | .parameterTypes( 51 | TypeDescriptors.fromRaw("[I"), 52 | TypeDescriptors.fromRaw("[[Lfoo/Bar;"), 53 | TypeDescriptors.fromRaw("Z")) 54 | .name("baz") 55 | .build(); 56 | assertEquals("Method descriptors should be identical", desc1, desc2); 57 | } 58 | 59 | @Test 60 | public void testPrettyParameters() { 61 | MethodDescriptor desc = MethodDescriptors.fromDesc("([I[[Lfoo/Bar;Z)V", "baz"); 62 | assertEquals("(int[], foo.Bar[][], boolean)", desc.prettyParameters()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/test/java/com/spotify/missinglink/nested/ClassWithNestedClass.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-core 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.nested; 37 | 38 | public class ClassWithNestedClass { 39 | 40 | public int foo(String s) { 41 | return new NestedClass().bar(Long.parseLong(s)); 42 | } 43 | 44 | public static class NestedClass { 45 | 46 | int bar(Long x) { 47 | return 2; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/test/resources/ArtifactLoaderTest.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/missinglink/1e95feac93756cdcd17d795e9df2c51b9c05a4ef/core/src/test/resources/ArtifactLoaderTest.jar -------------------------------------------------------------------------------- /core/src/test/resources/asm-5.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/missinglink/1e95feac93756cdcd17d795e9df2c51b9c05a4ef/core/src/test/resources/asm-5.0.4.jar -------------------------------------------------------------------------------- /core/src/test/resources/bcprov-jdk15on-1.68.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/missinglink/1e95feac93756cdcd17d795e9df2c51b9c05a4ef/core/src/test/resources/bcprov-jdk15on-1.68.jar -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example projects 2 | 3 | ## has-problematic-dependency 4 | 5 | This project has a class with a method that calls a Guava method which was 6 | removed in version 18. 7 | 8 | ## uses-problematic-dependency 9 | 10 | This project calls the method from `has` above, while setting the Guava 11 | dependency in pom.xml to version 18. By overriding the Guava version that `has` 12 | depends on, a NoSuchMethodError would be encountered at runtime. 13 | 14 | When the `missinglink-maven-plugin` is executed against this example project (the 15 | example adds the plugin to the build lifecycle automatically), it outputs a 16 | warning about this conflict and fails the build since `failOnConflicts=true`: 17 | 18 | ``` 19 | $ cd examples/uses-problematic-dependency 20 | $ mvn test 21 | [INFO] Scanning for projects... 22 | [INFO] 23 | [INFO] ------------------------------------------------------------------------ 24 | [INFO] Building uses-problematic-dependency 0.1-SNAPSHOT 25 | [INFO] ------------------------------------------------------------------------ 26 | [INFO] 27 | ... 28 | [INFO] 29 | [INFO] --- missinglink-maven-plugin:0.1.0:check-conflicts (default) @ uses-problematic-dependency --- 30 | [INFO] 1 conflicts found 31 | [WARNING] - Problem in class com.spotify.missinglink.examples.ProblematicDependency 32 | method: java.lang.Object reliesOnRemovedMethod() 33 | call to: com.google.common.base.Enums.valueOfFunction(java.lang.Class) 34 | reason: Method not found: com.google.common.base.Enums.valueOfFunction(java.lang.Class) 35 | [INFO] ------------------------------------------------------------------------ 36 | [INFO] BUILD FAILURE 37 | [INFO] ------------------------------------------------------------------------ 38 | ``` 39 | -------------------------------------------------------------------------------- /examples/has-problematic-dependency/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.spotify.missinglink.examples 9 | examples-parent 10 | 0.1-SNAPSHOT 11 | 12 | 13 | has-problematic-dependency 14 | jar 15 | 16 | 17 | An example of a project with a problematic dependency. This artifact has methods that call Guava 18 | methods which have been removed as of version 18. 19 | 20 | 21 | 22 | 23 | com.google.guava 24 | guava 25 | 14.0.1 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/has-problematic-dependency/src/main/java/com/spotify/javadeps/examples/ProblematicDependency.java: -------------------------------------------------------------------------------- 1 | package com.spotify.missinglink.examples; 2 | 3 | import com.google.common.base.Enums; 4 | 5 | /** 6 | * Calls a method in the Guava Enums class which was removed in guava 18. If a project calls this 7 | * method while overriding Guava to >= 18, it will cause a NoSuchMethodError at runtime. 8 | */ 9 | public class ProblematicDependency { 10 | 11 | public static Object reliesOnRemovedMethod() { 12 | return Enums.valueOfFunction(Foo.class); 13 | } 14 | 15 | enum Foo { 16 | BAR 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.spotify.missinglink.examples 8 | examples-parent 9 | 0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | Example projects 14 | 15 | 16 | 17 | has-problematic-dependency 18 | uses-problematic-dependency 19 | 20 | 21 | 22 | 1.8 23 | UTF-8 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | 3.3 33 | 34 | ${java.version} 35 | ${java.version} 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/uses-problematic-dependency/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.spotify.missinglink.examples 9 | examples-parent 10 | 0.1-SNAPSHOT 11 | 12 | 13 | uses-problematic-dependency 14 | 15 | 16 | Example project that calls a class in 17 | com.spotify.missinglink.examples:has-problematic-dependency 18 | which relies on a method from Guava which has been removed in v18. By overridding the guava 19 | dependency to version 18 in this project, we cause a problem which would normally result in a 20 | NoSuchMethodError. 21 | 22 | 23 | 24 | 25 | 26 | com.google.guava 27 | guava 28 | 18.0 29 | 30 | 31 | com.spotify.missinglink.examples 32 | has-problematic-dependency 33 | ${project.version} 34 | 35 | 36 | 37 | 38 | 39 | 40 | com.spotify 41 | missinglink-maven-plugin 42 | ${project.version} 43 | 44 | true 45 | 46 | 47 | 48 | check 49 | process-classes 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /examples/uses-problematic-dependency/src/main/java/com/spotify/javadeps/examples/UsesProblematicDependency.java: -------------------------------------------------------------------------------- 1 | package com.spotify.missinglink.examples; 2 | 3 | /** 4 | * Invokes a method from {@link com.spotify.missinglink.examples.ProblematicDependency} which will blow 5 | * up if Guava >= 18 is used. 6 | */ 7 | public class UsesProblematicDependency { 8 | 9 | static Object callsClassThatReliesOnDeletedGuavaMethod() { 10 | return ProblematicDependency.reliesOnRemovedMethod(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /execute-maven-plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ $# -lt 1 ]; then 3 | echo "Usage: execute-maven-plugin.sh " 4 | exit 1 5 | fi 6 | PROJECT=$1 7 | shift 8 | mvn -e -Dverbose com.spotify:missinglink-maven-plugin:0.1.1-SNAPSHOT:check -f "$PROJECT/pom.xml" "$@" 9 | 10 | -------------------------------------------------------------------------------- /install-maven-plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mvn -am -pl maven-plugin clean install "$@" 3 | -------------------------------------------------------------------------------- /maven-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.spotify 7 | missinglink-parent 8 | 0.2.12-SNAPSHOT 9 | 10 | 11 | missinglink-maven-plugin 12 | maven-plugin 13 | 14 | 15 | 3.9.9 16 | 17 | 18 | 19 | 3.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.codehaus.plexus 27 | plexus-utils 28 | 3.5.1 29 | 30 | 31 | org.codehaus.plexus 32 | plexus-component-annotations 33 | 2.2.0 34 | 35 | 36 | org.slf4j 37 | slf4j-api 38 | 1.7.36 39 | 40 | 41 | 42 | 43 | 44 | 45 | com.spotify 46 | missinglink-core 47 | ${project.version} 48 | 49 | 50 | com.google.guava 51 | guava 52 | 33.3.1-jre 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugin-tools 58 | maven-plugin-annotations 59 | 3.15.0 60 | provided 61 | 62 | 63 | org.apache.maven 64 | maven-plugin-api 65 | ${maven.api.version} 66 | 67 | 68 | org.apache.maven 69 | maven-core 70 | ${maven.api.version} 71 | 72 | 73 | org.apache.maven 74 | maven-artifact 75 | ${maven.api.version} 76 | 77 | 78 | org.apache.maven.shared 79 | maven-dependency-tree 80 | 3.3.0 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugin-testing 86 | maven-plugin-testing-harness 87 | 3.3.0 88 | test 89 | 90 | 91 | org.apache.maven 92 | maven-compat 93 | ${maven.api.version} 94 | test 95 | 96 | 97 | junit 98 | junit 99 | 4.13.2 100 | test 101 | 102 | 103 | org.mockito 104 | mockito-core 105 | 4.9.0 106 | test 107 | 108 | 109 | org.assertj 110 | assertj-core 111 | 3.26.3 112 | test 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-plugin-plugin 121 | 3.15.0 122 | 123 | 124 | java-annotations 125 | 126 | 127 | 128 | 129 | default-descriptor 130 | 131 | descriptor 132 | 133 | process-classes 134 | 135 | 136 | help-descriptor 137 | 138 | helpmojo 139 | 140 | process-classes 141 | 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-invoker-plugin 147 | 3.8.0 148 | 149 | 150 | 151 | 00-test-harness/pom.xml 152 | 153 | ${project.build.directory}/it 154 | 155 | */pom.xml 156 | 157 | verify 158 | ${project.build.directory}/local-repo 159 | src/it/settings.xml 160 | true 161 | 162 | clean 163 | install 164 | 165 | 166 | 167 | 168 | integration-test 169 | 170 | install 171 | integration-test 172 | verify 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-class-duplicate/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | a-class-duplicate 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | 12 | org.apache.maven.plugins 13 | maven-compiler-plugin 14 | 3.5 15 | 16 | 8 17 | 8 18 | false 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-class-duplicate/src/main/java/A.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class A { 5 | 6 | public void methodOne(String s) { 7 | System.out.println("a-copy-one: " + s); 8 | } 9 | 10 | public void methodTwo(String s) { 11 | System.out.println("a-copy-two: " + s); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | parent 7 | 1-SNAPSHOT 8 | 9 | 10 | a 11 | 1-SNAPSHOT 12 | 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/A.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class A { 5 | 6 | public Object publicFieldOne; 7 | protected Object protectedFieldOne; 8 | private Object privateFieldOne; 9 | public static Object staticFieldOne; 10 | 11 | public void internalStaticFieldAccess() { staticFieldOne = ""; } 12 | public void internalFieldAccess() { A a = new A(); a.publicFieldOne = ""; } 13 | 14 | public void methodOne(String s) { 15 | System.out.println("a2-one: " + s); 16 | } 17 | 18 | public void methodTwo(Integer s) { 19 | System.out.println("a2-two: " + s); 20 | } 21 | 22 | public void accessLevelToChange() { 23 | System.out.println("accessible!"); 24 | } 25 | 26 | public static String staticIsNoise(String s, int i) { 27 | return "static"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/AnInterface.java: -------------------------------------------------------------------------------- 1 | public interface AnInterface { 2 | Object aMethod(String parameter) throws Exception; 3 | } 4 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/MethodWillBeRemoved.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class MethodWillBeRemoved { 5 | public void imGoingAway() { 6 | System.out.println("Still here!"); 7 | } 8 | 9 | public void butIWillStay() { 10 | System.out.println("Not going anywhere..."); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/Misc.java: -------------------------------------------------------------------------------- 1 | public class Misc { 2 | 3 | public void internalFieldAccess() { A a = new A(); a.publicFieldOne = ""; } 4 | 5 | } 6 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/SpecificReturnType.java: -------------------------------------------------------------------------------- 1 | public abstract class SpecificReturnType implements AnInterface { 2 | @Override 3 | public abstract String aMethod(String parameter); 4 | } 5 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/WillGoAway.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class WillGoAway { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v1/src/main/java/com/spotify/missinglink/d/WillGoAway.java: -------------------------------------------------------------------------------- 1 | package com.spotify.missinglink.d; 2 | 3 | /** 4 | * TODO: document! 5 | */ 6 | public class WillGoAway { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | parent 7 | 1-SNAPSHOT 8 | 9 | 10 | a 11 | 2-SNAPSHOT 12 | 13 | 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v2/src/main/java/A.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class A { 5 | 6 | public void methodOne(String s) { 7 | System.out.println("a1-one: " + s); 8 | } 9 | 10 | public void methodTwo(Object s) { 11 | System.out.println("a1-two: " + s); 12 | } 13 | 14 | private void accessLevelToChange() { 15 | System.out.println("not accessible...!"); 16 | } 17 | 18 | public String staticIsNoise(String s, int i) { 19 | return "static"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v2/src/main/java/AnInterface.java: -------------------------------------------------------------------------------- 1 | public interface AnInterface { 2 | Object aMethod(String parameter) throws Exception; 3 | Object anotherMethod(String parameter) throws Exception; 4 | } 5 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v2/src/main/java/MethodWillBeRemoved.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class MethodWillBeRemoved { 5 | public void butIWillStay() { 6 | System.out.println("Not going anywhere..."); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/a-v2/src/main/java/SpecificReturnType.java: -------------------------------------------------------------------------------- 1 | public abstract class SpecificReturnType implements AnInterface { 2 | @Override 3 | public Object aMethod(String parameter) { 4 | return "in"; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/b/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | parent 7 | 1-SNAPSHOT 8 | 9 | 10 | b 11 | 1-SNAPSHOT 12 | 13 | 14 | 15 | com.spotify.missinglink.tests 16 | a 17 | 1-SNAPSHOT 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/b/src/main/java/AnInterfaceImplementation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class AnInterfaceImplementation implements AnInterface { 5 | 6 | @Override 7 | public Object aMethod(String parameter) throws Exception { 8 | return parameter; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/b/src/main/java/B.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class B { 5 | public static void methodShouldBeMissing() { 6 | A a = new A(); 7 | a.methodOne("hi from b"); 8 | a.methodTwo(125); 9 | } 10 | 11 | public static void classShouldBeMissing() { 12 | System.out.println(new WillGoAway()); 13 | } 14 | 15 | public static void specificReturnTypeBroken(SpecificReturnType instance) { 16 | String result = instance.aMethod("parameter"); 17 | 18 | System.out.println("b got result: " + result); 19 | } 20 | 21 | public static AnInterface createAnInterface() { 22 | return new AnInterfaceImplementation(); 23 | } 24 | 25 | public static void doThings(MethodWillBeRemoved methodWillBeRemoved) { 26 | methodWillBeRemoved.imGoingAway(); 27 | } 28 | 29 | public static void methodBecameInaccessible() { 30 | new A().accessLevelToChange(); 31 | } 32 | 33 | public static void methodNoLongerStatic() { 34 | A.staticIsNoise("hi", 14); 35 | } 36 | 37 | public static void fieldShouldBeMissing() { 38 | A a = new A(); 39 | a.publicFieldOne = "Foo"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/b/src/main/java/C.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class C extends WillGoAway { 5 | } 6 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/b/src/main/java/com/spotify/missinglink/e/E.java: -------------------------------------------------------------------------------- 1 | package com.spotify.missinglink.e; 2 | 3 | import com.spotify.missinglink.d.WillGoAway; 4 | 5 | /** 6 | * TODO: document! 7 | */ 8 | public class E { 9 | public static void classShouldBeMissing() { 10 | System.out.println(new WillGoAway()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/00-test-harness/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | parent 7 | 1-SNAPSHOT 8 | pom 9 | 10 | 11 | a-v1 12 | a-v2 13 | a-class-duplicate 14 | b 15 | 16 | 17 | 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-compiler-plugin 22 | 3.5 23 | 24 | 8 25 | 8 26 | false 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /maven-plugin/src/it/class-missing-target-destination/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | class-missing-target-destination 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | 37 | com.spotify.missinglink.d 38 | 39 | 40 | 41 | 42 | 43 | check 44 | process-classes 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-compiler-plugin 51 | 3.5 52 | 53 | 8 54 | 8 55 | false 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /maven-plugin/src/it/class-missing-target-destination/src/main/java/com/spotify/missinglink/ClassMissingAllowDestination.java: -------------------------------------------------------------------------------- 1 | package com.spotify.missinglink; 2 | 3 | import com.spotify.missinglink.e.E; 4 | 5 | /** 6 | * This is expected to generate a runtime error because WillGoAway(), as invoked by E.classShouldBeMissing(), 7 | * doesn't exist in a conflicting package. 8 | */ 9 | public class ClassMissingAllowDestination { 10 | 11 | public static void main(String[] args) { 12 | E.classShouldBeMissing(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /maven-plugin/src/it/class-missing-target-destination/src/test/java/com/spotify/missinglink/ClassMissingAllowDestinationTest.java: -------------------------------------------------------------------------------- 1 | package com.spotify.missinglink; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class ClassMissingAllowDestinationTest { 7 | 8 | @Test 9 | public void shouldThrowError() throws Exception { 10 | Throwable ex = Assert.assertThrows(NoClassDefFoundError.class, () -> ClassMissingAllowDestination.main(new String[0])); 11 | Assert.assertTrue(ex.getMessage().contains("WillGoAway")); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/class-missing/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | class-missing 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/class-missing/src/main/java/ClassMissing.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class ClassMissing { 6 | 7 | public static void main(String[] args) { 8 | B.classShouldBeMissing(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/class-missing/src/test/java/ClassMissingTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class ClassMissingTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(NoClassDefFoundError.class, () -> ClassMissing.main(new String[0])); 9 | Assert.assertTrue(ex.getMessage().contains("WillGoAway")); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /maven-plugin/src/it/field-removed/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | field-removed 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/field-removed/src/main/java/FieldRemoved.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class FieldRemoved { 6 | 7 | public static void main(String[] args) { 8 | B.fieldShouldBeMissing(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/field-removed/src/test/java/FieldRemovedTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class FieldRemovedTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(NoSuchFieldError.class, 9 | () -> FieldRemoved.main(new String[0])); 10 | Assert.assertTrue(ex.getMessage().contains("publicFieldOne")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/instantiate-with-method-missing/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | instantiate-with-method-missing 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/instantiate-with-method-missing/src/main/java/InstantiateWithMethodMissing.java: -------------------------------------------------------------------------------- 1 | /** 2 | */ 3 | public class InstantiateWithMethodMissing { 4 | 5 | public static void main(String[] args) throws Exception { 6 | AnInterface anInterface = B.createAnInterface(); 7 | 8 | anInterface.anotherMethod("hey"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/instantiate-with-method-missing/src/test/java/InstantiateWithMethodMissingTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class InstantiateWithMethodMissingTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(AbstractMethodError.class, 9 | () -> InstantiateWithMethodMissing.main(new String[0])); 10 | Assert.assertTrue(ex.getMessage().contains("anotherMethod")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/library-invokes-removed-method/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | library-invokes-removed-method 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/library-invokes-removed-method/src/main/java/LibraryInvokesRemovedMethod.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class LibraryInvokesRemovedMethod { 6 | 7 | public static void main(String[] args) { 8 | MethodWillBeRemoved methodWillBeRemoved = new MethodWillBeRemoved(); 9 | methodWillBeRemoved.butIWillStay(); 10 | 11 | B.doThings(methodWillBeRemoved); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/library-invokes-removed-method/src/test/java/LibraryInvokesRemovedMethodTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class LibraryInvokesRemovedMethodTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(NoSuchMethodError.class, 9 | () -> LibraryInvokesRemovedMethod.main(new String[0])); 10 | Assert.assertTrue(ex.getMessage().contains("imGoingAway")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/method-became-inaccessible/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | method-became-inaccessible 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/method-became-inaccessible/src/main/java/MethodBecameInaccessible.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class MethodBecameInaccessible { 6 | 7 | public static void main(String[] args) { 8 | B.methodBecameInaccessible(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/method-became-inaccessible/src/test/java/MethodBecameInaccessibleTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class MethodBecameInaccessibleTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(IllegalAccessError.class, 9 | () -> MethodBecameInaccessible.main(new String[0])); 10 | Assert.assertTrue(ex.getMessage().contains("accessLevelToChange")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/method-removed/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | method-removed 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/method-removed/src/main/java/MethodRemoved.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class MethodRemoved { 6 | 7 | public static void main(String[] args) { 8 | A a = new A(); 9 | a.methodOne("hi from Simple"); 10 | a.methodTwo("hi again from Simple"); 11 | B.methodShouldBeMissing(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/method-removed/src/test/java/MethodRemovedTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class MethodRemovedTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(NoSuchMethodError.class, 9 | () -> MethodRemoved.main(new String[0])); 10 | Assert.assertTrue(ex.getMessage().contains("A.methodTwo")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /maven-plugin/src/it/return-type-change/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | return-type-change 7 | 0.1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/return-type-change/src/main/java/ReturnTypeChange.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class ReturnTypeChange { 6 | 7 | public static void main(String[] args) { 8 | B.specificReturnTypeBroken(new SpecificReturnType() { 9 | @Override 10 | public Object aMethod(String parameter) { 11 | return parameter.length(); 12 | } 13 | 14 | @Override 15 | public Object anotherMethod(String parameter) throws Exception { 16 | throw new UnsupportedOperationException(); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /maven-plugin/src/it/return-type-change/src/test/java/ReturnTypeChangeTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class ReturnTypeChangeTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(NoSuchMethodError.class, 9 | () -> ReturnTypeChange.main(new String[0])); 10 | 11 | // the message for NoSuchMethodError seems to have changed after Java 8 12 | String message = System.getProperty("java.vm.specification.version").startsWith("1.8") 13 | ? "SpecificReturnType.aMethod(Ljava/lang/String;)Ljava/lang/String;" 14 | : "'java.lang.String SpecificReturnType.aMethod(java.lang.String)'"; 15 | 16 | Assert.assertEquals("Exception message: " + ex.getMessage(), 17 | ex.getMessage(), message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /maven-plugin/src/it/same-class-different-artifact/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | same-class-different-artifact 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a-class-duplicate 13 | 1-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/same-class-different-artifact/src/main/java/SameClassDifferentArtifact.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class SameClassDifferentArtifact { 6 | 7 | public static void main(String[] args) { 8 | A a = new A(); 9 | a.methodOne("hi from main"); 10 | a.methodTwo("hi again from main"); 11 | B.methodShouldBeMissing(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/same-class-different-artifact/src/test/java/SameClassDifferentArtifactTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class SameClassDifferentArtifactTest { 5 | @Test 6 | public void shouldThrowError() throws Exception { 7 | Throwable ex = Assert.assertThrows(NoSuchMethodError.class, 8 | () -> SameClassDifferentArtifact.main(new String[0])); 9 | Assert.assertTrue(ex.getMessage().contains("A.methodTwo")); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /maven-plugin/src/it/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | it-repo 7 | 8 | true 9 | 10 | 11 | 12 | local.central 13 | @localRepositoryUrl@ 14 | 15 | true 16 | 17 | 18 | true 19 | 20 | 21 | 22 | 23 | 24 | local.central 25 | @localRepositoryUrl@ 26 | 27 | true 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /maven-plugin/src/it/static-method-became-instance/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | static-method-became-instance 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/static-method-became-instance/src/main/java/StaticMethodBecameInstance.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because A.methodTwo(Integer) doesn't 3 | * exist when B.methodShouldBeMissing() tries to call it. We should be able to detect that. 4 | */ 5 | public class StaticMethodBecameInstance { 6 | 7 | public static void main(String[] args) { 8 | B.methodNoLongerStatic(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/static-method-became-instance/src/test/java/StaticMethodBecameInstanceTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | public class StaticMethodBecameInstanceTest { 5 | 6 | @Test 7 | public void shouldThrowError() throws Exception { 8 | Throwable ex = Assert.assertThrows(IncompatibleClassChangeError.class, 9 | () -> StaticMethodBecameInstance.main(new String[0])); 10 | Assert.assertTrue(ex.getMessage().contains("Expected static method")); 11 | Assert.assertTrue(ex.getMessage().contains("staticIsNoise")); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maven-plugin/src/it/superclass-missing/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spotify.missinglink.tests 6 | superclass-missing 7 | 1-SNAPSHOT 8 | 9 | 10 | 11 | com.spotify.missinglink.tests 12 | a 13 | 2-SNAPSHOT 14 | 15 | 16 | com.spotify.missinglink.tests 17 | b 18 | 1-SNAPSHOT 19 | 20 | 21 | junit 22 | junit 23 | 4.13.1 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | @project.groupId@ 32 | @project.artifactId@ 33 | @project.version@ 34 | 35 | 36 | check 37 | process-classes 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.5 45 | 46 | 8 47 | 8 48 | false 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /maven-plugin/src/it/superclass-missing/src/main/java/SuperClassMissing.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This is expected to generate a runtime error because C cannot be resolved as its superclass 3 | * is gone from A. 4 | */ 5 | public class SuperClassMissing { 6 | 7 | public static void main(String[] args) { 8 | System.out.println(C.class.getCanonicalName()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maven-plugin/src/it/superclass-missing/src/test/java/SuperClassMissingTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Assert; 2 | import org.junit.Test; 3 | 4 | import java.lang.NoClassDefFoundError; 5 | 6 | public class SuperClassMissingTest { 7 | 8 | @Test 9 | public void shouldThrowError() throws Exception { 10 | Throwable ex = Assert.assertThrows(NoClassDefFoundError.class, 11 | () -> SuperClassMissing.main(new String[0])); 12 | Assert.assertTrue(ex.getMessage().contains("WillGoAway")); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /maven-plugin/src/main/java/com/spotify/missinglink/maven/MavenArtifactName.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-maven-plugin 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.maven; 37 | 38 | import static com.google.common.base.Preconditions.checkNotNull; 39 | 40 | import com.spotify.missinglink.datamodel.ArtifactName; 41 | 42 | public class MavenArtifactName extends ArtifactName { 43 | 44 | private final String groupId; 45 | private final String artifactId; 46 | private final String version; 47 | 48 | public MavenArtifactName(String groupId, String artifactId, String version) { 49 | super(groupId + ":" + artifactId + ":" + version); 50 | this.groupId = checkNotNull(groupId); 51 | this.artifactId = checkNotNull(artifactId); 52 | this.version = checkNotNull(version); 53 | } 54 | 55 | String groupId() { 56 | return groupId; 57 | } 58 | 59 | String artifactId() { 60 | return artifactId; 61 | } 62 | 63 | String version() { 64 | return version; 65 | } 66 | 67 | @Override 68 | public boolean equals(Object o) { 69 | if (this == o) { 70 | return true; 71 | } 72 | if (o == null || getClass() != o.getClass()) { 73 | return false; 74 | } 75 | if (!super.equals(o)) { 76 | return false; 77 | } 78 | 79 | MavenArtifactName that = (MavenArtifactName) o; 80 | 81 | if (!groupId.equals(that.groupId)) { 82 | return false; 83 | } 84 | if (!artifactId.equals(that.artifactId)) { 85 | return false; 86 | } 87 | return version.equals(that.version); 88 | } 89 | 90 | @Override 91 | public int hashCode() { 92 | int result = super.hashCode(); 93 | result = 31 * result + groupId.hashCode(); 94 | result = 31 * result + artifactId.hashCode(); 95 | result = 31 * result + version.hashCode(); 96 | return result; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "MavenArtifactName{" 102 | + "groupId='" 103 | + groupId 104 | + '\'' 105 | + ", artifactId='" 106 | + artifactId 107 | + '\'' 108 | + ", version='" 109 | + version 110 | + '\'' 111 | + '}'; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /maven-plugin/src/main/java/com/spotify/missinglink/maven/PackageFilter.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-maven-plugin 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.maven; 37 | 38 | import static com.google.common.base.Preconditions.checkNotNull; 39 | 40 | import com.google.common.base.Preconditions; 41 | 42 | /** 43 | * Package names to be ignored when reporting conflicts. A package name is a String like 44 | * "javax.servlet" (not a regular expression). By default, subpackages of the specified package are 45 | * also ignored - to disable this behavior, set filterSubpackages to false. 46 | */ 47 | public class PackageFilter { 48 | 49 | // Plexus seems to require classes to be referenced in the Maven project model to have no-arg 50 | // constructors and getters/setters. 51 | 52 | private String name; 53 | private boolean filterSubpackages = true; 54 | 55 | public PackageFilter() {} 56 | 57 | public PackageFilter(String name, boolean filterSubpackages) { 58 | this.name = checkNotNull(name); 59 | Preconditions.checkArgument(!name.isEmpty(), "name cannot be empty"); 60 | this.filterSubpackages = filterSubpackages; 61 | } 62 | 63 | public String getPackage() { 64 | return name; 65 | } 66 | 67 | // lets the xml field be called "" 68 | public void setPackage(String name) { 69 | this.name = name; 70 | } 71 | 72 | public boolean isFilterSubpackages() { 73 | return filterSubpackages; 74 | } 75 | 76 | public void setFilterSubpackages(boolean filterSubpackages) { 77 | this.filterSubpackages = filterSubpackages; 78 | } 79 | 80 | public void setIgnoreSubpackages(boolean ignoreSubpackages) { 81 | this.filterSubpackages = ignoreSubpackages; 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | if (this == o) { 87 | return true; 88 | } 89 | if (o == null || getClass() != o.getClass()) { 90 | return false; 91 | } 92 | 93 | PackageFilter that = (PackageFilter) o; 94 | 95 | if (filterSubpackages != that.filterSubpackages) { 96 | return false; 97 | } 98 | return name.equals(that.name); 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | int result = name.hashCode(); 104 | result = 31 * result + (filterSubpackages ? 1 : 0); 105 | return result; 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "IgnoredPackage{" 111 | + "name='" 112 | + name 113 | + '\'' 114 | + ", filterSubpackages=" 115 | + filterSubpackages 116 | + '}'; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /maven-plugin/src/main/java/com/spotify/missinglink/maven/Scope.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-maven-plugin 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.maven; 37 | 38 | /** 39 | * Enumeration of the Maven scopes that may be relevant for determining whether or not to include an 40 | * artifact in checks. 41 | */ 42 | public enum Scope { 43 | compile, 44 | test, 45 | runtime, 46 | system, 47 | provided 48 | } 49 | -------------------------------------------------------------------------------- /maven-plugin/src/test/java/com/spotify/missinglink/maven/MavenArtifactNameTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * missinglink-maven-plugin 4 | * -- 5 | * Copyright (C) 2016 - 2021 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 25 | * use this file except in compliance with the License. You may obtain a copy of 26 | * the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 32 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 33 | * License for the specific language governing permissions and limitations under 34 | * the License. 35 | */ 36 | package com.spotify.missinglink.maven; 37 | 38 | import static org.junit.Assert.assertEquals; 39 | 40 | import org.junit.Test; 41 | 42 | public class MavenArtifactNameTest { 43 | 44 | @Test 45 | public void testNameAsCoordinates() { 46 | final MavenArtifactName name = new MavenArtifactName("com.spotify", "secret-project", "1.2.3"); 47 | assertEquals("com.spotify:secret-project:1.2.3", name.name()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/exclude-dependencies/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | 18 | 19 | com.foobar 20 | bizbat 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/fail-on-warning/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/ignore-destination-packages/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | 20 | com.foo 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/ignore-source-packages/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | 20 | org.codehaus.janino 21 | false 22 | 23 | 24 | groovy.lang 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/include-categories-bad-values/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | FOO_BAR 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/include-categories/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | CLASS_NOT_FOUND 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/simple-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/skip/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.spacesprockets 22 | foo-bar 23 | 1.2.3 24 | jar 25 | 26 | 27 | 28 | 29 | com.spotify 30 | missinglink-maven-plugin 31 | 0.1.2-SNAPSHOT 32 | 33 | true 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/target-destination-packages/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | 20 | com.foobar 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /maven-plugin/src/test/projects/target-source-packages/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.spacesprockets 6 | foo-bar 7 | 1.2.3 8 | jar 9 | 10 | 11 | 12 | 13 | com.spotify 14 | missinglink-maven-plugin 15 | 0.1.2-SNAPSHOT 16 | 17 | true 18 | 19 | 20 | com.foobar 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.spotify 9 | foss-root 10 | 17 11 | 12 | 13 | missinglink-parent 14 | 0.2.12-SNAPSHOT 15 | pom 16 | 17 | missinglink 18 | 19 | 20 | Missinglink scans the bytecode of dependency libraries to attempt to find problems 21 | (such as NoSuchMethodError) caused by conflicting versions that would otherwise only 22 | be found at runtime. 23 | 24 | 25 | https://github.com/spotify/missinglink 26 | 27 | 28 | Spotify AB 29 | http://www.spotify.com 30 | 31 | 32 | 33 | 34 | liljencrantz 35 | Axel Liljencrantz 36 | liljencrantz@spotify.com 37 | 38 | 39 | krka 40 | Kristofer Karlsson 41 | krka@spotify.com 42 | 43 | 44 | mattbrown 45 | Matt Brown 46 | mattbrown@spotify.com 47 | 48 | 49 | pettermahlen 50 | Petter Måhlén 51 | petter@spotify.com 52 | 53 | 54 | dflemstr 55 | David Flemström 56 | dflemstr@spotify.com 57 | 58 | contributor 59 | 60 | 61 | 62 | 63 | 64 | benchmarks 65 | core 66 | maven-plugin 67 | 68 | 69 | 70 | 0.26.2 71 | 74 | xml 75 | 76 | 77 | 78 | scm:git:git@github.com:spotify/missinglink.git 79 | scm:git:git@github.com:spotify/missinglink.git 80 | https://github.com/spotify/missinglink 81 | HEAD 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.codehaus.plexus 89 | plexus-component-annotations 90 | 2.2.0 91 | 92 | 93 | org.apache.commons 94 | commons-lang3 95 | 3.17.0 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-compiler-plugin 106 | 3.13.0 107 | 108 | false 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-compiler-plugin 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-enforcer-plugin 121 | 122 | 123 | org.codehaus.mojo 124 | findbugs-maven-plugin 125 | 126 | 127 | 128 | com.coveo 129 | fmt-maven-plugin 130 | 131 | 132 | org.jacoco 133 | jacoco-maven-plugin 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /system-tests/b/src/main/java/C.java: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: document! 3 | */ 4 | public class C extends WillGoAway { 5 | } 6 | --------------------------------------------------------------------------------