├── maven ├── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ └── response │ │ │ │ ├── master-index.xml │ │ │ │ ├── group-index.xml │ │ │ │ └── maven-metadata.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── birbit │ │ │ └── artifactfinder │ │ │ └── maven │ │ │ └── MavenFetcherTest.kt │ └── main │ │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── artifactfinder │ │ └── maven │ │ ├── vo │ │ └── MavenDTO.kt │ │ ├── ExclusionFilters.kt │ │ ├── JacksonConverterFactory.kt │ │ ├── MavenFetcher.kt │ │ └── MavenApi.kt └── build.gradle ├── parser ├── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ ├── lib-release.aar │ │ │ └── gradle-wrapper.jar │ │ └── kotlin │ │ │ └── com │ │ │ └── birbit │ │ │ └── artifactfinder │ │ │ └── parser │ │ │ └── testapk │ │ │ ├── templates │ │ │ ├── AndroidFiles.kt │ │ │ └── GradleFiles.kt │ │ │ ├── ArtifactInfoSubject.kt │ │ │ └── TestApk.kt │ └── main │ │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── artifactfinder │ │ └── parser │ │ ├── vo │ │ ├── CodeSource.kt │ │ └── ParsedArtifactInfo.kt │ │ ├── CodeSourceParser.kt │ │ ├── StringExt.kt │ │ ├── ClassNodeExt.kt │ │ ├── ExclusionFilters.kt │ │ ├── KotlinMetadataExtractor.kt │ │ ├── KotlinMetadataExt.kt │ │ ├── ArtifactInput.kt │ │ └── ClassZipEntryParser.kt └── build.gradle ├── sample.gif ├── artifactfinder ├── src │ ├── main │ │ ├── resources │ │ │ └── external_sources.json │ │ └── kotlin │ │ │ └── com │ │ │ └── birbit │ │ │ └── artifactfinder │ │ │ ├── FetcherSource.kt │ │ │ ├── ExternalSources.kt │ │ │ ├── external │ │ │ └── ExternalSourceSpec.kt │ │ │ ├── VersionSelector.kt │ │ │ ├── worker │ │ │ └── JobQueue.kt │ │ │ └── Main.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── artifactfinder │ │ ├── PlaygroundTest.kt │ │ ├── external │ │ └── ExternalSourceSpecTest.kt │ │ └── VersionSelectorTest.kt └── build.gradle ├── web-client ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── .gitignore ├── package.json ├── src │ ├── App.test.js │ ├── App.js │ ├── App.css │ ├── index.css │ ├── index.js │ └── ArtifactSearch.js └── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── scripts └── copyright.kt ├── model ├── src │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── birbit │ │ │ └── artifactfinder │ │ │ └── model │ │ │ ├── MethodLookup.kt │ │ │ ├── ClassRecord.kt │ │ │ ├── MethodRecord.kt │ │ │ ├── Artifact.kt │ │ │ ├── ClassLookup.kt │ │ │ ├── PendingArtifact.kt │ │ │ ├── SearchRecord.kt │ │ │ ├── db │ │ │ ├── SharedObjectPool.kt │ │ │ ├── DbDriver.kt │ │ │ ├── ConnectionProvider.kt │ │ │ └── JdbcDbImpl.kt │ │ │ ├── Version.kt │ │ │ ├── ArtifactDao.kt │ │ │ └── ResultSorter.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── artifactfinder │ │ └── model │ │ ├── ArtifactFinderDbTest.kt │ │ └── db │ │ ├── SharedObjectPoolTest.kt │ │ └── ConnectionProviderTest.kt └── build.gradle ├── settings.gradle ├── dto ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── artifactfinder │ │ └── dto │ │ ├── SearchResponseDTO.kt │ │ └── SearchResultDTO.kt └── build.gradle ├── deploy.sh ├── CONTRIBUTING.md ├── ideplugin ├── src │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── birbit │ │ │ └── artifactfinder │ │ │ └── ideplugin │ │ │ ├── SearchArtifactAction.kt │ │ │ ├── KotlinImportResolver.kt │ │ │ ├── JavaQuickFixContributor.kt │ │ │ ├── ui │ │ │ ├── ButtonRenderer.kt │ │ │ ├── SearchResultTableModel.kt │ │ │ ├── VersionPopupRenderer.kt │ │ │ └── SearchClassWindow.kt │ │ │ ├── SearchArtifactIntentionAction.kt │ │ │ ├── SearchArtifactApi.kt │ │ │ ├── SearchResultModel.kt │ │ │ └── BuildDependencyHandler.kt │ │ └── resources │ │ └── META-INF │ │ └── plugin.xml └── build.gradle ├── core ├── build.gradle └── src │ └── main │ └── kotlin │ └── com │ └── birbit │ └── artifactfinder │ ├── vo │ └── Artifactory.kt │ └── parser │ └── vo │ └── Info.kt ├── .github └── workflows │ └── build_and_test.yml ├── external_sources.json ├── webservice ├── build.gradle └── src │ └── main │ └── kotlin │ └── com │ └── birbit │ └── artifactfinder │ └── webservice │ └── ArtifactFinderRequestHandler.kt ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /maven/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /parser/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/sample.gif -------------------------------------------------------------------------------- /artifactfinder/src/main/resources/external_sources.json: -------------------------------------------------------------------------------- 1 | ../../../../external_sources.json -------------------------------------------------------------------------------- /web-client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /web-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/web-client/public/favicon.ico -------------------------------------------------------------------------------- /web-client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/web-client/public/logo192.png -------------------------------------------------------------------------------- /web-client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/web-client/public/logo512.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /parser/src/test/resources/lib-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/parser/src/test/resources/lib-release.aar -------------------------------------------------------------------------------- /parser/src/test/resources/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/ArtifactFinder/HEAD/parser/src/test/resources/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.idea 3 | .gradle 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | buildSrc/build 17 | **/build 18 | **/bin 19 | .settings 20 | .project 21 | .classpath 22 | -------------------------------------------------------------------------------- /web-client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web-client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /scripts/copyright.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright $YEAR Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/MethodLookup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | data class MethodLookup( 20 | val identifier: String, 21 | val methodId: Long 22 | ) 23 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | rootProject.name = 'ArtifactFinder' 18 | include ':core' 19 | include ':parser' 20 | include ':artifactfinder' 21 | include ':maven' 22 | include ':model' 23 | include ':ideplugin' 24 | include ':webservice' 25 | include ':dto' -------------------------------------------------------------------------------- /web-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://birbit.com/artifact_finder", 6 | "dependencies": { 7 | "axios": "^0.19.0", 8 | "react": "^16.9.0", 9 | "react-dom": "^16.9.0", 10 | "react-scripts": "3.1.2" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": { 22 | "production": [ 23 | ">0.2%", 24 | "not dead", 25 | "not op_mini all" 26 | ], 27 | "development": [ 28 | "last 1 chrome version", 29 | "last 1 firefox version", 30 | "last 1 safari version" 31 | ] 32 | }, 33 | "devDependencies": { 34 | "prettier": "1.18.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Google, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | #Sat Sep 14 22:53:52 PDT 2019 18 | distributionBase=GRADLE_USER_HOME 19 | distributionPath=wrapper/dists 20 | zipStoreBase=GRADLE_USER_HOME 21 | zipStorePath=wrapper/dists 22 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip 23 | -------------------------------------------------------------------------------- /maven/src/test/resources/response/master-index.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /dto/src/main/kotlin/com/birbit/artifactfinder/dto/SearchResponseDTO.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.dto 18 | 19 | import kotlinx.serialization.Serializable 20 | 21 | @Serializable 22 | data class SearchResponseDTO( 23 | val latestVersion: Int, 24 | val results: List 25 | ) 26 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/ClassRecord.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | data class ClassRecord( 20 | val id: Long, 21 | // . separated 22 | val pkg: String, 23 | // $ separated 24 | val name: String, 25 | val artifactId: Long 26 | ) 27 | -------------------------------------------------------------------------------- /web-client/src/App.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from "react"; 18 | import ReactDOM from "react-dom"; 19 | import App from "./App"; 20 | 21 | it("renders without crashing", () => { 22 | const div = document.createElement("div"); 23 | ReactDOM.render(, div); 24 | ReactDOM.unmountComponentAtNode(div); 25 | }); 26 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/MethodRecord.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | data class MethodRecord( 20 | val id: Long, 21 | val pkg: String, 22 | val name: String, 23 | val receivePkg: String?, 24 | val receiveName: String?, 25 | val artifactId: Long 26 | ) 27 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/Artifact.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | import com.birbit.artifactfinder.vo.Artifactory 20 | 21 | data class Artifact( 22 | val id: Long, 23 | val groupId: String, 24 | val artifactId: String, 25 | val version: Version, 26 | val artifactory: Artifactory 27 | ) 28 | -------------------------------------------------------------------------------- /web-client/src/App.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from "react"; 18 | import logo from "./logo.svg"; 19 | import "./App.css"; 20 | import ArtifactSearch from "./ArtifactSearch.js"; 21 | function App() { 22 | return ( 23 |
24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #Copyright 2019 Google, Inc. 2 | # 3 | #Licensed under the Apache License, Version 2.0 (the "License"); 4 | #you may not use this file except in compliance with the License. 5 | #You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | #Unless required by applicable law or agreed to in writing, software 10 | #distributed under the License is distributed on an "AS IS" BASIS, 11 | #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #See the License for the specific language governing permissions and 13 | #limitations under the License. 14 | #!/bin/bash 15 | cd web-client && npm run build && cd .. 16 | zip -r web-client.zip web-client/build/* 17 | scp web-client.zip $DROPLET:~/web-client.zip 18 | ssh $DROPLET 'cd ~ && rm -rf web-client && unzip web-client.zip' 19 | gw :artifactfinder:shadowDistZip 20 | scp artifactfinder/build/distributions/artifactfinder-shadow.zip $DROPLET:~/artifactfinder.zip 21 | ssh $DROPLET 'cd ~ && rm -rf artifactfinder-shadow && unzip artifactfinder.zip' 22 | -------------------------------------------------------------------------------- /maven/src/test/resources/response/group-index.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/vo/CodeSource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.vo 18 | 19 | import java.util.zip.ZipEntry 20 | 21 | class ClassZipEntry( 22 | val entry: ZipEntry, 23 | val stream: ByteArray 24 | ) 25 | 26 | /** 27 | * Some artifact that can provide class information 28 | */ 29 | interface CodeSource { 30 | val classDeclarations: Sequence 31 | } 32 | -------------------------------------------------------------------------------- /web-client/src/App.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .App { 18 | text-align: center; 19 | } 20 | 21 | .App-logo { 22 | height: 40vmin; 23 | } 24 | 25 | .App-header { 26 | background-color: #282c34; 27 | min-height: 100vh; 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | justify-content: center; 32 | font-size: calc(10px + 2vmin); 33 | color: white; 34 | } 35 | 36 | .App-link { 37 | color: #09d3ac; 38 | } 39 | -------------------------------------------------------------------------------- /web-client/src/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | body { 18 | margin: 0; 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 20 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 21 | sans-serif; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | code { 27 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 28 | monospace; 29 | } 30 | -------------------------------------------------------------------------------- /parser/src/test/kotlin/com/birbit/artifactfinder/parser/testapk/templates/AndroidFiles.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.testapk.templates 18 | 19 | fun androidManifest( 20 | appPkg: String 21 | ) = """ 22 | 24 | 27 | 28 | 29 | """.trimIndent() 30 | -------------------------------------------------------------------------------- /web-client/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from "react"; 18 | import ReactDOM from "react-dom"; 19 | import "./index.css"; 20 | import App from "./App"; 21 | import * as serviceWorker from "./serviceWorker"; 22 | 23 | ReactDOM.render(, document.getElementById("root")); 24 | 25 | // If you want your app to work offline and load faster, you can change 26 | // unregister() to register() below. Note this comes with some pitfalls. 27 | // Learn more about service workers: https://bit.ly/CRA-PWA 28 | serviceWorker.unregister(); 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /artifactfinder/src/main/kotlin/com/birbit/artifactfinder/FetcherSource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder 18 | 19 | import com.birbit.artifactfinder.vo.Artifactory 20 | 21 | data class FetcherSource( 22 | val artifacts: List, 23 | val groups: List 24 | ) 25 | 26 | data class GroupSource( 27 | val groupId: String, 28 | val artifactory: Artifactory 29 | ) 30 | 31 | data class ArtifactSource( 32 | val groupId: String, 33 | val artifactId: String, 34 | val artifactory: Artifactory, 35 | val processorCoordinates: String? = null 36 | ) 37 | -------------------------------------------------------------------------------- /maven/src/main/kotlin/com/birbit/artifactfinder/maven/vo/MavenDTO.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.maven.vo 18 | 19 | import java.io.InputStream 20 | 21 | data class GroupIndex(val groupId: String, val artifactIds: Set) 22 | 23 | data class ArtifactMetadata(val groupId: String, val artifactId: String, val versioning: Versioning) 24 | 25 | data class Versioning(val latest: String, val release: String, val versions: Set) 26 | 27 | enum class ArtifactType { 28 | JAR, 29 | AAR 30 | } 31 | 32 | data class Artifact( 33 | val type: ArtifactType, 34 | val inputStream: InputStream 35 | ) 36 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/SearchArtifactAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.birbit.artifactfinder.ideplugin.ui.SearchArtifactPanelController 20 | import com.intellij.openapi.actionSystem.AnAction 21 | import com.intellij.openapi.actionSystem.AnActionEvent 22 | 23 | class SearchArtifactAction : AnAction("Search Artifact") { 24 | override fun actionPerformed(event: AnActionEvent) { 25 | val project = event.project ?: return 26 | 27 | SearchArtifactPanelController( 28 | project = project 29 | ).buildAndShow() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/ClassLookup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | /** 20 | * Represents the lookup information for a class. 21 | * 22 | * For instance, if the class is called "Foo$Bar", we'll two entries in this table that has 23 | * identifier "Bar" and "Foo$Bar" that refers back to the classId. 24 | * 25 | * Identifiers are always lowercase so that we can do fast lookup w/ case-insensitive like 26 | * 27 | * The identifier is indexed, which allows us to do a query on it with prefix-search 28 | */ 29 | data class ClassLookup( 30 | val identifier: String, 31 | val classId: Long 32 | ) 33 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/PendingArtifact.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | import com.birbit.artifactfinder.vo.Artifactory 20 | 21 | data class PendingArtifact( 22 | val id: Long, 23 | val groupId: String, 24 | val artifactId: String, 25 | val version: Version, 26 | val retries: Int = 0, 27 | val fetched: Boolean = false, 28 | val artifactory: Artifactory 29 | ) { 30 | fun toArtifact() = Artifact( 31 | id = id, 32 | groupId = groupId, 33 | artifactId = artifactId, 34 | version = version, 35 | artifactory = artifactory 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/KotlinImportResolver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.intellij.codeInsight.intention.IntentionAction 20 | import org.jetbrains.kotlin.diagnostics.Errors 21 | import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor 22 | import org.jetbrains.kotlin.idea.quickfix.QuickFixes 23 | 24 | class KotlinImportResolver : QuickFixContributor { 25 | override fun registerQuickFixes(quickFixes: QuickFixes) { 26 | val action: IntentionAction = SearchArtifactIntentionAction() 27 | quickFixes.register(Errors.UNRESOLVED_REFERENCE, action) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /maven/src/test/resources/response/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | foo.bar 20 | artifact1 21 | 22 | 2.2.0-rc01 23 | 2.2.0-rc01 24 | 25 | 2.0.0-alpha1 26 | 2.0.0-beta01 27 | 2.0.0-rc01 28 | 2.0.0 29 | 2.1.0-alpha01 30 | 31 | 20180507111121 32 | 33 | 34 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/CodeSourceParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import com.birbit.artifactfinder.parser.vo.ArtifactInfoBuilder 20 | import com.birbit.artifactfinder.parser.vo.CodeSource 21 | import com.birbit.artifactfinder.parser.vo.ParsedArtifactInfo 22 | 23 | /** 24 | * Parses a [CodeSource] and turns it into [ParsedArtifactInfo] 25 | */ 26 | object CodeSourceParser { 27 | fun parse(source: CodeSource): ParsedArtifactInfo { 28 | val builder = ArtifactInfoBuilder() 29 | source.classDeclarations.forEach { 30 | ClassZipEntryParser.parse(it, builder) 31 | } 32 | return builder.build() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/StringExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import com.birbit.artifactfinder.parser.vo.ParsedClassInfo 20 | 21 | internal fun String.toClassInfo() = split('/').let { 22 | if (it.isEmpty()) { 23 | throw IllegalArgumentException("there is no package name / class name in $this") 24 | } else if (it.size < 2) { 25 | ParsedClassInfo( 26 | pkg = "", 27 | name = it[1].replace('.', '$') // for kotlin inner classes 28 | ) 29 | } else { 30 | ParsedClassInfo( 31 | pkg = it.take(it.size - 1).joinToString("."), 32 | name = it.last().replace('.', '$') 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/JavaQuickFixContributor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.intellij.codeInsight.daemon.QuickFixActionRegistrar 20 | import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider 21 | import com.intellij.psi.PsiJavaCodeReferenceElement 22 | 23 | class JavaQuickFixContributor : UnresolvedReferenceQuickFixProvider() { 24 | override fun registerFixes( 25 | ref: PsiJavaCodeReferenceElement, 26 | registrar: QuickFixActionRegistrar 27 | ) { 28 | registrar.register(SearchArtifactIntentionAction()) 29 | } 30 | 31 | override fun getReferenceClass() = PsiJavaCodeReferenceElement::class.java 32 | } 33 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/SearchRecord.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | data class SearchRecord( 20 | val pkg: String, 21 | val name: String, 22 | val type: Type, 23 | val receiverName: String?, 24 | val groupId: String, 25 | val artifactId: String, 26 | val version: Version 27 | ) : Comparable { 28 | var score: Int = 0 29 | override fun compareTo(other: SearchRecord): Int { 30 | val scopeCmp = score.compareTo(other.score) 31 | if (scopeCmp != 0) { 32 | return -scopeCmp 33 | } 34 | return -(version.compareTo(other.version)) 35 | } 36 | 37 | enum class Type { 38 | CLASS, 39 | GLOBAL_METHOD, 40 | EXTENSION_METHOD 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dto/src/main/kotlin/com/birbit/artifactfinder/dto/SearchResultDTO.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.dto 18 | 19 | import kotlinx.serialization.Serializable 20 | 21 | @Serializable 22 | data class SearchResultDTO( 23 | val score: Int, 24 | val pkg: String, 25 | val name: String, 26 | val groupId: String, 27 | val receiverName: String?, 28 | val artifactId: String, 29 | val version: String 30 | ) { 31 | val artifactDesc by lazy(LazyThreadSafetyMode.NONE) { 32 | "$groupId:$artifactId" 33 | } 34 | val nameDesc by lazy(LazyThreadSafetyMode.NONE) { 35 | if (receiverName == null) { 36 | name 37 | } else { 38 | "$receiverName.$name" 39 | } 40 | } 41 | 42 | fun qualifiedArtifact() = "$groupId:$artifactId:$version" 43 | } 44 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/ui/ButtonRenderer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin.ui 18 | 19 | import java.awt.Component 20 | import javax.swing.Icon 21 | import javax.swing.JButton 22 | import javax.swing.JTable 23 | import javax.swing.table.TableCellRenderer 24 | 25 | class ButtonRenderer( 26 | val text: String? = null, 27 | val icon: Icon? = null 28 | ) : TableCellRenderer { 29 | override fun getTableCellRendererComponent( 30 | table: JTable?, 31 | value: Any?, 32 | isSelected: Boolean, 33 | hasFocus: Boolean, 34 | row: Int, 35 | column: Int 36 | ): Component { 37 | return JButton(text ?: "", icon).apply { 38 | isOpaque = true 39 | isEnabled = true 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.birbit.build.PublishUtil 18 | 19 | apply plugin: 'java-library' 20 | apply plugin: 'kotlin' 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation libs.kotlinRuntime 24 | } 25 | 26 | sourceCompatibility = "8" 27 | targetCompatibility = "8" 28 | buildscript { 29 | ext.kotlin_version = '1.3.50' 30 | repositories { 31 | mavenCentral() 32 | } 33 | dependencies { 34 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 35 | } 36 | } 37 | repositories { 38 | mavenCentral() 39 | } 40 | compileKotlin { 41 | kotlinOptions { 42 | jvmTarget = "1.8" 43 | } 44 | } 45 | compileTestKotlin { 46 | kotlinOptions { 47 | jvmTarget = "1.8" 48 | } 49 | } 50 | PublishUtil.enablePublishing(project, "core") -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | 25 | # build cache 26 | - name: Cache 27 | uses: actions/cache@v1.1.2 28 | with: 29 | path: ~/.gradle/caches 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 31 | # An ordered list of keys to use for restoring the cache if no cache hit occurred for key 32 | restore-keys: | 33 | ${{ runner.os }}-gradle- 34 | - uses: actions/cache@v1.1.2 35 | with: 36 | path: ~/.gradle/wrapper 37 | key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} 38 | # Runs a single command using the runners shell 39 | - name: build and test 40 | run: ./gradlew spotlessCheck test 41 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/birbit/artifactfinder/vo/Artifactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.vo 18 | 19 | // from: https://cwiki.apache.org/confluence/display/MAVEN/Remote+repository+layout 20 | enum class Artifactory( 21 | val id: Int, // add an id so that we can persist them 22 | val baseUrl: String 23 | ) { 24 | GOOGLE( 25 | id = 0, 26 | baseUrl = "https://dl.google.com/dl/android/maven2/" 27 | ), 28 | MAVEN( 29 | id = 1, 30 | baseUrl = "https://repo1.maven.org/maven2/" 31 | ); 32 | 33 | companion object { 34 | fun getById(id: Int): Artifactory { 35 | val artifactory = values().firstOrNull { 36 | it.id == id 37 | } 38 | checkNotNull(artifactory) { 39 | "invalid artifact id $id" 40 | } 41 | return artifactory 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dto/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.birbit.build.PublishUtil 18 | 19 | apply plugin: 'java-library' 20 | apply plugin: 'kotlin' 21 | apply plugin: 'kotlinx-serialization' 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation libs.kotlinRuntime 25 | implementation libs.kotlinSerialization 26 | } 27 | 28 | sourceCompatibility = "8" 29 | targetCompatibility = "8" 30 | buildscript { 31 | ext.kotlin_version = '1.3.50' 32 | repositories { 33 | mavenCentral() 34 | } 35 | dependencies { 36 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 37 | } 38 | } 39 | repositories { 40 | mavenCentral() 41 | } 42 | compileKotlin { 43 | kotlinOptions { 44 | jvmTarget = "1.8" 45 | } 46 | } 47 | compileTestKotlin { 48 | kotlinOptions { 49 | jvmTarget = "1.8" 50 | } 51 | } 52 | PublishUtil.enablePublishing(project, "dto") -------------------------------------------------------------------------------- /external_sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "__INFO__" : "This file contains the list of non gmaven artifacts to index", 3 | "version": 1, 4 | "groups": [ 5 | { 6 | "groupId": "com.github.bumptech.glide", 7 | "artifactIds": [ 8 | "glide" 9 | ], 10 | "artifactory": "MAVEN" 11 | }, 12 | { 13 | "groupId": "com.google.dagger", 14 | "artifactIds": [ 15 | "dagger" 16 | ], 17 | "artifactory": "MAVEN" 18 | }, 19 | { 20 | "groupId": "com.squareup.retrofit2", 21 | "artifactIds": [ 22 | "adapter-guava", 23 | "adapter-java8", 24 | "adapter-rxjava", 25 | "adapter-rxjava2", 26 | "adapter-scala", 27 | "converter-gson", 28 | "converter-guava", 29 | "converter-jackson", 30 | "converter-java8", 31 | "converter-jaxb", 32 | "converter-moshi", 33 | "converter-protobuf", 34 | "converter-scalars", 35 | "converter-simplexml", 36 | "converter-wire", 37 | "retrofit", 38 | "retrofit-adapters", 39 | "retrofit-converters", 40 | "retrofit-mock" 41 | ], 42 | "artifactory": "MAVEN" 43 | }, 44 | { 45 | "groupId": "org.jetbrains.kotlinx", 46 | "artifactIds": [ 47 | "kotlinx-coroutines-core", 48 | "kotlinx-coroutines-android" 49 | ] 50 | }, 51 | { 52 | "groupId": "com.jakewharton.timber", 53 | "artifactIds": [ 54 | "timber" 55 | ] 56 | }, 57 | { 58 | "groupId": "com.dropbox.mobile.store", 59 | "artifactIds": [ 60 | "store4" 61 | ] 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /parser/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'java-library' 18 | apply plugin: 'kotlin' 19 | 20 | dependencies { 21 | implementation fileTree(dir: 'libs', include: ['*.jar']) 22 | implementation libs.kotlinRuntime 23 | implementation libs.asm 24 | implementation libs.kotlinMetadata 25 | implementation project(path: ":core", configuration: "default") 26 | testImplementation libs.junit 27 | testImplementation libs.truth 28 | testImplementation gradleTestKit() 29 | } 30 | 31 | sourceCompatibility = "7" 32 | targetCompatibility = "7" 33 | buildscript { 34 | ext.kotlin_version = '1.3.50' 35 | repositories { 36 | mavenCentral() 37 | } 38 | dependencies { 39 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 40 | } 41 | } 42 | repositories { 43 | mavenCentral() 44 | } 45 | compileKotlin { 46 | kotlinOptions { 47 | jvmTarget = "1.8" 48 | } 49 | } 50 | compileTestKotlin { 51 | kotlinOptions { 52 | jvmTarget = "1.8" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/birbit/artifactfinder/parser/vo/Info.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.vo 18 | 19 | /** 20 | * Parsed information about an artifact 21 | */ 22 | data class ParsedArtifactInfo( 23 | val classes: Set = emptySet(), 24 | val methods: Set = emptySet() 25 | ) 26 | 27 | /** 28 | * Holds the data about a class 29 | */ 30 | data class ParsedClassInfo( 31 | /** 32 | * Package for the class. Parts separated by '.' 33 | */ 34 | val pkg: String, 35 | /** 36 | * name of the class. Parts, if exists, separated by '$' 37 | */ 38 | val name: String 39 | ) 40 | 41 | /** 42 | * Holds the data about an extension methods in kotlin or global methods 43 | */ 44 | data class ParsedMethodInfo( 45 | val pkg: String, 46 | // null for global methods 47 | val receiver: ParsedClassInfo?, 48 | val name: String 49 | ) 50 | 51 | /** 52 | * Holds the information about an inner class 53 | */ 54 | data class InnerClassInfo( 55 | val parent: ParsedClassInfo, 56 | val classInfo: ParsedClassInfo 57 | ) 58 | -------------------------------------------------------------------------------- /webservice/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.birbit.build.PublishUtil 18 | 19 | apply plugin: 'java-library' 20 | apply plugin: 'kotlin' 21 | apply plugin: 'kotlinx-serialization' 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation libs.kotlinRuntime 26 | implementation libs.coroutines 27 | implementation libs.ktor 28 | implementation libs.kotlinSerialization 29 | implementation project(":model") 30 | implementation project(":dto") 31 | } 32 | 33 | sourceCompatibility = "8" 34 | targetCompatibility = "8" 35 | buildscript { 36 | ext.kotlin_version = '1.3.50' 37 | repositories { 38 | mavenCentral() 39 | } 40 | dependencies { 41 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 42 | } 43 | } 44 | repositories { 45 | mavenCentral() 46 | } 47 | compileKotlin { 48 | kotlinOptions { 49 | jvmTarget = "1.8" 50 | } 51 | } 52 | compileTestKotlin { 53 | kotlinOptions { 54 | jvmTarget = "1.8" 55 | } 56 | } 57 | PublishUtil.enablePublishing(project, "webservice") -------------------------------------------------------------------------------- /maven/src/main/kotlin/com/birbit/artifactfinder/maven/ExclusionFilters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.maven 18 | 19 | private fun SUPPORT(pkg: String) = pkg.startsWith("com.android.support") || 20 | pkg.startsWith("com.android.databinding") 21 | 22 | private fun OLD_ARCH(pkg: String) = pkg.startsWith("android.arch") 23 | 24 | private fun TOOLS(pkg: String) = pkg.startsWith("com.android.tools") || 25 | pkg.startsWith("tools.base") || pkg == "zipflinger" || pkg.startsWith("com.android.java.tools") 26 | 27 | private val PKG_EXCLUSION_FILTERS = listOf(::SUPPORT, ::OLD_ARCH, ::TOOLS) 28 | 29 | @Suppress("UNUSED_PARAMETER") 30 | private fun COMPILER(pkg: String, name: String) = name.contains("compiler") 31 | @Suppress("UNUSED_PARAMETER") 32 | private fun SIGNING(pkg: String, name: String) = name.contains("zipflinger") || name.contains("signflinger") 33 | private val ARTFACT_EXCLUSION_FILTERS = listOf(::COMPILER, ::SIGNING) 34 | internal fun shouldParsePackage(pkg: String) = PKG_EXCLUSION_FILTERS.none { it(pkg) } 35 | 36 | internal fun shouldParseArtfiact(pkg: String, artifactName: String) = 37 | ARTFACT_EXCLUSION_FILTERS.none { 38 | it(pkg, artifactName) 39 | } 40 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/ui/SearchResultTableModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin.ui 18 | 19 | import com.birbit.artifactfinder.ideplugin.SearchResultModel 20 | import javax.swing.table.DefaultTableModel 21 | 22 | class SearchResultTableModel : DefaultTableModel(COLUMNS, 0) { 23 | override fun isCellEditable(row: Int, column: Int): Boolean { 24 | return getColumnName(column) == COL_ADD_DEPENDENCY 25 | } 26 | 27 | fun setItems(items: List) { 28 | (rowCount - 1 downTo 0).forEach { 29 | removeRow(it) 30 | } 31 | items.forEach { item -> 32 | addRow( 33 | arrayOf( 34 | item.desc, item.artifactDesc, item 35 | ) 36 | ) 37 | } 38 | } 39 | 40 | companion object { 41 | private const val COL_CLASS = "Class / Method" 42 | private const val COL_ARTIFACT = "Artifact" 43 | const val COL_ADD_DEPENDENCY = "Add Dependency" 44 | 45 | val COLUMNS = arrayOf( 46 | COL_CLASS, 47 | COL_ARTIFACT, 48 | COL_ADD_DEPENDENCY 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /maven/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'java-library' 18 | apply plugin: 'kotlin' 19 | 20 | dependencies { 21 | implementation fileTree(dir: 'libs', include: ['*.jar']) 22 | implementation project(path : ":core", configuration: "default") 23 | implementation libs.kotlinRuntime 24 | implementation libs.retrofit 25 | implementation libs.jacksonKotlin 26 | implementation libs.jacksonXml 27 | implementation libs.coroutines 28 | implementation libs.okHttp 29 | implementation libs.okHttpLogger 30 | testImplementation libs.coroutinesTesting 31 | testImplementation libs.junit 32 | testImplementation libs.truth 33 | testImplementation libs.mockWebServer 34 | testImplementation gradleTestKit() 35 | } 36 | 37 | sourceCompatibility = "8" 38 | targetCompatibility = "8" 39 | buildscript { 40 | ext.kotlin_version = '1.3.50' 41 | repositories { 42 | mavenCentral() 43 | } 44 | dependencies { 45 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 46 | } 47 | } 48 | repositories { 49 | mavenCentral() 50 | } 51 | compileKotlin { 52 | kotlinOptions { 53 | jvmTarget = "1.8" 54 | } 55 | } 56 | compileTestKotlin { 57 | kotlinOptions { 58 | jvmTarget = "1.8" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/ClassNodeExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import com.birbit.artifactfinder.parser.vo.InnerClassInfo 20 | import org.objectweb.asm.Opcodes 21 | import org.objectweb.asm.tree.AnnotationNode 22 | import org.objectweb.asm.tree.ClassNode 23 | 24 | // extensions for asm ClassNode 25 | 26 | private fun List?.hasRestrictTo(): Boolean { 27 | return this?.any { 28 | it.desc == "Landroidx/annotation/RestrictTo;" 29 | } ?: false 30 | } 31 | 32 | internal fun ClassNode.isVisibleFromOutside(): Boolean { 33 | if (outerMethod != null) { 34 | return false 35 | } 36 | if (this.access.and(Opcodes.ACC_PUBLIC) == 0) { 37 | return false 38 | } 39 | if (invisibleAnnotations.hasRestrictTo() || visibleAnnotations.hasRestrictTo()) { 40 | return false 41 | } 42 | return true 43 | } 44 | 45 | internal fun ClassNode.isInnerClass() = this.innerClasses?.any { 46 | it.name == this.name 47 | } ?: false 48 | 49 | internal fun ClassNode.toClassInfo() = name.toClassInfo() 50 | 51 | internal fun ClassNode.toInnerClassInfo() = InnerClassInfo( 52 | parent = innerClasses.first { it.name == this.name }.outerName.toClassInfo(), 53 | classInfo = toClassInfo() 54 | ) 55 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Google, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Project-wide Gradle settings. 18 | # IDE (e.g. Android Studio) users: 19 | # Gradle settings configured through the IDE *will override* 20 | # any settings specified in this file. 21 | # For more details on how to configure your build environment visit 22 | # http://www.gradle.org/docs/current/userguide/build_environment.html 23 | # Specifies the JVM arguments used for the daemon process. 24 | # The setting is particularly useful for tweaking memory settings. 25 | org.gradle.jvmargs=-Xmx1536m 26 | # When configured, Gradle will run in incubating parallel mode. 27 | # This option should only be used with decoupled projects. More details, visit 28 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 29 | # org.gradle.parallel=true 30 | # AndroidX package structure to make it clearer which packages are bundled with the 31 | # Android operating system, and which are packaged with your app's APK 32 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 33 | android.useAndroidX=true 34 | # Automatically convert third-party libraries to use AndroidX 35 | android.enableJetifier=true 36 | # Kotlin code style for this project: "official" or "obsolete": 37 | kotlin.code.style=official 38 | # set while publishing via command line 39 | intellijPublishToken=NOT_SET 40 | -------------------------------------------------------------------------------- /model/src/test/kotlin/com/birbit/artifactfinder/model/ArtifactFinderDbTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | import com.birbit.artifactfinder.model.db.ArtifactFinderDb 20 | import com.google.common.truth.Truth 21 | import kotlinx.coroutines.ExperimentalCoroutinesApi 22 | import kotlinx.coroutines.test.TestCoroutineScope 23 | import kotlinx.coroutines.test.runBlockingTest 24 | import org.junit.Rule 25 | import org.junit.Test 26 | import org.junit.rules.TemporaryFolder 27 | import org.junit.runner.RunWith 28 | import org.junit.runners.JUnit4 29 | 30 | @ExperimentalCoroutinesApi 31 | @RunWith(JUnit4::class) 32 | class ArtifactFinderDbTest { 33 | private val scope = TestCoroutineScope() 34 | @Rule 35 | @JvmField 36 | val tmpFolder = TemporaryFolder() 37 | @Test 38 | fun checkLikePragmaOff() = scope.runBlockingTest { 39 | val db = ArtifactFinderDb(null) 40 | val result = db.query("SELECT 'a' LIKE 'A' AS result") { 41 | it.nextRow() 42 | it.requireInt("result") 43 | } 44 | Truth.assertThat(result).isEqualTo(0) 45 | } 46 | 47 | @Test 48 | fun checkWal() = scope.runBlockingTest { 49 | val db = ArtifactFinderDb(tmpFolder.newFile().absoluteFile.absolutePath) 50 | val result = db.query("PRAGMA journal_mode") { 51 | it.nextRow() 52 | it.requireString("journal_mode").toLowerCase() 53 | } 54 | Truth.assertThat(result).isEqualTo("wal") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/SearchArtifactIntentionAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.birbit.artifactfinder.ideplugin.ui.SearchArtifactPanelController 20 | import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction 21 | import com.intellij.openapi.editor.Editor 22 | import com.intellij.openapi.module.ModuleUtil 23 | import com.intellij.openapi.project.Project 24 | import com.intellij.psi.PsiElement 25 | import com.intellij.psi.PsiWhiteSpace 26 | import com.intellij.psi.util.PsiTreeUtil 27 | 28 | class SearchArtifactIntentionAction : PsiElementBaseIntentionAction() { 29 | init { 30 | text = "Search Artifact" 31 | } 32 | override fun getFamilyName() = "Search Artifact" 33 | 34 | override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean { 35 | return ModuleUtil.findModuleForPsiElement(element) != null 36 | } 37 | 38 | override fun invoke(project: Project, editor: Editor?, element: PsiElement) { 39 | val value = if (element is PsiWhiteSpace) { 40 | PsiTreeUtil.skipSiblingsBackward(element, PsiWhiteSpace::class.java) 41 | ?.text 42 | } else element.text 43 | SearchArtifactPanelController( 44 | project = project, 45 | module = ModuleUtil.findModuleForPsiElement(element), 46 | initialText = value?.split(" ")?.lastOrNull() 47 | ).buildAndShow() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /model/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.birbit.build.PublishUtil 18 | 19 | apply plugin: 'java-library' 20 | apply plugin: 'kotlin' 21 | apply plugin: 'kotlin-kapt' 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation project(path : ":core", configuration: "default") 25 | implementation libs.kotlinRuntime 26 | implementation libs.coroutines 27 | implementation libs.xerial 28 | testImplementation libs.coroutinesTesting 29 | testImplementation libs.junit 30 | testImplementation libs.truth 31 | testImplementation libs.guava 32 | } 33 | 34 | sourceCompatibility = "8" 35 | targetCompatibility = "8" 36 | buildscript { 37 | ext.kotlin_version = '1.3.50' 38 | repositories { 39 | mavenCentral() 40 | } 41 | dependencies { 42 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 43 | } 44 | } 45 | repositories { 46 | mavenCentral() 47 | } 48 | compileKotlin { 49 | kotlinOptions { 50 | jvmTarget = "1.8" 51 | } 52 | kotlinOptions { 53 | // YOLO 54 | freeCompilerArgs += "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi" 55 | } 56 | } 57 | compileTestKotlin { 58 | kotlinOptions { 59 | jvmTarget = "1.8" 60 | } 61 | kotlinOptions { 62 | // YOLO 63 | freeCompilerArgs += "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi" 64 | } 65 | } 66 | PublishUtil.enablePublishing(project, "model") 67 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/ExclusionFilters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("FunctionName") 18 | 19 | package com.birbit.artifactfinder.parser 20 | 21 | import com.birbit.artifactfinder.parser.vo.ParsedClassInfo 22 | import com.birbit.artifactfinder.parser.vo.ParsedMethodInfo 23 | 24 | // exclude filters for class names 25 | 26 | typealias ExclusionFilter = (ParsedClassInfo) -> Boolean 27 | 28 | typealias MethodExclusionFilter = (ParsedMethodInfo) -> Boolean 29 | 30 | private fun BUILD_CONFIG(classInfo: ParsedClassInfo) = classInfo.name == "BuildConfig" || 31 | classInfo.name == "R" 32 | private fun LOWERCASE(classInfo: ParsedClassInfo) = classInfo.name[0].isLowerCase() 33 | private fun COMPANION(classInfo: ParsedClassInfo) = classInfo.name.endsWith("Companion") 34 | private fun DAGGER_COMPONENT(classInfo: ParsedClassInfo) = classInfo.name.startsWith("Dagger") && 35 | classInfo.name.endsWith("Component") 36 | private fun DEFAULT_IMPL(classInfo: ParsedClassInfo) = classInfo.name.endsWith("DefaultImpls") 37 | private fun INTERNAL_PKG(classInfo: ParsedClassInfo) = classInfo.pkg.contains("internal") 38 | 39 | private fun INTERNAL_METHOD_PKG(methodInfo: ParsedMethodInfo) = methodInfo.pkg.contains("internal") 40 | 41 | val EXCLUSION_FILTERS = listOf( 42 | ::BUILD_CONFIG, ::LOWERCASE, ::COMPANION, ::DAGGER_COMPONENT, ::DEFAULT_IMPL, ::INTERNAL_PKG 43 | ) 44 | 45 | val METHOD_EXCLUSION_FILTERS = listOf( 46 | ::INTERNAL_METHOD_PKG 47 | ) 48 | -------------------------------------------------------------------------------- /artifactfinder/src/main/kotlin/com/birbit/artifactfinder/ExternalSources.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder 18 | 19 | import com.birbit.artifactfinder.vo.Artifactory.MAVEN 20 | 21 | // https://repo.maven.apache.org/maven2/ does not provide maven-metadata anymore :/ 22 | private val RETROFIT = listOf( 23 | "adapter-guava", 24 | "adapter-java8", 25 | "adapter-rxjava", 26 | "adapter-rxjava2", 27 | "adapter-scala", 28 | "converter-gson", 29 | "converter-guava", 30 | "converter-jackson", 31 | "converter-java8", 32 | "converter-jaxb", 33 | "converter-moshi", 34 | "converter-protobuf", 35 | "converter-scalars", 36 | "converter-simplexml", 37 | "converter-wire", 38 | "retrofit", 39 | "retrofit-adapters", 40 | "retrofit-converters", 41 | "retrofit-mock" 42 | ).map { 43 | ArtifactSource( 44 | groupId = "com.squareup.retrofit2", 45 | artifactId = it, 46 | artifactory = MAVEN 47 | ) 48 | } 49 | // TODO move to an external file that can be fetched 50 | val EXTERNAL_SOURCES = FetcherSource( 51 | groups = emptyList(), 52 | artifacts = listOf( 53 | ArtifactSource( 54 | groupId = "com.github.bumptech.glide", 55 | artifactId = "glide", 56 | artifactory = MAVEN 57 | ), 58 | ArtifactSource( 59 | groupId = "com.google.dagger", 60 | artifactId = "dagger", 61 | artifactory = MAVEN 62 | ) 63 | ) + RETROFIT 64 | 65 | ) 66 | -------------------------------------------------------------------------------- /artifactfinder/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'java-library' 18 | apply plugin: 'kotlin' 19 | apply plugin: 'application' 20 | apply plugin: 'com.github.johnrengelman.shadow' 21 | apply plugin: 'kotlinx-serialization' 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation libs.kotlinRuntime 25 | implementation libs.coroutines 26 | implementation libs.kotlinSerialization 27 | implementation project(":model") 28 | implementation project(path : ":core", configuration: "default") 29 | implementation project(":parser") 30 | implementation project(":maven") 31 | implementation libs.okHttp 32 | implementation libs.okHttpLogger 33 | testImplementation libs.junit 34 | testImplementation libs.truth 35 | } 36 | 37 | sourceCompatibility = "8" 38 | targetCompatibility = "8" 39 | buildscript { 40 | ext.kotlin_version = '1.3.50' 41 | repositories { 42 | mavenCentral() 43 | } 44 | dependencies { 45 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 46 | } 47 | } 48 | repositories { 49 | mavenCentral() 50 | } 51 | compileKotlin { 52 | kotlinOptions { 53 | jvmTarget = "1.8" 54 | } 55 | kotlinOptions { 56 | freeCompilerArgs += "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi" 57 | } 58 | } 59 | compileTestKotlin { 60 | kotlinOptions { 61 | jvmTarget = "1.8" 62 | } 63 | } 64 | 65 | shadowJar { 66 | mainClassName = 'com.birbit.artifactfinder.MainKt' 67 | } -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/db/SharedObjectPool.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model.db 18 | 19 | import kotlinx.coroutines.channels.Channel 20 | import kotlinx.coroutines.sync.Mutex 21 | import kotlinx.coroutines.sync.withLock 22 | 23 | class SharedObjectPool( 24 | private val limit: Int, 25 | private val creator: suspend () -> T 26 | ) { 27 | init { 28 | check(limit > 0) { 29 | "shared object pool limit cannot be less than 1, c'mon" 30 | } 31 | } 32 | 33 | val createdCnt 34 | get() = counter 35 | private val free = Channel(capacity = Channel.UNLIMITED) 36 | private val lock = Mutex() 37 | private var counter = 0 38 | private suspend fun acquire(): T { 39 | val existing = free.poll() 40 | if (existing != null) { 41 | return existing 42 | } 43 | // nothing ready, trigger a create and then wait forever 44 | val create = lock.withLock { 45 | if (counter < limit) { 46 | counter++ 47 | true 48 | } else { 49 | false 50 | } 51 | } 52 | if (create) { 53 | free.send(creator()) 54 | } 55 | 56 | // now wait forever 57 | return free.receive() 58 | } 59 | 60 | private suspend fun release(t: T) { 61 | free.send(t) 62 | } 63 | 64 | suspend fun use( 65 | block: suspend (T) -> R 66 | ): R { 67 | val item = acquire() 68 | return try { 69 | block(item) 70 | } finally { 71 | release(item) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/SearchArtifactApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.birbit.artifactfinder.dto.SearchResponseDTO 20 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.withContext 23 | import kotlinx.serialization.json.Json 24 | import okhttp3.MediaType 25 | import retrofit2.Retrofit 26 | import retrofit2.http.GET 27 | import retrofit2.http.Query 28 | 29 | class SearchArtifactModel { 30 | private val api = SearchArtifactApi.build() 31 | 32 | suspend fun query(query: String) = withContext(Dispatchers.IO) { 33 | SearchResultModel.fromSearchResponse(api.queryResults(query)) 34 | } 35 | } 36 | 37 | private interface SearchArtifactApi { 38 | @GET("searchArtifact") 39 | suspend fun queryResults( 40 | @Query("query") query: String, 41 | @Query("includeClasses") includeClasses: Boolean = true, 42 | @Query("includeExtensionMethods") includeExtensionMethods: Boolean = true, 43 | @Query("includeGlobalMethods") includeGlobalMethods: Boolean = true, 44 | @Query("version") version: Int = 1 45 | ): SearchResponseDTO 46 | 47 | companion object { 48 | fun build(): SearchArtifactApi { 49 | val contentType = MediaType.get("application/json") 50 | 51 | return Retrofit.Builder() 52 | .baseUrl("https://birbit.com/") 53 | .addConverterFactory(Json.nonstrict.asConverterFactory(contentType)) 54 | .build().create(SearchArtifactApi::class.java) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/db/DbDriver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model.db 18 | 19 | import java.io.Closeable 20 | 21 | interface QueryResult : Closeable { 22 | val columnNames: Set 23 | fun hasColumn(name: String) = columnNames.contains(name) 24 | fun nextRow(): Boolean 25 | fun requireInt(columnName: String): Int 26 | fun requireLong(columnName: String): Long 27 | fun requireString(columnName: String): String 28 | fun getString(columnName: String): String? 29 | fun requireBoolean(columnName: String): Boolean = requireInt(columnName) == 1 30 | fun asSequence() = generateSequence { 31 | if (nextRow()) { 32 | this@QueryResult 33 | } else { 34 | close() 35 | null 36 | } 37 | } 38 | } 39 | 40 | interface Query { 41 | fun bindInt(index: Int, value: Int) 42 | fun bindLong(index: Int, value: Long) 43 | fun bindString(index: Int, value: String) 44 | fun bindNull(index: Int) 45 | fun bindBoolean(index: Int, value: Boolean) = bindInt(index, if (value) 1 else 0) 46 | suspend fun query(block: (QueryResult) -> T): T 47 | } 48 | 49 | interface WriteQuery : Query { 50 | suspend fun exec(): Boolean 51 | suspend fun execForLastRowId(block: (Long) -> T): T 52 | suspend fun execForLastRowId(): Long = execForLastRowId { it } 53 | } 54 | 55 | interface DbDriver { 56 | fun prepareRead(sql: String): Query 57 | } 58 | 59 | interface WritableDbDriver : DbDriver { 60 | suspend fun exec(sql: String) = prepareWrite(sql).exec() 61 | fun prepareWrite(sql: String): WriteQuery 62 | suspend fun withTransaction(block: suspend () -> T): T 63 | } 64 | -------------------------------------------------------------------------------- /ideplugin/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'org.jetbrains.intellij' 18 | apply plugin: 'org.jetbrains.kotlin.jvm' 19 | apply plugin: 'kotlin' 20 | apply plugin: 'kotlinx-serialization' 21 | 22 | dependencies { 23 | compile libs.kotlinRuntime 24 | compile libs.coroutinesSwing 25 | compile libs.coroutines // keep these compile to avoid conflicts via the IJ plugin 26 | implementation libs.retrofit 27 | implementation libs.okHttp // specify this to get the Platform bugfix https://github.com/square/okhttp/pull/6055 28 | implementation libs.kotlinSerialization 29 | implementation libs.retrofitKotlinSerialization 30 | implementation project(path : ":dto", configuration: "default") 31 | 32 | } 33 | intellij { 34 | updateSinceUntilBuild false 35 | version "IC-2020.1.1" 36 | pluginName = 'Artifact Finder' 37 | plugins = [ 38 | "Kotlin", "IntelliLang", "android", "java" 39 | ] 40 | // Uncomment to test against Android Studio 41 | intellij.alternativeIdePath = '/home/yboyar/app/as-4.1/android-studio' 42 | } 43 | 44 | patchPluginXml { 45 | changeNotes """ 46 | Initial version of Artifact Finder""" 47 | } 48 | 49 | publishPlugin { 50 | token intellijPublishToken 51 | } 52 | 53 | targetCompatibility = "1.8" 54 | sourceCompatibility = "1.8" 55 | 56 | compileKotlin { 57 | kotlinOptions { 58 | jvmTarget = "1.8" 59 | } 60 | kotlinOptions { 61 | // YOLO 62 | freeCompilerArgs += "-Xuse-experimental=kotlinx.serialization.UnstableDefault" 63 | freeCompilerArgs += "-Xuse-experimental=kotlinx.coroutines.FlowPreview" 64 | freeCompilerArgs += "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi" 65 | } 66 | } -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/SearchResultModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.birbit.artifactfinder.dto.SearchResponseDTO 20 | 21 | /** 22 | * processes search results for UI representation 23 | */ 24 | class SearchResultModel private constructor( 25 | val latestVersion: Int, 26 | val items: List 27 | ) { 28 | 29 | data class SearchResult( 30 | val desc: String, 31 | val artifactDesc: String, 32 | val groupId: String, 33 | val artifactId: String, 34 | val versions: MutableList = mutableListOf() 35 | ) { 36 | fun qualifiedArtifact(version: String) = "$groupId:$artifactId:$version" 37 | } 38 | 39 | companion object { 40 | val EMPTY = SearchResultModel(0, emptyList()) 41 | fun fromSearchResponse(response: SearchResponseDTO): SearchResultModel { 42 | val items = LinkedHashMap() 43 | response.results.forEach { rawResult -> 44 | val result = items.getOrPut(rawResult.nameDesc + " " + rawResult.artifactDesc) { 45 | SearchResult( 46 | desc = rawResult.nameDesc, 47 | artifactDesc = rawResult.artifactDesc, 48 | groupId = rawResult.groupId, 49 | artifactId = rawResult.artifactId 50 | ) 51 | } 52 | result.versions.add(rawResult.version) 53 | } 54 | return SearchResultModel( 55 | latestVersion = response.latestVersion, 56 | items = items.values.toList() 57 | ) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ideplugin/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | com.birbit.artifactfinder 19 | Artifact Finder 20 | 0.0.3 21 | 22 | An Android Studio plugin which allows you to find Maven artifacts by class name, kotlin global 23 | methods or kotlin extension functions. 24 | 25 | Yigit Boyar 26 | 27 | com.intellij.modules.platform 28 | 29 | 30 | 31 | 32 | org.jetbrains.kotlin 33 | org.jetbrains.android 34 | com.intellij.modules.java 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /artifactfinder/src/test/kotlin/com/birbit/artifactfinder/PlaygroundTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder 18 | 19 | import com.birbit.artifactfinder.maven.MavenFetcher 20 | import com.birbit.artifactfinder.maven.vo.ArtifactType 21 | import com.birbit.artifactfinder.model.ArtifactFinderModel 22 | import com.birbit.artifactfinder.parser.Aar 23 | import com.birbit.artifactfinder.parser.CodeSourceParser 24 | import com.birbit.artifactfinder.parser.Jar 25 | import com.birbit.artifactfinder.vo.Artifactory 26 | import java.io.File 27 | import kotlinx.coroutines.runBlocking 28 | import org.junit.Test 29 | import org.junit.runner.RunWith 30 | import org.junit.runners.JUnit4 31 | 32 | @RunWith(JUnit4::class) 33 | class PlaygroundTest { 34 | 35 | @Test 36 | fun createDb() = runBlocking { 37 | if (true) return@runBlocking 38 | val targetFile = File("/home/yboyar/src/ArtifactFinder/tmp/artifacts.db") 39 | targetFile.delete() 40 | val model = ArtifactFinderModel(targetFile) 41 | model.findNextPendingArtifact(emptyList()) 42 | } 43 | 44 | @Test 45 | fun playground() = runBlocking { 46 | if (true) return@runBlocking 47 | val fetcher = MavenFetcher( 48 | artifactory = Artifactory.MAVEN 49 | ) 50 | val artifactInfo = fetcher.fetchArtifact( 51 | groupId = "org.jetbrains.kotlinx", 52 | artifactId = "kotlinx-coroutines-core", 53 | version = "1.3.4" 54 | ) 55 | val codeSource = when (artifactInfo.type) { 56 | ArtifactType.AAR -> Aar(artifactInfo.inputStream) 57 | ArtifactType.JAR -> Jar(artifactInfo.inputStream) 58 | } 59 | val src = CodeSourceParser.parse(codeSource) 60 | println(src) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /maven/src/main/kotlin/com/birbit/artifactfinder/maven/JacksonConverterFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.maven 18 | 19 | import com.fasterxml.jackson.databind.DeserializationFeature 20 | import com.fasterxml.jackson.databind.ObjectMapper 21 | import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule 22 | import com.fasterxml.jackson.dataformat.xml.XmlMapper 23 | import com.fasterxml.jackson.module.kotlin.KotlinModule 24 | import java.lang.reflect.ParameterizedType 25 | import java.lang.reflect.Type 26 | import okhttp3.ResponseBody 27 | import retrofit2.Converter 28 | import retrofit2.Retrofit 29 | 30 | internal class JacksonConverterFactory : Converter.Factory() { 31 | private val mapper = XmlMapper().registerModule(KotlinModule()).registerModule( 32 | JacksonXmlModule() 33 | ) 34 | .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false) 35 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 36 | 37 | override fun responseBodyConverter( 38 | type: Type, 39 | annotations: Array, 40 | retrofit: Retrofit 41 | ): Converter? { 42 | return when (type) { 43 | is Class<*> -> JacksonXmlBodyConverter(mapper, type) 44 | is ParameterizedType -> JacksonXmlBodyConverter(mapper, type.rawType as Class<*>) 45 | else -> null 46 | } 47 | } 48 | 49 | class JacksonXmlBodyConverter( 50 | private val mapper: ObjectMapper, 51 | private val klass: Class 52 | ) : Converter { 53 | override fun convert(value: ResponseBody?): T? { 54 | return value?.let { 55 | mapper.readValue(it.charStream(), klass) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/Version.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | import java.util.regex.Matcher 20 | import java.util.regex.Pattern 21 | 22 | data class Version( 23 | val major: Int, 24 | val minor: Int?, 25 | val patch: Int?, 26 | val extra: String? 27 | ) : Comparable { 28 | override fun compareTo(other: Version): Int = compareValuesBy(this, other, 29 | { it.major }, 30 | { it.minor }, 31 | { it.patch }, 32 | { it.extra == null }, // False (no extra) sorts above true (has extra) 33 | { it.extra } // gradle uses lexicographic ordering 34 | ) 35 | 36 | val isBeta = extra?.toLowerCase()?.startsWith("-beta") ?: false 37 | val isAlpha = extra?.toLowerCase()?.startsWith("-alpha") ?: false 38 | val isRc = extra?.toLowerCase()?.startsWith("-rc") ?: false 39 | val isRelease = extra == null 40 | 41 | override fun toString(): String { 42 | return "$major.$minor.$patch${extra ?: ""}" 43 | } 44 | 45 | companion object { 46 | private val VERSION_REGEX = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$") 47 | fun fromString(input: String): Version? { 48 | val matcher = VERSION_REGEX.matcher(input) 49 | if (!matcher.matches()) { 50 | return null 51 | } 52 | 53 | return Version( 54 | major = matcher.group(1).toInt(), 55 | minor = matcher.safeGet(2)?.toInt(), 56 | patch = matcher.safeGet(3)?.toInt(), 57 | extra = matcher.safeGet(4) 58 | ) 59 | } 60 | 61 | private fun Matcher.safeGet(index: Int): String? { 62 | return if (index <= groupCount()) { 63 | group(index) 64 | } else { 65 | null 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web-client/public/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 33 | 34 | 43 | Search Artifacts 44 | 45 | 46 | 47 |
48 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/KotlinMetadataExtractor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import kotlinx.metadata.jvm.KotlinClassHeader 20 | import kotlinx.metadata.jvm.KotlinClassMetadata 21 | import org.objectweb.asm.tree.AnnotationNode 22 | 23 | /** 24 | * Extracts KotlinClassMetadata from a Metadata annotation 25 | */ 26 | @Suppress("UNCHECKED_CAST") 27 | internal fun AnnotationNode.extractKotlinMetadata(): KotlinClassMetadata? { 28 | // TODO need to check if this is kotlin metada 29 | if (desc != "Lkotlin/Metadata;") return null 30 | 31 | val values = values ?: return null 32 | var k: Int? = null 33 | var mv: ArrayList? = null 34 | var bv: ArrayList? = null 35 | var d1: ArrayList? = null 36 | var d2: ArrayList? = null 37 | 38 | var key: String? = null 39 | values.forEach { 40 | if (key == null) { 41 | key = it as String 42 | } else { 43 | when (key) { 44 | "k" -> k = it as Int 45 | "mv" -> mv = it as ArrayList 46 | "bv" -> bv = it as ArrayList 47 | "d1" -> d1 = it as ArrayList 48 | "d2" -> d2 = it as ArrayList 49 | else -> { 50 | println("unknown key: $key from ${this.desc}") 51 | } 52 | } 53 | key = null 54 | } 55 | } 56 | if (k == null || mv == null || bv == null || d1 == null || d2 == null) { 57 | return null 58 | } 59 | 60 | val header = KotlinClassHeader( 61 | kind = k!!, 62 | metadataVersion = mv!!.toIntArray(), 63 | bytecodeVersion = bv!!.toIntArray(), 64 | data1 = d1!!.toTypedArray(), 65 | data2 = d2!!.toTypedArray(), 66 | extraString = null, 67 | extraInt = null, 68 | packageName = null 69 | ) 70 | return KotlinClassMetadata.read(header) 71 | } 72 | -------------------------------------------------------------------------------- /parser/src/test/kotlin/com/birbit/artifactfinder/parser/testapk/ArtifactInfoSubject.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.testapk 18 | 19 | import com.birbit.artifactfinder.parser.vo.ParsedArtifactInfo 20 | import com.birbit.artifactfinder.parser.vo.ParsedClassInfo 21 | import com.birbit.artifactfinder.parser.vo.ParsedMethodInfo 22 | import com.google.common.truth.FailureMetadata 23 | import com.google.common.truth.Subject 24 | import com.google.common.truth.Subject.Factory 25 | import com.google.common.truth.Truth 26 | 27 | class ArtifactInfoSubject( 28 | metadata: FailureMetadata?, 29 | private val actual: ParsedArtifactInfo 30 | ) : Subject(metadata, actual) { 31 | fun hasExactClasses(expected: Collection): ArtifactInfoSubject { 32 | check("classes()").that(actual.classes).isEqualTo(expected.toSet()) 33 | return this 34 | } 35 | 36 | fun hasClasses(expected: Collection): ArtifactInfoSubject { 37 | check("some classes()").that(actual.classes).containsAtLeastElementsIn(expected) 38 | return this 39 | } 40 | 41 | fun hasExactGlobalMethods(expected: Collection): ArtifactInfoSubject { 42 | check("methods()").that(actual.methods.filter { 43 | it.receiver == null 44 | }.toSet()).isEqualTo(expected.toSet()) 45 | return this 46 | } 47 | 48 | fun hasExactExtensionMethods(expected: Collection): ArtifactInfoSubject { 49 | check("extensionMethods()").that(actual.methods.filter { 50 | it.receiver != null 51 | }.toSet()).isEqualTo(expected.toSet()) 52 | return this 53 | } 54 | 55 | companion object { 56 | @JvmStatic 57 | fun artifactInfo(): Subject.Factory { 58 | return Factory { metadata, actual -> ArtifactInfoSubject(metadata, actual) } 59 | } 60 | 61 | fun assertThat(artifactInfo: ParsedArtifactInfo): ArtifactInfoSubject { 62 | return Truth.assertAbout(artifactInfo()) 63 | .that(artifactInfo) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Artifact Finder 2 | Find maven artifacts by class names, extension methods or global methods. 3 | 4 | ## Disclaimer 5 | This is **not** an official Google product. 6 | 7 | ## Why? 8 | Did you ever start a new project and struggle to find the maven coordinates for 9 | your favorite library? You know the class name you were using, but *what is the 10 | maven coordinates* or *what is the latest version* ? 11 | I personally did a lot so ended up creating this project. 12 | 13 | ## What is it? 14 | This project has two main pieces: 15 | * A java application `/artifactfinder` that can index maven artifacts (`jar`, `aar`). 16 | This index includes a reverse mapping from class / method names to related artifacts. 17 | 18 | * An Android Studio plugin which allows you to search this index and easily add 19 | dependencies to your project. 20 | 21 | ![](sample.gif) 22 | 23 | ## How do I add artifacts to be indexed? 24 | Right now, only Google Maven and a few other Maven Central artifacts are indexed. 25 | 26 | Eventually, there will be a json file that you can modify to add more artifacts 27 | and send PRs but for now, external artifacts (non Google Maven) are listed 28 | [here](artifactfinder/src/main/kotlin/com/birbit/artifactfinder/ExternalSources.kt) 29 | 30 | ## What is being indexed? 31 | It currently indexes only `aar` and `jar` files in artifacts. 32 | The indexer respects annotations like `RestrictTo` or skips any non-public class 33 | (`internal`, `private` etc). Since `ktx` artifacts are a common case for AndroidX 34 | artifacts, it also indexes extension methods and global kotlin methods. 35 | 36 | There is also a bunch of hand written filters to avoid unnecessary common classes 37 | (e.g. `R`, `BuildConfig`, `Dagger*Component`). It also tries to avoid any non-android 38 | artifacts (e.g. Android Gradle Plugin). 39 | 40 | ## Where is the index? 41 | Right now I host it on my personal droplet. There is a cron job that checks repositories 42 | for updates every 15 minutes and also tries to download new artifacts. 43 | 44 | You can directly query it in the following URL: 45 | 46 | `curl -H "Accept: application/json" "https://birbit.com/searchArtifact?query=Room"` 47 | 48 | Copyright: 49 | 50 | Copyright 2019 Google LLC 51 | 52 | Licensed under the Apache License, Version 2.0 (the "License"); 53 | you may not use this file except in compliance with the License. 54 | You may obtain a copy of the License at 55 | 56 | https://www.apache.org/licenses/LICENSE-2.0 57 | 58 | Unless required by applicable law or agreed to in writing, software 59 | distributed under the License is distributed on an "AS IS" BASIS, 60 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 61 | See the License for the specific language governing permissions and 62 | limitations under the License. 63 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/BuildDependencyHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin 18 | 19 | import com.android.ide.common.repository.GradleCoordinate 20 | import com.android.tools.idea.gradle.dependencies.GradleDependencyManager 21 | import com.android.tools.idea.projectsystem.getModuleSystem 22 | import com.intellij.openapi.module.Module 23 | 24 | class BuildDependencyHandler(private val module: Module) { 25 | fun addMavenDependency( 26 | coordinate: String, 27 | onSuccess: () -> Unit, 28 | onError: (msg: String) -> Unit 29 | ) { 30 | val parsedCoordinate = GradleCoordinate.parseCoordinateString(coordinate) 31 | val parsedLatestCoordinate = GradleCoordinate.parseCoordinateString( 32 | parsedCoordinate.groupId + ":" + parsedCoordinate.artifactId + ":+" 33 | ) 34 | val gradle = GradleDependencyManager.getInstance(module.project) 35 | val existing = module.getModuleSystem().getResolvedDependency(parsedLatestCoordinate) 36 | val errorMsg: String? = if (existing == null) { 37 | if (gradle.addDependenciesAndSync(module, listOf(parsedCoordinate), null)) { 38 | null 39 | } else { 40 | GENERIC_ERROR 41 | } 42 | } else { 43 | val cmp = existing.version.compareTo(parsedCoordinate.version) 44 | if (cmp < 0) { 45 | val updated = gradle.updateLibrariesToVersion( 46 | module, 47 | listOf(parsedCoordinate), 48 | null 49 | ) 50 | if (updated) { 51 | null 52 | } else { 53 | GENERIC_ERROR 54 | } 55 | } else { 56 | "A newer version (${existing.version}) already exists" 57 | } 58 | } 59 | if (errorMsg == null) { 60 | onSuccess() 61 | } else { 62 | onError(errorMsg) 63 | } 64 | } 65 | 66 | companion object { 67 | private const val GENERIC_ERROR = "Gradle Build model cannot be found or is not ready for modifications" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/ArtifactDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | internal interface ArtifactDao { 20 | suspend fun insertArtifact(artifact: Artifact): Long 21 | 22 | suspend fun findArtifact( 23 | groupId: String, 24 | artifactId: String, 25 | version: Version 26 | ): Artifact? 27 | 28 | suspend fun insertClassRecord(classRecord: ClassRecord): Long 29 | 30 | suspend fun insertClassLookup(classLookup: ClassLookup) 31 | 32 | suspend fun searchClasses(query: String): List 33 | 34 | suspend fun searchMethods(query: String, methodSearchType: MethodSearchType): List 35 | 36 | suspend fun allLookups(): List 37 | 38 | suspend fun insertPendingArtifact(pendingArtifact: PendingArtifact) 39 | 40 | suspend fun findPendingArtifact( 41 | groupId: String, 42 | artifactId: String, 43 | version: Version 44 | ): Artifact? 45 | 46 | suspend fun incrementPendingArtifactRetry(id: Long) 47 | 48 | suspend fun markPendingArtifactFetched(id: Long) 49 | 50 | suspend fun findNextPendingArtifact(excludeIds: List): PendingArtifact? 51 | 52 | suspend fun deleteArtifact(artifact: Artifact) 53 | 54 | suspend fun deleteClassRecord(classRecord: ClassRecord) 55 | 56 | suspend fun insertMethodRecord(methodRecord: MethodRecord): Long 57 | 58 | suspend fun insertMethodLookup(methodLookup: MethodLookup) 59 | 60 | enum class MethodSearchType { 61 | ALL_METHOD, 62 | ONLY_EXTENSIONS, 63 | ONLY_GLOBAL; 64 | companion object { 65 | fun get( 66 | includeGlobal: Boolean, 67 | includeExtension: Boolean 68 | ): MethodSearchType? { 69 | return if (includeGlobal) { 70 | if (includeExtension) { 71 | ALL_METHOD 72 | } else { 73 | ONLY_GLOBAL 74 | } 75 | } else if (includeExtension) { 76 | ONLY_EXTENSIONS 77 | } else { 78 | null 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /parser/src/test/kotlin/com/birbit/artifactfinder/parser/testapk/templates/GradleFiles.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.testapk.templates 18 | 19 | fun settingsGradle() = """ 20 | include ':lib' 21 | """.trimIndent() 22 | 23 | fun moduleBuildFile( 24 | hasKotlin: Boolean 25 | ): String { 26 | val applyKotlin = if (hasKotlin) { 27 | "apply plugin: 'kotlin-android'" 28 | } else { 29 | "" 30 | } 31 | return """ 32 | apply plugin: 'com.android.library' 33 | $applyKotlin 34 | 35 | android { 36 | compileSdkVersion 29 37 | buildToolsVersion "29.0.2" 38 | defaultConfig { 39 | minSdkVersion 22 40 | targetSdkVersion 29 41 | versionCode 1 42 | versionName "1.0" 43 | } 44 | } 45 | 46 | dependencies { 47 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${"$"}kotlin_version" 48 | implementation "androidx.annotation:annotation:1.1.0" 49 | } 50 | 51 | """.trimIndent() 52 | } 53 | 54 | fun mainBuildFile( 55 | agpVersion: String, 56 | kotlinVersion: String 57 | ) = """ 58 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 59 | 60 | buildscript { 61 | ext.kotlin_version = '$kotlinVersion' 62 | repositories { 63 | google() 64 | jcenter() 65 | 66 | } 67 | dependencies { 68 | classpath 'com.android.tools.build:gradle:$agpVersion' 69 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 70 | } 71 | } 72 | 73 | allprojects { 74 | repositories { 75 | google() 76 | jcenter() 77 | } 78 | } 79 | 80 | task clean(type: Delete) { 81 | delete rootProject.buildDir 82 | } 83 | 84 | """.trimIndent() 85 | 86 | fun gradlePropsFile(version: String) = """ 87 | distributionBase=GRADLE_USER_HOME 88 | distributionPath=wrapper/dists 89 | zipStoreBase=GRADLE_USER_HOME 90 | zipStorePath=wrapper/dists 91 | distributionUrl=https\://services.gradle.org/distributions/gradle-$version-bin.zip 92 | """.trimIndent() 93 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/ResultSorter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model 18 | 19 | import java.util.* 20 | 21 | object ResultSorter { 22 | fun sort(query: String, results: List): List { 23 | val lowercased = query.toLowerCase(Locale.US) 24 | results.forEach { 25 | it.score = score( 26 | original = query, 27 | lowercased = lowercased, 28 | record = it 29 | ) 30 | } 31 | return results.sorted() 32 | } 33 | 34 | private fun score( 35 | original: String, 36 | lowercased: String, 37 | record: SearchRecord 38 | ): Int { 39 | var score = MAX_SCORE 40 | if (original == record.name) { 41 | return score 42 | } 43 | score -= 10 44 | val lowercasedClassName = record.name.toLowerCase(Locale.US) 45 | if (lowercased == lowercasedClassName) { 46 | return score 47 | } 48 | score -= similarityPenalty( 49 | original, 50 | lowercased, 51 | className = record.name, 52 | lowercasedClassName = lowercasedClassName 53 | ) 54 | return score 55 | } 56 | 57 | private fun similarityPenalty( 58 | original: String, 59 | lowercased: String, 60 | className: String, 61 | lowercasedClassName: String 62 | ): Int { 63 | var penalty = 0 64 | val startPos = lowercasedClassName.indexOf(lowercased) 65 | if (startPos < 0) { // safe guard, should not happen in regular search 66 | return 50 + className.length 67 | } 68 | if (startPos != 0 && className[startPos - 1] != '$') { 69 | penalty += startPos 70 | } 71 | 72 | // ever lowercase / uppercase mismatch is also minus 1 73 | for (offset in 0 until original.length) { 74 | if (original[offset] != className[startPos + offset]) { 75 | penalty++ 76 | } 77 | } 78 | var endPos = startPos + lowercased.length 79 | while (endPos < className.length) { 80 | penalty += 1 81 | endPos++ 82 | } 83 | 84 | return penalty 85 | } 86 | 87 | private val MAX_SCORE = 100 88 | } 89 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/KotlinMetadataExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import com.birbit.artifactfinder.parser.vo.InnerClassInfo 20 | import com.birbit.artifactfinder.parser.vo.ParsedMethodInfo 21 | import kotlinx.metadata.Flag 22 | import kotlinx.metadata.Flags 23 | import kotlinx.metadata.KmClass 24 | import kotlinx.metadata.KmClassifier 25 | import kotlinx.metadata.KmFunction 26 | import kotlinx.metadata.KmType 27 | 28 | // extensions for KotlinMetadata classes 29 | 30 | private val unwantedFlags = arrayOf( 31 | Flag.IS_INTERNAL, 32 | Flag.IS_LOCAL, 33 | Flag.IS_PRIVATE_TO_THIS, 34 | Flag.IS_PROTECTED, 35 | Flag.IS_LOCAL, 36 | Flag.IS_PRIVATE 37 | ) 38 | 39 | private fun Flags.isVisibleFromOutsideFlags() = unwantedFlags.none { 40 | it.invoke(this) 41 | } 42 | 43 | internal fun KmType.toClassInfo() = this.classifier.let { 44 | when (it) { 45 | is KmClassifier.Class -> it.name.toClassInfo() 46 | is KmClassifier.TypeAlias -> it.name.toClassInfo() 47 | is KmClassifier.TypeParameter -> { 48 | throw IllegalArgumentException("$this cannot become a class info") 49 | } 50 | } 51 | } 52 | 53 | internal fun KmClass.isVisibleFromOutside() = flags.isVisibleFromOutsideFlags() 54 | 55 | internal fun KmFunction.isVisibleFromOutside() = flags.isVisibleFromOutsideFlags() 56 | 57 | internal fun KmClass.toClassInfo() = this.name.toClassInfo() 58 | 59 | internal fun KmClass.isInnerClass() = this.name.contains('.') 60 | 61 | internal fun KmClass.toInnerClassInfo(): InnerClassInfo { 62 | val dotIndex = name.lastIndexOf('.') 63 | check(dotIndex >= 0) { 64 | "$this should have had . in its name: $name" 65 | } 66 | val parent = name.substring(0, dotIndex) 67 | return InnerClassInfo( 68 | parent = parent.toClassInfo(), 69 | classInfo = toClassInfo() 70 | ) 71 | } 72 | 73 | internal fun KmFunction.isExtensionMethod() = this.receiverParameterType != null 74 | 75 | internal fun KmFunction.toGlobalFunction(pkg: String) = ParsedMethodInfo( 76 | pkg = pkg, 77 | receiver = null, 78 | name = name) 79 | 80 | internal fun KmFunction.toExtensionFunction(pkg: String) = ParsedMethodInfo( 81 | pkg = pkg, 82 | receiver = receiverParameterType!!.toClassInfo(), 83 | name = name 84 | ) 85 | -------------------------------------------------------------------------------- /artifactfinder/src/main/kotlin/com/birbit/artifactfinder/external/ExternalSourceSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.external 18 | 19 | import com.birbit.artifactfinder.ArtifactSource 20 | import com.birbit.artifactfinder.vo.Artifactory 21 | import java.io.InputStream 22 | import kotlinx.serialization.SerialName 23 | import kotlinx.serialization.Serializable 24 | import kotlinx.serialization.json.Json 25 | import kotlinx.serialization.json.JsonConfiguration 26 | 27 | @Serializable 28 | data class ExternalSourceSpec( 29 | // include a spec version so that we can change it w/o making the crawler throw errors 30 | @SerialName("version") 31 | val version: Int = LATEST_VERSION, 32 | // list of packages to crawl 33 | @SerialName("groups") 34 | val groups: List = emptyList() 35 | ) { 36 | fun asArtifactSources() = groups.flatMap { pkg -> 37 | pkg.artifactIds.map { artifactId -> 38 | ArtifactSource( 39 | groupId = pkg.groupId, 40 | artifactId = artifactId, 41 | artifactory = pkg.artifactory 42 | ) 43 | } 44 | } 45 | companion object { 46 | val LATEST_VERSION = 1 47 | private val json = Json( 48 | JsonConfiguration.Stable.copy( 49 | strictMode = false, 50 | prettyPrint = true 51 | ) 52 | ) 53 | 54 | fun parse(input: String): ExternalSourceSpec { 55 | return json.parse(serializer(), input) 56 | } 57 | 58 | fun parse(input: InputStream): ExternalSourceSpec { 59 | return parse(input.reader(Charsets.UTF_8).readText()) 60 | } 61 | 62 | fun print(externalSourceSpec: ExternalSourceSpec): String { 63 | return json.stringify(serializer(), externalSourceSpec) 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Represents an external source. 70 | */ 71 | @Serializable 72 | data class ExternalGroupSpec( 73 | @SerialName("groupId") 74 | val groupId: String, 75 | @SerialName("artifactIds") 76 | val artifactIds: List = emptyList(), 77 | @SerialName("artifactory") 78 | val artifactory: Artifactory = Artifactory.MAVEN 79 | ) { 80 | init { 81 | check(artifactIds.isNotEmpty()) { 82 | "Artifacts for a package cannot be empty" 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /web-client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/ArtifactInput.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import com.birbit.artifactfinder.parser.vo.ClassZipEntry 20 | import com.birbit.artifactfinder.parser.vo.CodeSource 21 | import java.io.IOException 22 | import java.io.InputStream 23 | import java.util.zip.ZipEntry 24 | import java.util.zip.ZipInputStream 25 | 26 | /** 27 | * Represents a jar artifact 28 | */ 29 | class Jar( 30 | private val inputStream: InputStream 31 | ) : CodeSource { 32 | override val classDeclarations 33 | get() = ZipInputStream(inputStream).asClassFileSequence() 34 | } 35 | 36 | /** 37 | * Represents an AAR artifact 38 | */ 39 | class Aar( 40 | private val inputStream: InputStream 41 | ) : CodeSource { 42 | override val classDeclarations 43 | get(): Sequence { 44 | val zip = ZipInputStream(inputStream) 45 | val classesJar = zip.asEntrySequence().first { 46 | it.name == "classes.jar" 47 | }.let { 48 | zip 49 | } 50 | return ZipInputStream(classesJar).asClassFileSequence() 51 | } 52 | } 53 | 54 | private fun ZipInputStream.asClassFileSequence(): Sequence { 55 | return sequence { 56 | this@asClassFileSequence.use { 57 | var next = nextEntry 58 | while (next != null) { 59 | if (next.name.endsWith(".class")) { 60 | val bytes = this@asClassFileSequence.readBytes() 61 | 62 | yield( 63 | ClassZipEntry( 64 | entry = next, 65 | stream = bytes 66 | ) 67 | ) 68 | } 69 | next = try { 70 | nextEntry 71 | } catch (io: IOException) { 72 | if (io.message == "Stream closed") { 73 | null 74 | } else { 75 | throw io 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | private fun ZipInputStream.asEntrySequence(): Sequence { 84 | return sequence { 85 | this@asEntrySequence.use { 86 | var next = nextEntry 87 | while (next != null) { 88 | yield(next) 89 | next = nextEntry 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /maven/src/main/kotlin/com/birbit/artifactfinder/maven/MavenFetcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.maven 18 | 19 | import com.birbit.artifactfinder.maven.vo.Artifact 20 | import com.birbit.artifactfinder.maven.vo.ArtifactMetadata 21 | import com.birbit.artifactfinder.maven.vo.ArtifactType 22 | import com.birbit.artifactfinder.maven.vo.GroupIndex 23 | import com.birbit.artifactfinder.vo.Artifactory 24 | import okhttp3.HttpUrl 25 | import retrofit2.HttpException 26 | 27 | class MavenFetcher internal constructor( 28 | artifactory: Artifactory, 29 | baseUrl: HttpUrl? = null 30 | ) { 31 | 32 | constructor(artifactory: Artifactory) : this(artifactory, null) 33 | 34 | private val mavenApi = MavenApi.create( 35 | artifactory = artifactory, 36 | baseUrl = baseUrl 37 | ) 38 | 39 | suspend fun fetchPackages() = mavenApi.masterIndex().keys 40 | .filter(::shouldParsePackage) 41 | 42 | suspend fun fetchGroupIndex(groupId: String) = mavenApi.groupIndex( 43 | groupId.toPath() 44 | ).let { 45 | GroupIndex( 46 | groupId = groupId, 47 | artifactIds = it.keys.filter { 48 | shouldParseArtfiact(groupId, it) 49 | }.toSet() 50 | ) 51 | } 52 | 53 | suspend fun fetchArtifactMetadata(groupId: String, artifactId: String): ArtifactMetadata { 54 | return mavenApi.mavenMetadata(groupId.toPath(), artifactId) 55 | } 56 | 57 | suspend fun fetchArtifact( 58 | groupId: String, 59 | artifactId: String, 60 | version: String 61 | ): Artifact { 62 | val path = groupId.toPath() 63 | return try { 64 | Artifact( 65 | type = ArtifactType.JAR, 66 | inputStream = mavenApi.jar( 67 | groupPath = path, 68 | artifactId = artifactId, 69 | version = version 70 | ).byteStream() 71 | ) 72 | } catch (io: HttpException) { 73 | if (io.code() == 404) { 74 | Artifact( 75 | type = ArtifactType.AAR, 76 | inputStream = mavenApi.aar( 77 | groupPath = path, 78 | artifactId = artifactId, 79 | version = version 80 | ).byteStream() 81 | ) 82 | } else { 83 | throw io 84 | } 85 | } 86 | } 87 | 88 | private fun String.toPath() = replace('.', '/') 89 | } 90 | -------------------------------------------------------------------------------- /model/src/test/kotlin/com/birbit/artifactfinder/model/db/SharedObjectPoolTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model.db 18 | 19 | import com.google.common.truth.Truth.assertThat 20 | import java.util.concurrent.atomic.AtomicInteger 21 | import kotlinx.coroutines.async 22 | import kotlinx.coroutines.awaitAll 23 | import kotlinx.coroutines.delay 24 | import kotlinx.coroutines.test.TestCoroutineScope 25 | import kotlinx.coroutines.test.runBlockingTest 26 | import org.junit.Test 27 | import org.junit.runner.RunWith 28 | import org.junit.runners.JUnit4 29 | 30 | @RunWith(JUnit4::class) 31 | class SharedObjectPoolTest { 32 | private val scope = TestCoroutineScope() 33 | private val idCounter = AtomicInteger(0) 34 | 35 | @Test 36 | fun onCreateOne() = scope.runBlockingTest { 37 | val pool = SharedObjectPool(10) { 38 | Item() 39 | } 40 | assertThat(pool.use { it }).isEqualTo(Item(1)) 41 | assertThat(pool.use { it }).isEqualTo(Item(1)) 42 | assertThat(pool.createdCnt).isEqualTo(1) 43 | } 44 | 45 | @Test 46 | fun limit() = scope.runBlockingTest { 47 | val pool = SharedObjectPool(7) { 48 | Item() 49 | } 50 | val items = (1..20).map { 51 | async { 52 | pool.use { 53 | delay(100) 54 | it 55 | } 56 | } 57 | }.awaitAll().toSet() 58 | assertThat(pool.createdCnt).isEqualTo(7) 59 | assertThat(items).isEqualTo( 60 | (1..7).map { 61 | Item(it) 62 | }.toSet() 63 | ) 64 | // now ask for more, shouldn't create anymore than needed 65 | val items2 = (1..7).map { 66 | async { 67 | pool.use { 68 | delay(100) 69 | it 70 | } 71 | } 72 | }.awaitAll().toSet() 73 | assertThat(pool.createdCnt).isEqualTo(7) 74 | assertThat(items2).isEqualTo( 75 | (1..7).map { 76 | Item(it) 77 | }.toSet() 78 | ) 79 | } 80 | 81 | private inner class Item(val id: Int = idCounter.incrementAndGet()) { 82 | override fun equals(other: Any?): Boolean { 83 | if (this === other) return true 84 | if (other !is Item) return false 85 | 86 | if (id != other.id) return false 87 | 88 | return true 89 | } 90 | 91 | override fun hashCode(): Int { 92 | return id 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/vo/ParsedArtifactInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.vo 18 | 19 | import com.birbit.artifactfinder.parser.EXCLUSION_FILTERS 20 | import com.birbit.artifactfinder.parser.METHOD_EXCLUSION_FILTERS 21 | 22 | internal data class ArtifactInfoBuilder( 23 | private val classes: MutableSet = mutableSetOf(), 24 | private val methods: MutableSet = mutableSetOf(), 25 | private val innerClasses: MutableSet = mutableSetOf() 26 | ) { 27 | fun build(): ParsedArtifactInfo { 28 | val allAvailableClasses = mergeEligibleInnerClasses(classes, innerClasses) 29 | return ParsedArtifactInfo( 30 | classes = allAvailableClasses, 31 | methods = methods 32 | ) 33 | } 34 | 35 | fun add(classInfo: ParsedClassInfo): ArtifactInfoBuilder { 36 | if (EXCLUSION_FILTERS.none { it(classInfo) }) { 37 | classes.add(classInfo) 38 | } 39 | return this 40 | } 41 | 42 | fun add(innerClassInfo: InnerClassInfo): ArtifactInfoBuilder { 43 | if (EXCLUSION_FILTERS.none { it(innerClassInfo.classInfo) }) { 44 | innerClasses.add(innerClassInfo) 45 | } 46 | return this 47 | } 48 | 49 | /** 50 | * InnerClass's visibility depends on parent's visibility. 51 | * Traverse parents for inner classes to decide if they are truly visible. 52 | */ 53 | private fun mergeEligibleInnerClasses( 54 | classes: Set, 55 | innerClasses: Set 56 | ): Set { 57 | val result = mutableSetOf() 58 | result.addAll(classes) 59 | val candidates = mutableSetOf().also { 60 | it.addAll(innerClasses) 61 | } 62 | var startSize = result.size 63 | var endSize = -1 64 | while (candidates.isNotEmpty() && startSize != endSize) { 65 | startSize = result.size 66 | try { 67 | val hasVisibleParent = candidates.firstOrNull { 68 | result.contains(it.parent) 69 | } 70 | hasVisibleParent?.let { 71 | result.add(it.classInfo) 72 | candidates.remove(it) 73 | } 74 | } finally { 75 | endSize = result.size 76 | } 77 | } 78 | return result 79 | } 80 | 81 | fun add(func: ParsedMethodInfo): ArtifactInfoBuilder { 82 | if (METHOD_EXCLUSION_FILTERS.none { it(func) }) { 83 | methods.add(func) 84 | } 85 | return this 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/ui/VersionPopupRenderer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin.ui 18 | 19 | import com.birbit.artifactfinder.ideplugin.SearchResultModel 20 | import com.birbit.artifactfinder.ideplugin.VersionSelectionPopupController 21 | import com.intellij.openapi.module.Module 22 | import com.intellij.openapi.project.Project 23 | import com.intellij.ui.TableUtil 24 | import com.intellij.ui.awt.RelativePoint 25 | import com.intellij.ui.components.JBLabel 26 | import com.intellij.util.ui.AbstractTableCellEditor 27 | import java.awt.Component 28 | import java.awt.Point 29 | import javax.swing.JTable 30 | 31 | class VersionPopupRenderer( 32 | private val project: Project, 33 | private val currentModule: Module?, 34 | private val onSuccess: () -> Unit 35 | ) : AbstractTableCellEditor() { 36 | private var editedValue: SearchResultModel.SearchResult? = null 37 | override fun getCellEditorValue(): Any { 38 | return editedValue ?: "?" 39 | } 40 | 41 | override fun getTableCellEditorComponent( 42 | table: JTable, 43 | value: Any?, 44 | isSelected: Boolean, 45 | row: Int, 46 | column: Int 47 | ): Component { 48 | editedValue = (value as SearchResultModel.SearchResult) 49 | showVersionSelection( 50 | table = table, 51 | row = row, 52 | column = column, 53 | result = editedValue!!, 54 | project = project, 55 | currentModule = currentModule, 56 | onSelected = { 57 | onSuccess() 58 | } 59 | ) 60 | return JBLabel( 61 | "select" 62 | ) 63 | } 64 | 65 | fun showVersionSelection( 66 | table: JTable, 67 | row: Int, 68 | column: Int, 69 | result: SearchResultModel.SearchResult, 70 | project: Project, 71 | currentModule: Module?, 72 | onSelected: () -> Unit 73 | ) { 74 | val rect = table.getCellRect(row, column, true) 75 | val point = Point(rect.x, rect.y) 76 | VersionSelectionPopupController( 77 | result = result, 78 | project = project, 79 | currentModule = currentModule, 80 | callback = object : VersionSelectionPopupController.Callback { 81 | override fun onChosen() { 82 | TableUtil.stopEditing(table) 83 | onSelected() 84 | } 85 | 86 | override fun onCancel() { 87 | TableUtil.stopEditing(table) 88 | } 89 | } 90 | ).buildPopup() 91 | .show(RelativePoint(table, point)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /maven/src/main/kotlin/com/birbit/artifactfinder/maven/MavenApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.maven 18 | 19 | import com.birbit.artifactfinder.maven.vo.ArtifactMetadata 20 | import com.birbit.artifactfinder.vo.Artifactory 21 | import java.util.concurrent.Executors 22 | import okhttp3.HttpUrl 23 | import okhttp3.OkHttpClient 24 | import okhttp3.ResponseBody 25 | import okhttp3.logging.HttpLoggingInterceptor 26 | import retrofit2.Retrofit 27 | import retrofit2.http.GET 28 | import retrofit2.http.Path 29 | 30 | internal interface MavenApi { 31 | companion object { 32 | internal const val MAVEN_METADATA = "maven-metadata.xml" 33 | internal const val GROUP_INDEX = "group-index.xml" 34 | internal const val MASTER_INDEX = "master-index.xml" 35 | private fun create(baseUrl: HttpUrl): MavenApi { 36 | val client = OkHttpClient.Builder() 37 | .addInterceptor(HttpLoggingInterceptor().also { 38 | it.level = HttpLoggingInterceptor.Level.BASIC 39 | }) 40 | .build() 41 | val builder = Retrofit.Builder() 42 | .client(client) 43 | .baseUrl(baseUrl) 44 | .addConverterFactory(JacksonConverterFactory()) 45 | .callbackExecutor(Executors.newSingleThreadExecutor()) 46 | .build() 47 | return builder.create(MavenApi::class.java) 48 | } 49 | 50 | fun create(artifactory: Artifactory, baseUrl: HttpUrl? = null): MavenApi { 51 | val httpUrl = baseUrl ?: HttpUrl.get(artifactory.baseUrl) 52 | return httpUrl?.let { 53 | create(it) 54 | } ?: throw IllegalArgumentException("bad url ${artifactory.baseUrl}") 55 | } 56 | } 57 | 58 | @GET("{groupPath}/{artifactId}/$MAVEN_METADATA") 59 | suspend fun mavenMetadata( 60 | @Path(value = "groupPath", encoded = true) groupPath: String, 61 | @Path("artifactId") artifactId: String 62 | ): ArtifactMetadata 63 | 64 | @GET("{groupPath}/{artifactId}/{version}/{artifactId}-{version}.jar") 65 | suspend fun jar( 66 | @Path(value = "groupPath", encoded = true) groupPath: String, 67 | @Path("artifactId") artifactId: String, 68 | @Path("version") version: String 69 | ): ResponseBody 70 | 71 | @GET("{groupPath}/{artifactId}/{version}/{artifactId}-{version}.aar") 72 | suspend fun aar( 73 | @Path(value = "groupPath", encoded = true) groupPath: String, 74 | @Path("artifactId") artifactId: String, 75 | @Path("version") version: String 76 | ): ResponseBody 77 | 78 | @GET(MASTER_INDEX) 79 | suspend fun masterIndex(): Map 80 | 81 | @GET("{groupPath}/$GROUP_INDEX") 82 | suspend fun groupIndex(@Path("groupPath", encoded = true) groupPath: String): 83 | Map 84 | } 85 | -------------------------------------------------------------------------------- /artifactfinder/src/main/kotlin/com/birbit/artifactfinder/VersionSelector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder 18 | 19 | import com.birbit.artifactfinder.model.Version 20 | 21 | object VersionSelector { 22 | /** 23 | * Current version selection logic: 24 | * Take last 3 stable releases 25 | * Take latest rc, if it is greater than latest stable 26 | * Take latest beta, if it is greater than latest stable & rc 27 | * Take latest alpha, if it is greater than latest stable & rc & beta 28 | * 29 | */ 30 | fun selectVersions(input: List): Set { 31 | val sorted = input.sortedDescending() 32 | val selected = mutableListOf() 33 | 34 | val counters = Counters() 35 | sorted.forEach { version -> 36 | val select = if (version.isRelease) { 37 | true 38 | } else if (version.isRc) { 39 | selected.all { 40 | if (it.isRelease) { 41 | it < version 42 | } else { 43 | true // can pick rc when there is 44 | } 45 | } 46 | } else if (version.isBeta) { 47 | selected.all { 48 | if (it.isRelease || it.isRc) { 49 | it < version 50 | } else { 51 | true 52 | } 53 | } 54 | } else if (version.isAlpha) { 55 | selected.all { 56 | if (it.isRelease || it.isRc || it.isBeta) { 57 | it < version 58 | } else { 59 | true 60 | } 61 | } 62 | } else { 63 | false 64 | } 65 | if (select && counters.inc(version)) { 66 | selected.add(version) 67 | } 68 | } 69 | return selected.toSet() 70 | } 71 | } 72 | 73 | private class Counter( 74 | var limit: Int, 75 | var cnt: Int = 0 76 | ) { 77 | fun inc(): Boolean { 78 | return if (cnt < limit) { 79 | cnt++ 80 | true 81 | } else { 82 | false 83 | } 84 | } 85 | } 86 | 87 | private class Counters { 88 | var stable = Counter(LIMIT_STABLE) 89 | var rc = Counter(LIMIT_RC) 90 | var beta = Counter(LIMIT_BETA) 91 | var alpha = Counter(LIMIT_ALPHA) 92 | fun inc(version: Version): Boolean { 93 | val counter = when { 94 | version.isRelease -> stable 95 | version.isRc -> rc 96 | version.isBeta -> beta 97 | version.isAlpha -> alpha 98 | else -> return false 99 | } 100 | return counter.inc() 101 | } 102 | 103 | companion object { 104 | val LIMIT_STABLE = 3 105 | val LIMIT_RC = 1 106 | val LIMIT_BETA = 1 107 | val LIMIT_ALPHA = 1 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/db/ConnectionProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model.db 18 | 19 | import kotlin.coroutines.AbstractCoroutineContextElement 20 | import kotlin.coroutines.CoroutineContext 21 | import kotlin.coroutines.coroutineContext 22 | import kotlinx.coroutines.sync.Mutex 23 | import kotlinx.coroutines.sync.withLock 24 | import kotlinx.coroutines.withContext 25 | 26 | class ConnectionProvider( 27 | limit: Int, 28 | private val createConnection: suspend () -> WritableDbDriver, 29 | private val initFirstDb: suspend (WritableDbDriver) -> Unit 30 | ) { 31 | private val pool = SharedObjectPool( 32 | limit = limit, 33 | creator = ::doCreate 34 | ) 35 | 36 | private val writeLock = Mutex() 37 | 38 | private val createLock = Mutex() 39 | private var first = true 40 | private suspend fun doCreate(): WritableDbDriver { 41 | val conn = createConnection() 42 | createLock.withLock { 43 | if (first) { 44 | first = false 45 | initFirstDb(conn) 46 | } 47 | } 48 | return conn 49 | } 50 | 51 | private suspend fun use(block: suspend WritableDbDriver.() -> R): R { 52 | @Suppress("UNCHECKED_CAST") 53 | val contextElm = coroutineContext[ContextConnection.KEY] 54 | if (contextElm != null) { 55 | return contextElm.conn.block() 56 | } 57 | return pool.use { myConn -> 58 | val newConn = ContextConnection(myConn, false) 59 | try { 60 | withContext(coroutineContext + newConn) { 61 | myConn.block() 62 | } 63 | } finally { 64 | newConn.releaseIfWriteable(writeLock) 65 | } 66 | } 67 | } 68 | 69 | suspend fun write( 70 | block: suspend WritableDbDriver.() -> R 71 | ): R { 72 | return use { 73 | val contextElm = checkNotNull(coroutineContext[ContextConnection.KEY]) 74 | contextElm.makeWriteable(writeLock) 75 | block() 76 | } 77 | } 78 | 79 | suspend fun read( 80 | block: suspend DbDriver.() -> R 81 | ): R { 82 | return use { 83 | block() 84 | } 85 | } 86 | 87 | internal class ContextConnection( 88 | val conn: WritableDbDriver, 89 | @Volatile var writeable: Boolean 90 | ) : AbstractCoroutineContextElement(KEY) { 91 | companion object { 92 | internal val KEY = object : CoroutineContext.Key {} 93 | } 94 | 95 | suspend fun makeWriteable(mutex: Mutex) { 96 | if (writeable) return 97 | mutex.lock(this) 98 | writeable = true 99 | } 100 | 101 | suspend fun releaseIfWriteable(mutex: Mutex) { 102 | if (!writeable) return 103 | writeable = false 104 | mutex.unlock(this) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /artifactfinder/src/main/kotlin/com/birbit/artifactfinder/worker/JobQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.worker 18 | 19 | import kotlinx.coroutines.CancellationException 20 | import kotlinx.coroutines.CoroutineDispatcher 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.channels.Channel 23 | import kotlinx.coroutines.channels.ClosedReceiveChannelException 24 | import kotlinx.coroutines.flow.Flow 25 | import kotlinx.coroutines.flow.collect 26 | import kotlinx.coroutines.flow.flow 27 | import kotlinx.coroutines.joinAll 28 | import kotlinx.coroutines.launch 29 | import kotlinx.coroutines.sync.Mutex 30 | import kotlinx.coroutines.sync.withLock 31 | import kotlinx.coroutines.withContext 32 | 33 | suspend fun distributeJobs( 34 | items: List, 35 | workers: Int = (items.size / 5).coerceAtMost(5).coerceAtLeast(1), 36 | dispatcher: CoroutineDispatcher = Dispatchers.IO, 37 | consumer: suspend (T) -> R 38 | ): List = distributeJobs( 39 | items = flow { 40 | items.forEach { 41 | emit(it) 42 | } 43 | }, 44 | workers = workers, 45 | dispatcher = dispatcher, 46 | consumer = consumer 47 | ) 48 | 49 | private fun log(msg: Any?) { 50 | println("[JQ][${Thread.currentThread().name}]: $msg") 51 | } 52 | 53 | suspend fun distributeJobs( 54 | items: Flow, 55 | workers: Int, 56 | dispatcher: CoroutineDispatcher = Dispatchers.IO, 57 | consumer: suspend (T) -> R 58 | ): List = withContext(dispatcher) { 59 | val distributionChannel = Channel() 60 | launch { 61 | try { 62 | items.collect { 63 | if (it != null) { 64 | log("dispatch $it") 65 | distributionChannel.send(it) 66 | } else { 67 | log("flow emitted null") 68 | throw AbortFlowException() 69 | } 70 | } 71 | } catch (abort: AbortFlowException) { 72 | } finally { 73 | distributionChannel.close() 74 | log("distributionChannel DONE") 75 | } 76 | } 77 | val resultLock = Mutex() 78 | val results = mutableListOf() 79 | (0 until workers).map { workerId -> 80 | launch { 81 | while (!distributionChannel.isClosedForReceive) { 82 | try { 83 | val value = distributionChannel.receive() 84 | log("will process $value in $workerId") 85 | val result = consumer(value) 86 | log("will save result $result for $workerId") 87 | resultLock.withLock { 88 | log("saving result for $workerId") 89 | results.add(result) 90 | } 91 | } catch (closed: ClosedReceiveChannelException) { 92 | log("worker $workerId done") 93 | } catch (th: Throwable) { 94 | log("worker has thrown exception ${th.message}") 95 | } 96 | } 97 | } 98 | }.joinAll() 99 | log("JobQueue DONE") 100 | return@withContext results 101 | } 102 | 103 | private class AbortFlowException : CancellationException() 104 | -------------------------------------------------------------------------------- /parser/src/main/kotlin/com/birbit/artifactfinder/parser/ClassZipEntryParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser 18 | 19 | import com.birbit.artifactfinder.parser.vo.ArtifactInfoBuilder 20 | import com.birbit.artifactfinder.parser.vo.ClassZipEntry 21 | import kotlinx.metadata.jvm.KotlinClassMetadata 22 | import org.objectweb.asm.ClassReader 23 | import org.objectweb.asm.Opcodes 24 | import org.objectweb.asm.tree.AnnotationNode 25 | import org.objectweb.asm.tree.ClassNode 26 | 27 | internal object ClassZipEntryParser { 28 | fun parse(classZipEntry: ClassZipEntry, builder: ArtifactInfoBuilder) { 29 | val node = ClassNode(Opcodes.ASM7) 30 | val reader = ClassReader(classZipEntry.stream) 31 | reader.accept(node, skippedParts) 32 | val metadata = node.kotlinMetadataAnnotation() 33 | 34 | if (metadata != null) { 35 | parseMetadata(metadata, node, builder) 36 | } else { 37 | parseJavaClass(node, builder) 38 | } 39 | } 40 | 41 | private fun parseJavaClass( 42 | node: ClassNode, 43 | into: ArtifactInfoBuilder 44 | ) { 45 | if (!node.isVisibleFromOutside()) return 46 | if (node.isInnerClass()) { 47 | into.add(node.toInnerClassInfo()) 48 | } else { 49 | into.add(node.toClassInfo()) 50 | } 51 | } 52 | 53 | private fun parseMetadata( 54 | metadata: KotlinClassMetadata, 55 | node: ClassNode, 56 | into: ArtifactInfoBuilder 57 | ) { 58 | val nodeInfo = node.toClassInfo() 59 | when (metadata) { 60 | is KotlinClassMetadata.Class -> { 61 | val kmClass = metadata.toKmClass() 62 | val isVisible = kmClass.isVisibleFromOutside() && node.isVisibleFromOutside() 63 | if (!isVisible) { 64 | return 65 | } 66 | if (kmClass.isInnerClass()) { 67 | into.add(kmClass.toInnerClassInfo()) 68 | } else { 69 | into.add(kmClass.toClassInfo()) 70 | } 71 | } 72 | is KotlinClassMetadata.FileFacade -> { 73 | val kmPackage = metadata.toKmPackage() 74 | kmPackage.functions.forEach { 75 | if (it.isVisibleFromOutside()) { 76 | if (it.isExtensionMethod()) { 77 | into.add(it.toExtensionFunction(nodeInfo.pkg)) 78 | } else { 79 | into.add(it.toGlobalFunction(nodeInfo.pkg)) 80 | } 81 | } 82 | } 83 | } 84 | else -> { 85 | // ignore? 86 | } 87 | } 88 | } 89 | 90 | private val skippedParts = 91 | ClassReader.SKIP_CODE.or(ClassReader.SKIP_DEBUG).or(ClassReader.SKIP_FRAMES) 92 | } 93 | 94 | private fun ClassNode.kotlinMetadataAnnotation(): KotlinClassMetadata? { 95 | return visibleAnnotations?.mapNotNull { 96 | it.kotlinMetadata() 97 | }?.firstOrNull() ?: invisibleAnnotations?.mapNotNull { 98 | it.kotlinMetadata() 99 | }?.firstOrNull() 100 | } 101 | 102 | private fun AnnotationNode.kotlinMetadata(): KotlinClassMetadata? { 103 | return extractKotlinMetadata() 104 | } 105 | -------------------------------------------------------------------------------- /model/src/main/kotlin/com/birbit/artifactfinder/model/db/JdbcDbImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model.db 18 | 19 | import java.sql.Connection 20 | import java.sql.PreparedStatement 21 | import java.sql.ResultSet 22 | import java.sql.Types 23 | 24 | internal class JdbcQueryResult( 25 | private val rs: ResultSet 26 | ) : QueryResult { 27 | override val columnNames: Set = (1..rs.metaData.columnCount).map { 28 | rs.metaData.getColumnName(it) 29 | }.toSet() 30 | 31 | override fun nextRow() = rs.next() 32 | 33 | override fun requireInt(columnName: String) = rs.getInt(columnName) 34 | 35 | override fun requireLong(columnName: String): Long = rs.getLong(columnName) 36 | 37 | override fun requireString(columnName: String): String = rs.getString(columnName) 38 | 39 | override fun getString(columnName: String): String? = rs.getString(columnName) 40 | 41 | override fun close() { 42 | rs.close() 43 | } 44 | } 45 | 46 | internal open class JdbcQueryImpl( 47 | val stmt: PreparedStatement 48 | ) : Query { 49 | override fun bindInt(index: Int, value: Int) { 50 | stmt.setInt(index, value) 51 | } 52 | 53 | override fun bindLong(index: Int, value: Long) { 54 | stmt.setLong(index, value) 55 | } 56 | 57 | override fun bindString(index: Int, value: String) { 58 | stmt.setString(index, value) 59 | } 60 | 61 | override fun bindNull(index: Int) { 62 | stmt.setNull(index, Types.NULL) 63 | } 64 | 65 | override suspend fun query(block: (QueryResult) -> T): T { 66 | return stmt.use { 67 | val rs = JdbcQueryResult(stmt.executeQuery()) 68 | rs.use(block) 69 | } 70 | } 71 | } 72 | 73 | internal class JdbcWritableQueryImpl( 74 | stmt: PreparedStatement, 75 | private val conn: Connection 76 | ) : JdbcQueryImpl(stmt), WriteQuery { 77 | override suspend fun exec() = stmt.use { 78 | it.execute() 79 | } 80 | 81 | override suspend fun execForLastRowId(block: (Long) -> T): T { 82 | stmt.use { 83 | stmt.execute() 84 | } 85 | return conn.prepareStatement("SELECT last_insert_rowid()").use { 86 | it.executeQuery().use { 87 | block(it.getLong(1)) 88 | } 89 | } 90 | } 91 | } 92 | 93 | internal open class JdbcDbDriver( 94 | val conn: Connection 95 | ) : DbDriver { 96 | override fun prepareRead(sql: String): Query { 97 | return JdbcQueryImpl(conn.prepareStatement(sql)) 98 | } 99 | } 100 | 101 | internal class JdbcWriteableDbDriver( 102 | conn: Connection 103 | ) : JdbcDbDriver(conn), WritableDbDriver { 104 | override suspend fun withTransaction(block: suspend () -> T): T { 105 | if (conn.autoCommit == false) { 106 | return block() // already in a transaction, just run it 107 | } 108 | conn.autoCommit = false 109 | try { 110 | val result = block() 111 | conn.commit() 112 | return result 113 | } finally { 114 | conn.autoCommit = true 115 | } 116 | } 117 | 118 | override fun prepareWrite(sql: String): WriteQuery { 119 | return JdbcWritableQueryImpl( 120 | stmt = conn.prepareStatement(sql), 121 | conn = conn 122 | ) 123 | } 124 | 125 | override fun prepareRead(sql: String): Query { 126 | return JdbcQueryImpl(conn.prepareStatement(sql)) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /web-client/src/ArtifactSearch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, { Component } from "react"; 18 | import axios from "axios"; 19 | 20 | class SearchResult extends Component { 21 | render() { 22 | const item = this.props.item; 23 | const receiverPrefix = 24 | item.receiverName == null ? "" : item.receiverName + "."; 25 | return ( 26 |
27 |

28 | {receiverPrefix} 29 | {item.name} ({item.score}) @ {item.groupId}:{item.artifactId}: 30 | {item.version}{" "} 31 |

32 |
33 | ); 34 | } 35 | } 36 | 37 | function ResultList(props) { 38 | const listItems = props.searchResults.map(item => ( 39 |
  • 40 | 41 |
  • 42 | )); 43 | return
      {listItems}
    ; 44 | } 45 | 46 | class ArtifactSearch extends Component { 47 | state = { 48 | query: "", 49 | includeClasses: "", 50 | includeExtensionMethods: "", 51 | includeGlobalMethods: "", 52 | searchResults: [] 53 | }; 54 | handleInputChange = async event => { 55 | //event.preventDefault(); 56 | 57 | // Promise is resolved and value is inside of the response const. 58 | // https://jsonplaceholder.typicode.com/users 59 | // http://0.0.0.0:8080/searchArtifact 60 | // /searchArtifact 61 | console.log( 62 | "include classes:", 63 | this.includeClassesInput.checked, 64 | "include extension", 65 | this.includeExtensionMethodsInput.checked, 66 | "include global", 67 | this.includeGlobalMethodsInput.checked 68 | ); 69 | const response = await axios.get(`/searchArtifact`, { 70 | params: { 71 | query: this.search.value, 72 | includeClasses: this.includeClassesInput.checked, 73 | includeExtensionMethods: this.includeExtensionMethodsInput.checked, 74 | includeGlobalMethods: this.includeGlobalMethodsInput.checked 75 | }, 76 | headers: { "Content-Type": "application/json" } 77 | }); 78 | console.log(response); 79 | console.log(response.data); 80 | this.setState({ 81 | query: this.search.value, 82 | searchResults: response.data.results 83 | }); 84 | }; 85 | 86 | render() { 87 | return ( 88 |
    89 | (this.search = input)} 93 | onChange={this.handleInputChange} 94 | /> 95 |
    96 | (this.includeClassesInput = input)} 102 | onChange={this.handleInputChange} 103 | defaultChecked 104 | /> 105 | Classes | 106 | (this.includeExtensionMethodsInput = input)} 111 | onChange={this.handleInputChange} 112 | value="true" 113 | /> 114 | ExtensionMethods | 115 | (this.includeGlobalMethodsInput = input)} 120 | onChange={this.handleInputChange} 121 | value="true" 122 | /> 123 | GlobalMethods 124 |

    {this.state.query}

    125 | 126 | 127 | ); 128 | } 129 | } 130 | 131 | export default ArtifactSearch; 132 | -------------------------------------------------------------------------------- /artifactfinder/src/test/kotlin/com/birbit/artifactfinder/external/ExternalSourceSpecTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.external 18 | 19 | import com.birbit.artifactfinder.maven.MavenFetcher 20 | import com.birbit.artifactfinder.vo.Artifactory 21 | import com.google.common.truth.Truth.assertThat 22 | import com.google.common.truth.Truth.assertWithMessage 23 | import kotlinx.coroutines.runBlocking 24 | import org.junit.Test 25 | 26 | class ExternalSourceSpecTest { 27 | @Test 28 | fun testEmpty() { 29 | assertThat("""{}""".parse()).isEqualTo( 30 | ExternalSourceSpec( 31 | version = 1, 32 | groups = emptyList() 33 | ) 34 | ) 35 | } 36 | 37 | @Test 38 | fun test_oneArtifact() { 39 | assertThat( 40 | """{ 41 | "groups" : [ 42 | { 43 | "groupId" : "foo.bar", 44 | "artifactIds" : ["baz"] 45 | } 46 | ] 47 | }""".trimMargin().parse() 48 | ).isEqualTo( 49 | ExternalSourceSpec( 50 | version = 1, 51 | groups = listOf( 52 | ExternalGroupSpec( 53 | groupId = "foo.bar", 54 | artifactIds = listOf("baz") 55 | ) 56 | ) 57 | ) 58 | ) 59 | } 60 | 61 | @Test 62 | fun test_ignoreUnknown() { 63 | assertThat( 64 | """{ 65 | "ignore_unknown" : true, 66 | "groups" : [ 67 | { 68 | "groupId" : "foo.bar", 69 | "artifactIds" : ["baz"] 70 | } 71 | ] 72 | }""".trimMargin().parse() 73 | ).isEqualTo( 74 | ExternalSourceSpec( 75 | version = 1, 76 | groups = listOf( 77 | ExternalGroupSpec( 78 | groupId = "foo.bar", 79 | artifactIds = listOf("baz") 80 | ) 81 | ) 82 | ) 83 | ) 84 | } 85 | 86 | @Test 87 | fun test_emptyArtifact() { 88 | val result = runCatching { 89 | """{ 90 | "groups" : [ 91 | { 92 | "groupId" : "foo.bar" 93 | } 94 | ] 95 | }""".trimMargin().parse() 96 | } 97 | assertThat(result.isFailure).isTrue() 98 | assertThat(result.exceptionOrNull()).hasMessageThat().contains( 99 | "Artifacts for a package cannot be empty" 100 | ) 101 | } 102 | 103 | @Test 104 | fun validateCurrent() = runBlocking { 105 | val current = ExternalSourceSpecTest::class.java.getResourceAsStream("/external_sources.json") 106 | .reader(Charsets.UTF_8) 107 | .readText().parse() 108 | assertThat(current.groups).isNotEmpty() 109 | val fetchers = mutableMapOf() 110 | current.asArtifactSources().forEach { 111 | val fetcher = fetchers.getOrPut(it.artifactory) { 112 | MavenFetcher(it.artifactory) 113 | } 114 | val mavenInfo = runCatching { 115 | fetcher.fetchArtifactMetadata(it.groupId, it.artifactId) 116 | } 117 | assertWithMessage("should be able to fetch ${it.groupId} / ${it.artifactId}") 118 | .that(mavenInfo.isSuccess).isTrue() 119 | } 120 | } 121 | 122 | private fun String.parse(): ExternalSourceSpec { 123 | return ExternalSourceSpec.parse(this) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /parser/src/test/kotlin/com/birbit/artifactfinder/parser/testapk/TestApk.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.parser.testapk 18 | 19 | import com.birbit.artifactfinder.parser.Aar 20 | import com.birbit.artifactfinder.parser.testapk.templates.androidManifest 21 | import com.birbit.artifactfinder.parser.testapk.templates.gradlePropsFile 22 | import com.birbit.artifactfinder.parser.testapk.templates.mainBuildFile 23 | import com.birbit.artifactfinder.parser.testapk.templates.moduleBuildFile 24 | import com.birbit.artifactfinder.parser.testapk.templates.settingsGradle 25 | import com.google.common.truth.Truth 26 | import java.io.File 27 | import org.gradle.testkit.runner.GradleRunner 28 | import org.gradle.testkit.runner.TaskOutcome 29 | 30 | class SourceFile( 31 | val path: String, 32 | val code: String 33 | ) 34 | 35 | class TestApk( 36 | private val tmpFolder: File, 37 | private val sources: List, 38 | private val gradleVersion: String = "5.6.1", 39 | private val agpVersion: String = "3.5.0", 40 | private val kotlinVersion: String = "1.3.50", 41 | private val appPkg: String = "com.test" 42 | ) { 43 | private fun prepareProject() { 44 | tmpFolder.deleteRecursively() 45 | tmpFolder.mkdirs() 46 | tmpFolder.resolve("gradle/wrapper").mkdirs() 47 | tmpFolder.resolve("gradle/wrapper/gradle-wrapper.properties") 48 | .writeText(gradlePropsFile(gradleVersion)) 49 | tmpFolder.resolve("gradle/wrapper/gradle-wrapper.jar") 50 | .writeBytes( 51 | TestApk::class.java.getResourceAsStream("/gradle-wrapper.jar") 52 | .readBytes() 53 | ) 54 | tmpFolder.resolve("gradlew").apply { 55 | writeBytes( 56 | TestApk::class.java.getResourceAsStream("/gradlew") 57 | .readBytes() 58 | ) 59 | setExecutable(true) 60 | } 61 | tmpFolder.resolve("build.gradle").writeText( 62 | mainBuildFile( 63 | agpVersion = agpVersion, 64 | kotlinVersion = kotlinVersion 65 | ) 66 | ) 67 | tmpFolder.resolve("settings.gradle").writeText( 68 | settingsGradle() 69 | ) 70 | tmpFolder.resolve("lib").mkdirs() 71 | tmpFolder.resolve("lib").resolve("build.gradle") 72 | .writeText( 73 | moduleBuildFile( 74 | hasKotlin = true 75 | ) 76 | ) 77 | val srcRoot = tmpFolder.resolve("lib/src/main/java") 78 | srcRoot.mkdirs() 79 | sources.forEach { 80 | srcRoot.resolve(it.path).apply { 81 | parentFile.mkdirs() 82 | writeText(it.code) 83 | } 84 | } 85 | 86 | tmpFolder.resolve("lib/src/main/AndroidManifest.xml") 87 | .writeText(androidManifest(appPkg)) 88 | } 89 | 90 | fun buildAar(): Aar { 91 | prepareProject() 92 | @Suppress("UnstableApiUsage") 93 | val result = GradleRunner.create() 94 | .withProjectDir(tmpFolder) 95 | .withArguments(":lib:assembleRelease") 96 | .withEnvironment( 97 | mapOf( 98 | "ANDROID_HOME" to "/home/yboyar/android/sdk" // TODO 99 | ) 100 | ) 101 | .build() 102 | val output = tmpFolder.resolve("lib/build/outputs/aar/lib-release.aar") 103 | Truth.assertThat(output.exists()).isTrue() 104 | val assembleTask = result.tasks.first { 105 | it.path == ":lib:assembleRelease" 106 | } 107 | Truth.assertThat(assembleTask.outcome).isEqualTo( 108 | TaskOutcome.SUCCESS 109 | ) 110 | return Aar(output.inputStream()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /webservice/src/main/kotlin/com/birbit/artifactfinder/webservice/ArtifactFinderRequestHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.webservice 18 | 19 | import com.birbit.artifactfinder.dto.SearchResponseDTO 20 | import com.birbit.artifactfinder.dto.SearchResultDTO 21 | import com.birbit.artifactfinder.model.ArtifactFinderModel 22 | import io.ktor.application.ApplicationCall 23 | import io.ktor.http.ContentType 24 | import io.ktor.http.HttpStatusCode 25 | import io.ktor.response.respond 26 | import io.ktor.response.respondText 27 | import java.io.File 28 | import kotlinx.serialization.UnstableDefault 29 | import kotlinx.serialization.json.Json 30 | 31 | @Suppress("unused") 32 | @UnstableDefault 33 | class ArtifactFinderRequestHandler( 34 | dbFile: File 35 | ) { 36 | private val model by lazy { 37 | ArtifactFinderModel(dbFile) 38 | } 39 | 40 | suspend fun handleArtifactFinderRequest(call: ApplicationCall) { 41 | call.handleRequest() 42 | } 43 | 44 | private suspend fun ApplicationCall.handleRequest() { 45 | val query = parameters["query"] 46 | val limit = (parameters["limit"]?.toIntOrNull() ?: 20).coerceAtMost(50) 47 | @Suppress("UNUSED_VARIABLE") // one day 48 | val version = parameters["version"]?.toIntOrNull() ?: 1 49 | val includeClasses = parameters["includeClasses"].parseBoolean(true) 50 | val includeExtensionMethods = parameters["includeExtensionMethods"].parseBoolean(false) 51 | val includeGlobalMethods = parameters["includeGlobalMethods"].parseBoolean(false) 52 | when { 53 | query == null -> respond( 54 | status = HttpStatusCode.BadRequest, 55 | message = "need a 'query' parameter" 56 | ) 57 | query.isEmpty() -> respond( 58 | status = HttpStatusCode.OK, 59 | message = SearchResponseDTO( 60 | latestVersion = 1, 61 | results = emptyList() 62 | ) 63 | ) 64 | else -> { 65 | val items = model.search( 66 | ArtifactFinderModel.SearchParams( 67 | query = query, 68 | includeClasses = includeClasses, 69 | includeExtensionMethods = includeExtensionMethods, 70 | includeGlobalMethods = includeGlobalMethods 71 | ) 72 | ).take(limit).map { 73 | SearchResultDTO( 74 | pkg = it.pkg, 75 | name = it.name, 76 | receiverName = it.receiverName, 77 | groupId = it.groupId, 78 | artifactId = it.artifactId, 79 | version = it.version.toString(), 80 | score = it.score 81 | ) 82 | } 83 | val json = Json.stringify( 84 | serializer = SearchResponseDTO.serializer(), 85 | obj = SearchResponseDTO( 86 | latestVersion = 1, 87 | results = items 88 | ) 89 | ) 90 | respondText( 91 | contentType = JSON_CONTENT_TYPE, 92 | status = HttpStatusCode.OK, 93 | text = json 94 | ) 95 | } 96 | } 97 | } 98 | 99 | private fun String?.parseBoolean(default: Boolean): Boolean { 100 | if (this == null) { 101 | return default 102 | } 103 | val trimmed = trim() 104 | if (trimmed.toLowerCase() == "true") { 105 | return true 106 | } 107 | return trimmed.toIntOrNull()?.let { 108 | it > 0 109 | } ?: default 110 | } 111 | 112 | companion object { 113 | private val JSON_CONTENT_TYPE = ContentType.parse("application/json") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /artifactfinder/src/main/kotlin/com/birbit/artifactfinder/Main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder 18 | 19 | import java.io.File 20 | import java.io.IOException 21 | import java.nio.channels.FileChannel 22 | import java.nio.file.StandardOpenOption 23 | import java.util.Locale 24 | 25 | internal enum class Action(vararg val names: String) { 26 | TRAVERSE_GMAVEN("gmaven"), 27 | DOWNLOAD_ARTIFACTS("download"), 28 | TRAVERSE_EXTERNAL("external") 29 | } 30 | 31 | internal enum class Params(vararg val names: String) { 32 | DB("db"), 33 | ACTION("action") 34 | } 35 | 36 | internal data class CmdlineArgs( 37 | val action: Action, 38 | val db: File 39 | ) 40 | 41 | private fun usage(args: Array, exception: Throwable?) { 42 | exception?.let { 43 | println(it.message) 44 | } 45 | val actionKey = Params.values().flatMap { it.names.toList() }.joinToString("|") 46 | val dbKey = Params.DB.names.joinToString("|") 47 | val actionOptions = Action.values().flatMap { it.names.toList() }.joinToString("|") 48 | println("Usage: --$dbKey --$actionKey $actionOptions") 49 | println("received: ${args.joinToString(" ")}") 50 | System.exit(1) 51 | } 52 | 53 | private fun parseArgs(args: Array): CmdlineArgs { 54 | var action: Action? = null 55 | var db: File? = null 56 | repeat(args.size / 2) { 57 | val type = args[it * 2] 58 | val value = args[it * 2 + 1] 59 | check(type.startsWith("--") && type.length > 2) { 60 | "type must start with --, but $type does not" 61 | } 62 | val parsedType = type.substring(2).toLowerCase(Locale.US) 63 | when { 64 | Params.ACTION.names.contains(parsedType) -> { 65 | check(action == null) { 66 | "action is already set to $action" 67 | } 68 | action = Action.values().firstOrNull { 69 | it.names.contains(value.toLowerCase(Locale.US)) 70 | } 71 | checkNotNull(action) { 72 | "invalid action $action" 73 | } 74 | } 75 | Params.DB.names.contains(parsedType) -> { 76 | check(db == null) { 77 | "db is already set to ${db!!.canonicalPath}" 78 | } 79 | db = File(value) 80 | } 81 | else -> throw IllegalArgumentException("invalid command $parsedType") 82 | } 83 | } 84 | checkNotNull(action) { 85 | "must provide an action" 86 | } 87 | checkNotNull(db) { 88 | "must provide a database path" 89 | } 90 | return CmdlineArgs( 91 | action = action!!, 92 | db = db!! 93 | ) 94 | } 95 | 96 | suspend fun main(vararg args: String) { 97 | val cmd = try { 98 | parseArgs(args) 99 | } catch (e: Throwable) { 100 | usage(args, e) 101 | null 102 | } ?: return 103 | println(cmd) 104 | try { 105 | ensureSingleton() 106 | when (cmd.action) { 107 | Action.TRAVERSE_GMAVEN -> ArtifactFinder(cmd.db).indexGMaven() 108 | Action.TRAVERSE_EXTERNAL -> ArtifactFinder(cmd.db).indexExternalArtifacts() 109 | Action.DOWNLOAD_ARTIFACTS -> ArtifactFinder(cmd.db).fetchArtifacts() 110 | } 111 | } catch (ex: Throwable) { 112 | ex.printStackTrace() 113 | System.exit(1) 114 | } 115 | System.exit(0) 116 | } 117 | 118 | fun ensureSingleton() { 119 | val userHome = System.getProperty("user.home") 120 | val file = File(userHome, "artifactFinder.lock") 121 | try { 122 | val fc = FileChannel.open( 123 | file.toPath(), 124 | StandardOpenOption.CREATE, 125 | StandardOpenOption.WRITE 126 | ) 127 | val lock = fc.tryLock() 128 | if (lock == null) { 129 | println("another instance is running") 130 | System.exit(0) 131 | } 132 | } catch (e: IOException) { 133 | System.exit(0) 134 | throw Error(e) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ideplugin/src/main/kotlin/com/birbit/artifactfinder/ideplugin/ui/SearchClassWindow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.ideplugin.ui 18 | 19 | import com.intellij.icons.AllIcons 20 | import com.intellij.openapi.module.Module 21 | import com.intellij.openapi.project.Project 22 | import com.intellij.openapi.ui.popup.JBPopupFactory 23 | import com.intellij.openapi.ui.popup.JBPopupListener 24 | import com.intellij.openapi.ui.popup.LightweightWindowEvent 25 | import com.intellij.ui.SideBorder 26 | import com.intellij.ui.components.JBLabel 27 | import com.intellij.ui.components.JBTextField 28 | import com.intellij.ui.table.JBTable 29 | import com.intellij.util.ui.UI 30 | import com.intellij.util.ui.UIUtil 31 | import java.awt.Component 32 | import java.awt.Dimension 33 | import javax.swing.* 34 | import kotlinx.coroutines.* 35 | 36 | @FlowPreview 37 | @ExperimentalCoroutinesApi 38 | class SearchArtifactPanelController( 39 | private val project: Project, 40 | private val module: Module? = null, 41 | private val initialText: String? = null 42 | ) { 43 | private val job = SupervisorJob(null) 44 | private val scope = CoroutineScope(Dispatchers.Main + job) 45 | 46 | fun buildAndShow() { 47 | val searchResultModel = SearchResultTableModel() 48 | val resultTable = JBTable(searchResultModel).also { 49 | it.setMaxItemsForSizeCalculation(10) 50 | it.setShowColumns(true) 51 | it.autoResizeMode = JBTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS 52 | it.fillsViewportHeight = true 53 | } 54 | 55 | val inputText = JBTextField(initialText) 56 | inputText.emptyText.text = "input a class name. e.g: RecyclerView" 57 | val errorText = JBLabel( 58 | "", AllIcons.General.Error, JBLabel.LEADING 59 | ) 60 | 61 | resultTable.putClientProperty(UIUtil.KEEP_BORDER_SIDES, SideBorder.ALL) 62 | 63 | val bottomPanel = JPanel() 64 | bottomPanel.layout = BoxLayout(bottomPanel, BoxLayout.Y_AXIS) 65 | val helpButton = JButton("Help", AllIcons.Actions.Help) 66 | 67 | bottomPanel.add(helpButton) 68 | helpButton.alignmentX = Component.RIGHT_ALIGNMENT 69 | val root = UI.PanelFactory.grid() 70 | .add( 71 | UI.PanelFactory.panel(inputText) 72 | .withLabel("&Class Name:") 73 | ) 74 | .add( 75 | UI.PanelFactory.panel(errorText) 76 | ) 77 | .add( 78 | UI.PanelFactory.panel(JScrollPane(resultTable)) 79 | ) 80 | .add( 81 | UI.PanelFactory.panel(bottomPanel) 82 | ) 83 | .createPanel() 84 | val popup = JBPopupFactory.getInstance() 85 | .createComponentPopupBuilder(root, inputText) 86 | .also { 87 | it.setFocusable(true) 88 | it.setRequestFocus(true) 89 | it.setResizable(true) 90 | it.setTitle("Search Artifacts") 91 | it.setCancelOnClickOutside(true) 92 | it.setMovable(true) 93 | it.setShowBorder(true) 94 | it.addListener(object : JBPopupListener { 95 | override fun onClosed(event: LightweightWindowEvent) { 96 | scope.cancel() 97 | } 98 | }) 99 | } 100 | .createPopup().apply { 101 | showCenteredInCurrentWindow(project) 102 | } 103 | 104 | popup.setMinimumSize(Dimension(800, 600)) 105 | 106 | resultTable.getColumn(SearchResultTableModel.COL_ADD_DEPENDENCY).cellRenderer = 107 | ButtonRenderer(icon = AllIcons.General.Add) 108 | 109 | resultTable.getColumn(SearchResultTableModel.COL_ADD_DEPENDENCY).cellEditor = 110 | VersionPopupRenderer( 111 | project = project, 112 | currentModule = module 113 | ) { 114 | popup.cancel() 115 | } 116 | SearchWindowController( 117 | scope = scope, 118 | initialText = initialText, 119 | inputText = inputText, 120 | errorText = errorText, 121 | resultTable = resultTable, 122 | searchResultModel = searchResultModel, 123 | helpButton = helpButton, 124 | popup = popup 125 | ).startStream() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /maven/src/test/kotlin/com/birbit/artifactfinder/maven/MavenFetcherTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.maven 18 | 19 | import com.birbit.artifactfinder.maven.vo.ArtifactMetadata 20 | import com.birbit.artifactfinder.maven.vo.GroupIndex 21 | import com.birbit.artifactfinder.maven.vo.Versioning 22 | import com.birbit.artifactfinder.vo.Artifactory 23 | import com.google.common.truth.Truth.assertThat 24 | import kotlinx.coroutines.runBlocking 25 | import okhttp3.mockwebserver.MockResponse 26 | import okhttp3.mockwebserver.MockWebServer 27 | import okhttp3.mockwebserver.RecordedRequest 28 | import okio.buffer 29 | import okio.source 30 | import org.junit.Test 31 | import org.junit.runner.RunWith 32 | import org.junit.runners.JUnit4 33 | 34 | @RunWith(JUnit4::class) 35 | class MavenFetcherTest { 36 | private val resourceDispatcher = object : okhttp3.mockwebserver.Dispatcher() { 37 | override fun dispatch(request: RecordedRequest): MockResponse { 38 | fun loadResource(name: String): String { 39 | return MavenFetcherTest::class.java.getResourceAsStream("/response/$name") 40 | .source() 41 | .buffer() 42 | .readUtf8() 43 | } 44 | return when (request.path!!.substring(1)) { 45 | MavenApi.MASTER_INDEX -> MockResponse() 46 | .setResponseCode(200) 47 | .setBody(loadResource("master-index.xml")) 48 | "foo/bar/${MavenApi.GROUP_INDEX}" -> MockResponse() 49 | .setResponseCode(200) 50 | .setBody(loadResource("group-index.xml")) 51 | "foo/bar/artifact1/${MavenApi.MAVEN_METADATA}" -> MockResponse() 52 | .setResponseCode(200) 53 | .setBody(loadResource("maven-metadata.xml")) 54 | "foo/bar/artifact1/2.2.0-rc01/artifact1-2.2.0-rc01.jar" -> MockResponse() 55 | .setResponseCode(200) 56 | .setBody("jarContent") 57 | "foo/bar/artifact-2/2.2.0-rc01/artifact-2-2.2.0-rc01.aar" -> MockResponse() 58 | .setResponseCode(200) 59 | .setBody("aarContent") 60 | else -> MockResponse() 61 | .setResponseCode(404) 62 | .setBody("what is ${request.path}") 63 | } 64 | } 65 | } 66 | 67 | val mockServer = MockWebServer().also { 68 | it.dispatcher = resourceDispatcher 69 | } 70 | val fetcher = MavenFetcher( 71 | artifactory = Artifactory.GOOGLE, 72 | baseUrl = mockServer.url("/") 73 | ) 74 | 75 | @Test 76 | fun fetchMasterIndex() = runBlocking { 77 | val masterIndex = fetcher.fetchPackages() 78 | assertThat(masterIndex).containsExactly( 79 | "androidx.foo.bar", 80 | "androidx.baz.bar" 81 | ) 82 | } 83 | 84 | @Test 85 | fun fetchGroupIndex() = runBlocking { 86 | val groupIndex = fetcher.fetchGroupIndex("foo.bar") 87 | assertThat(groupIndex).isEqualTo( 88 | GroupIndex( 89 | groupId = "foo.bar", 90 | artifactIds = setOf("artifact1", "artifact-2") 91 | ) 92 | ) 93 | } 94 | 95 | @Test 96 | fun fetchArtifactMetadata() = runBlocking { 97 | val metadata = fetcher.fetchArtifactMetadata("foo.bar", "artifact1") 98 | assertThat(metadata).isEqualTo( 99 | ArtifactMetadata( 100 | groupId = "foo.bar", 101 | artifactId = "artifact1", 102 | versioning = Versioning( 103 | latest = "2.2.0-rc01", 104 | release = "2.2.0-rc01", 105 | versions = setOf( 106 | "2.0.0-alpha1", "2.0.0-beta01", "2.0.0-rc01", "2.0.0", "2.1.0-alpha01" 107 | ) 108 | ) 109 | ) 110 | ) 111 | } 112 | 113 | @Test 114 | fun fetchArtifact_jar() = runBlocking { 115 | val jar = fetcher.fetchArtifact( 116 | groupId = "foo.bar", 117 | artifactId = "artifact1", 118 | version = "2.2.0-rc01" 119 | ) 120 | assertThat(jar.inputStream.source().buffer().readUtf8()).isEqualTo("jarContent") 121 | } 122 | 123 | @Test 124 | fun fetchArtifact_aar() = runBlocking { 125 | val aar = fetcher.fetchArtifact( 126 | groupId = "foo.bar", 127 | artifactId = "artifact-2", 128 | version = "2.2.0-rc01" 129 | ) 130 | assertThat(aar.inputStream.source().buffer().readUtf8()).isEqualTo("aarContent") 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /artifactfinder/src/test/kotlin/com/birbit/artifactfinder/VersionSelectorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder 18 | 19 | import com.birbit.artifactfinder.VersionSelector.selectVersions 20 | import com.birbit.artifactfinder.model.Version 21 | import com.google.common.truth.Truth.assertThat 22 | import org.junit.Test 23 | import org.junit.runner.RunWith 24 | import org.junit.runners.JUnit4 25 | 26 | @RunWith(JUnit4::class) 27 | class VersionSelectorTest { 28 | 29 | @Test 30 | fun empty() { 31 | assertThat(selectVersions(emptyList())).isEmpty() 32 | } 33 | 34 | @Test 35 | fun oneOfAny() { 36 | listOf( 37 | V_1_1_0, 38 | V_1_1_0_alpha01, 39 | V_1_1_0_beta01, 40 | V_1_1_0_rc01 41 | ).forEach { 42 | assertThat( 43 | selectVersions( 44 | listOf(it) 45 | ) 46 | ).containsExactly(it) 47 | } 48 | } 49 | 50 | @Test 51 | fun threeStable() { 52 | assertThat( 53 | selectVersions( 54 | listOf( 55 | V_1_0_0, 56 | V_2_0_0, 57 | V_1_1_0, 58 | V_1_2_0 59 | ) 60 | ) 61 | ).containsExactly( 62 | V_2_0_0, 63 | V_1_2_0, 64 | V_1_1_0 65 | ) 66 | } 67 | 68 | @Test 69 | fun nonStable_hasStable() { 70 | assertThat( 71 | selectVersions( 72 | listOf( 73 | V_1_1_0, 74 | V_1_1_0_alpha01, 75 | V_1_1_0_alpha02, 76 | V_1_1_0_beta01, 77 | V_1_1_0_beta02, 78 | V_1_1_0_rc01, 79 | V_1_1_0_rc02 80 | ) 81 | ) 82 | ).containsExactly( 83 | V_1_1_0 84 | ) 85 | } 86 | 87 | @Test 88 | fun nonStable_hasBetterStable() { 89 | assertThat( 90 | selectVersions( 91 | listOf( 92 | V_1_2_0, 93 | V_1_1_0_alpha01, 94 | V_1_1_0_alpha02, 95 | V_1_1_0_beta01, 96 | V_1_1_0_beta02, 97 | V_1_1_0_rc01, 98 | V_1_1_0_rc02 99 | ) 100 | ) 101 | ).containsExactly( 102 | V_1_2_0 103 | ) 104 | } 105 | 106 | @Test 107 | fun nonStable_hasWorseStable_rc() { 108 | assertThat( 109 | selectVersions( 110 | listOf( 111 | V_1_0_0, 112 | V_1_1_0_alpha01, 113 | V_1_1_0_alpha02, 114 | V_1_1_0_beta01, 115 | V_1_1_0_beta02, 116 | V_1_1_0_rc01, 117 | V_1_1_0_rc02 118 | ) 119 | ) 120 | ).containsExactly( 121 | V_1_0_0, 122 | V_1_1_0_rc02 123 | ) 124 | } 125 | 126 | @Test 127 | fun nonStable_hasWorseStable_beta() { 128 | assertThat( 129 | selectVersions( 130 | listOf( 131 | V_1_0_0, 132 | V_1_1_0_alpha01, 133 | V_1_1_0_alpha02, 134 | V_1_1_0_beta01, 135 | V_1_1_0_beta02 136 | ) 137 | ) 138 | ).containsExactly( 139 | V_1_0_0, 140 | V_1_1_0_beta02 141 | ) 142 | } 143 | 144 | @Test 145 | fun nonStable_hasWorseStable_alpha() { 146 | assertThat( 147 | selectVersions( 148 | listOf( 149 | V_1_0_0, 150 | V_1_1_0_alpha01, 151 | V_1_1_0_alpha02 152 | ) 153 | ) 154 | ).containsExactly( 155 | V_1_0_0, 156 | V_1_1_0_alpha02 157 | ) 158 | } 159 | 160 | companion object { 161 | val V_1_0_0 = Version.fromString("1.0.0")!! 162 | val V_1_1_0 = Version.fromString("1.1.0")!! 163 | val V_1_2_0 = Version.fromString("1.2.0")!! 164 | val V_2_0_0 = Version.fromString("2.0.0")!! 165 | val V_1_1_0_alpha01 = Version.fromString("1.1.0-alpha01")!! 166 | val V_1_1_0_alpha02 = Version.fromString("1.1.0-alpha02")!! 167 | val V_1_1_0_beta01 = Version.fromString("1.1.0-beta01")!! 168 | val V_1_1_0_beta02 = Version.fromString("1.1.0-beta02")!! 169 | val V_1_1_0_rc01 = Version.fromString("1.1.0-rc01")!! 170 | val V_1_1_0_rc02 = Version.fromString("1.1.0-rc02")!! 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /model/src/test/kotlin/com/birbit/artifactfinder/model/db/ConnectionProviderTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.birbit.artifactfinder.model.db 18 | 19 | import com.google.common.truth.Truth.assertThat 20 | import java.sql.DriverManager 21 | import kotlinx.coroutines.ExperimentalCoroutinesApi 22 | import kotlinx.coroutines.async 23 | import kotlinx.coroutines.awaitAll 24 | import kotlinx.coroutines.delay 25 | import kotlinx.coroutines.test.TestCoroutineScope 26 | import kotlinx.coroutines.test.runBlockingTest 27 | import org.junit.Test 28 | import org.junit.runner.RunWith 29 | import org.junit.runners.JUnit4 30 | 31 | @ExperimentalCoroutinesApi 32 | @RunWith(JUnit4::class) 33 | class ConnectionProviderTest { 34 | 35 | private val testScope = TestCoroutineScope() 36 | 37 | private val provider = ConnectionProvider( 38 | limit = 3, 39 | createConnection = ::createConnection, 40 | initFirstDb = ::initFirstDb 41 | ) 42 | 43 | @Test 44 | fun parallelReads() = testScope.runBlockingTest { 45 | val results = (1..3).map { 46 | async { 47 | provider.read { 48 | delay(100) 49 | now() 50 | } 51 | } 52 | } 53 | val items = results.awaitAll().toList() 54 | println(items) 55 | assertThat(items).isEqualTo(listOf(100, 100, 100)) 56 | assertThat(now()).isEqualTo(100) 57 | } 58 | 59 | @Test 60 | fun parallelReads_moreThanCapacity() = testScope.runBlockingTest { 61 | val results = (1..4).map { 62 | async { 63 | provider.read { 64 | delay(100) 65 | now() 66 | } 67 | } 68 | } 69 | val res = results.awaitAll().toSet() 70 | assertThat(res).isEqualTo(setOf(100, 100, 100, 200)) 71 | assertThat(now()).isEqualTo(200) 72 | } 73 | 74 | @Test 75 | fun parallelWrites() = testScope.runBlockingTest { 76 | val results = (1..3).map { 77 | async { 78 | provider.write { 79 | delay(100) 80 | now() 81 | } 82 | } 83 | } 84 | assertThat(results.awaitAll().toSet()).isEqualTo(setOf(100, 200, 300)) 85 | assertThat(now()).isEqualTo(300) 86 | } 87 | 88 | @Test 89 | fun parallelWrites_moreThanCapacity() = testScope.runBlockingTest { 90 | val results = (1..4).map { 91 | async { 92 | provider.write { 93 | delay(100) 94 | now() 95 | } 96 | } 97 | } 98 | assertThat(results.awaitAll().toSet()).isEqualTo(setOf(100, 200, 300, 400)) 99 | assertThat(now()).isEqualTo(400) 100 | } 101 | 102 | @Test 103 | fun parallelReadsUpgradeToWrites() = testScope.runBlockingTest { 104 | val results = (1..3).map { 105 | async { 106 | provider.read { 107 | delay(100) 108 | provider.write { 109 | delay(5) 110 | now() 111 | } 112 | } 113 | } 114 | } 115 | assertThat(results.awaitAll().toSet()).isEqualTo(setOf(105, 110, 115)) 116 | assertThat(now()).isEqualTo(115) 117 | } 118 | 119 | @Test 120 | fun parallelReadsUpgradeToWrites_moreThanCapacity() = testScope.runBlockingTest { 121 | val results = (1..5).map { 122 | async { 123 | provider.read { 124 | delay(100) 125 | provider.write { 126 | delay(5) 127 | now() 128 | } 129 | } 130 | } 131 | } 132 | assertThat(results.awaitAll().toSet()).isEqualTo(setOf(105, 110, 115, 210, 215)) 133 | // 3 readers: 100 ms 134 | // 1 writes : 105, another reader comes at 105 135 | // another writes: 110, another reader comes at 110 136 | // another writes: 115 137 | // first available writes, 205 + 5 = 210 138 | // second available writes, 210 + 5 = 215 139 | assertThat(now()).isEqualTo(215) 140 | } 141 | 142 | private fun now() = testScope.currentTime.toInt() 143 | 144 | @Suppress("RedundantSuspendModifier") 145 | private suspend fun createConnection(): WritableDbDriver { 146 | return JdbcWriteableDbDriver(DriverManager.getConnection("jdbc:sqlite::memory:")) 147 | } 148 | 149 | @Suppress("RedundantSuspendModifier") 150 | private suspend fun initFirstDb(driver: WritableDbDriver) { 151 | ArtifactFinderDb.createAllTables(driver) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | --------------------------------------------------------------------------------