Repository: sdaschner/jaxrs-analyzer Branch: master Commit: 4ac62942202d Files: 317 Total size: 972.9 KB Directory structure: gitextract_st1raaxs/ ├── .gitignore ├── Changelog.adoc ├── Documentation.adoc ├── LICENSE ├── README.adoc ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── sebastian_daschner/ │ │ └── jaxrs_analyzer/ │ │ ├── JAXRSAnalyzer.java │ │ ├── LogProvider.java │ │ ├── Main.java │ │ ├── analysis/ │ │ │ ├── JobRegistry.java │ │ │ ├── ProjectAnalyzer.java │ │ │ ├── bytecode/ │ │ │ │ ├── BytecodeAnalyzer.java │ │ │ │ ├── MethodContentAnalyzer.java │ │ │ │ ├── ResourceMethodContentAnalyzer.java │ │ │ │ ├── SubResourceLocatorMethodContentAnalyzer.java │ │ │ │ ├── collection/ │ │ │ │ │ └── InstructionBuilder.java │ │ │ │ ├── reduction/ │ │ │ │ │ ├── InstructionFinder.java │ │ │ │ │ ├── RelevantInstructionReducer.java │ │ │ │ │ └── StackSizeSimulator.java │ │ │ │ └── simulation/ │ │ │ │ ├── InjectableArgumentMethodSimulator.java │ │ │ │ ├── KnownJsonResultMethod.java │ │ │ │ ├── KnownResponseResultMethod.java │ │ │ │ ├── MethodPool.java │ │ │ │ └── MethodSimulator.java │ │ │ ├── classes/ │ │ │ │ ├── ContextClassReader.java │ │ │ │ ├── JAXRSAnnotatedSuperMethodClassVisitor.java │ │ │ │ ├── JAXRSAnnotatedSuperMethodVisitor.java │ │ │ │ ├── JAXRSClassVisitor.java │ │ │ │ ├── JAXRSFieldVisitor.java │ │ │ │ ├── JAXRSMethodVisitor.java │ │ │ │ ├── ProjectMethodClassVisitor.java │ │ │ │ ├── ProjectMethodVisitor.java │ │ │ │ └── annotation/ │ │ │ │ ├── ApplicationPathAnnotationVisitor.java │ │ │ │ ├── ClassAndMethodAnnotationVisitor.java │ │ │ │ ├── ConsumesAnnotationVisitor.java │ │ │ │ ├── DefaultValueAnnotationVisitor.java │ │ │ │ ├── ParamAnnotationVisitor.java │ │ │ │ ├── PathAnnotationVisitor.java │ │ │ │ ├── ProducesAnnotationVisitor.java │ │ │ │ └── ValueAnnotationVisitor.java │ │ │ ├── javadoc/ │ │ │ │ ├── JavaDocAnalyzer.java │ │ │ │ ├── JavaDocParserVisitor.java │ │ │ │ └── ResponseCommentExtractor.java │ │ │ └── results/ │ │ │ ├── DynamicTypeAnalyzer.java │ │ │ ├── JavaDocParameterResolver.java │ │ │ ├── JavaTypeAnalyzer.java │ │ │ ├── JsonMapper.java │ │ │ ├── PathNormalizer.java │ │ │ ├── ResponseTypeNormalizer.java │ │ │ ├── ResultInterpreter.java │ │ │ └── StringParameterResolver.java │ │ ├── backend/ │ │ │ ├── Backend.java │ │ │ ├── ComparatorUtils.java │ │ │ ├── JsonRepresentationAppender.java │ │ │ ├── StringBackend.java │ │ │ ├── asciidoc/ │ │ │ │ └── AsciiDocBackend.java │ │ │ ├── markdown/ │ │ │ │ └── MarkdownBackend.java │ │ │ ├── plaintext/ │ │ │ │ └── PlainTextBackend.java │ │ │ └── swagger/ │ │ │ ├── DefinitionNameBuilder.java │ │ │ ├── SchemaBuilder.java │ │ │ ├── SwaggerBackend.java │ │ │ ├── SwaggerOptions.java │ │ │ └── SwaggerScheme.java │ │ ├── model/ │ │ │ ├── JavaUtils.java │ │ │ ├── Types.java │ │ │ ├── elements/ │ │ │ │ ├── Element.java │ │ │ │ ├── HttpResponse.java │ │ │ │ ├── JsonArray.java │ │ │ │ ├── JsonObject.java │ │ │ │ ├── JsonValue.java │ │ │ │ └── MethodHandle.java │ │ │ ├── instructions/ │ │ │ │ ├── DefaultInstruction.java │ │ │ │ ├── DupInstruction.java │ │ │ │ ├── ExceptionHandlerInstruction.java │ │ │ │ ├── GetFieldInstruction.java │ │ │ │ ├── GetPropertyInstruction.java │ │ │ │ ├── GetStaticInstruction.java │ │ │ │ ├── Instruction.java │ │ │ │ ├── InvokeDynamicInstruction.java │ │ │ │ ├── InvokeInstruction.java │ │ │ │ ├── LoadInstruction.java │ │ │ │ ├── LoadStoreInstruction.java │ │ │ │ ├── LoadStoreInstructionPlaceholder.java │ │ │ │ ├── NewInstruction.java │ │ │ │ ├── PushInstruction.java │ │ │ │ ├── ReturnInstruction.java │ │ │ │ ├── SizeChangingInstruction.java │ │ │ │ ├── StoreInstruction.java │ │ │ │ └── ThrowInstruction.java │ │ │ ├── javadoc/ │ │ │ │ ├── ClassComment.java │ │ │ │ ├── MemberComment.java │ │ │ │ ├── MemberParameterTag.java │ │ │ │ └── MethodComment.java │ │ │ ├── methods/ │ │ │ │ ├── IdentifiableMethod.java │ │ │ │ ├── Method.java │ │ │ │ ├── MethodIdentifier.java │ │ │ │ └── ProjectMethod.java │ │ │ ├── rest/ │ │ │ │ ├── HttpMethod.java │ │ │ │ ├── MethodParameter.java │ │ │ │ ├── ParameterType.java │ │ │ │ ├── Project.java │ │ │ │ ├── ResourceMethod.java │ │ │ │ ├── Resources.java │ │ │ │ ├── Response.java │ │ │ │ ├── TypeIdentifier.java │ │ │ │ ├── TypeRepresentation.java │ │ │ │ └── TypeRepresentationVisitor.java │ │ │ └── results/ │ │ │ ├── ClassResult.java │ │ │ └── MethodResult.java │ │ └── utils/ │ │ ├── DebugUtils.java │ │ ├── Pair.java │ │ └── StringUtils.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── com.sebastian_daschner.jaxrs_analyzer.backend.Backend └── test/ ├── java/ │ └── com/ │ └── sebastian_daschner/ │ ├── jaxrs_analyzer/ │ │ ├── MainTest.java │ │ ├── analysis/ │ │ │ ├── ProjectAnalyzerTest.java │ │ │ ├── bytecode/ │ │ │ │ ├── SubResourceLocatorMethodContentAnalyzerTest.java │ │ │ │ ├── collection/ │ │ │ │ │ ├── ByteCodeCollectorTest.java │ │ │ │ │ └── testclasses/ │ │ │ │ │ ├── TestClass1.java │ │ │ │ │ ├── TestClass2.java │ │ │ │ │ ├── TestClass3.java │ │ │ │ │ ├── TestClass4.java │ │ │ │ │ ├── TestClass5.java │ │ │ │ │ ├── TestClass6.java │ │ │ │ │ ├── TestClass7.java │ │ │ │ │ ├── TestClass8.java │ │ │ │ │ └── TestClass9.java │ │ │ │ ├── reduction/ │ │ │ │ │ ├── RelevantInstructionReducerTest.java │ │ │ │ │ └── testclasses/ │ │ │ │ │ ├── TestClass1.java │ │ │ │ │ ├── TestClass10.java │ │ │ │ │ ├── TestClass2.java │ │ │ │ │ ├── TestClass3.java │ │ │ │ │ ├── TestClass4.java │ │ │ │ │ ├── TestClass5.java │ │ │ │ │ ├── TestClass6.java │ │ │ │ │ ├── TestClass7.java │ │ │ │ │ ├── TestClass8.java │ │ │ │ │ └── TestClass9.java │ │ │ │ └── subresource/ │ │ │ │ ├── TestClass1.java │ │ │ │ ├── TestClass2.java │ │ │ │ ├── TestClass3.java │ │ │ │ ├── TestClass4.java │ │ │ │ └── TestClass5.java │ │ │ ├── classes/ │ │ │ │ ├── JAXRSMethodVisitorTest.java │ │ │ │ ├── ResourceMethodContentAnalyzerTest.java │ │ │ │ └── testclasses/ │ │ │ │ └── resource/ │ │ │ │ ├── json/ │ │ │ │ │ ├── TestClass1.java │ │ │ │ │ ├── TestClass10.java │ │ │ │ │ ├── TestClass2.java │ │ │ │ │ ├── TestClass3.java │ │ │ │ │ ├── TestClass4.java │ │ │ │ │ ├── TestClass5.java │ │ │ │ │ ├── TestClass6.java │ │ │ │ │ ├── TestClass7.java │ │ │ │ │ ├── TestClass8.java │ │ │ │ │ └── TestClass9.java │ │ │ │ ├── object/ │ │ │ │ │ ├── TestClass1.java │ │ │ │ │ ├── TestClass10.java │ │ │ │ │ ├── TestClass11.java │ │ │ │ │ ├── TestClass12.java │ │ │ │ │ ├── TestClass13.java │ │ │ │ │ ├── TestClass14.java │ │ │ │ │ ├── TestClass2.java │ │ │ │ │ ├── TestClass3.java │ │ │ │ │ ├── TestClass4.java │ │ │ │ │ ├── TestClass5.java │ │ │ │ │ ├── TestClass6.java │ │ │ │ │ ├── TestClass7.java │ │ │ │ │ ├── TestClass8.java │ │ │ │ │ └── TestClass9.java │ │ │ │ └── response/ │ │ │ │ ├── TestClass1.java │ │ │ │ ├── TestClass10.java │ │ │ │ ├── TestClass11.java │ │ │ │ ├── TestClass12.java │ │ │ │ ├── TestClass13.java │ │ │ │ ├── TestClass14.java │ │ │ │ ├── TestClass15.java │ │ │ │ ├── TestClass16.java │ │ │ │ ├── TestClass17.java │ │ │ │ ├── TestClass18.java │ │ │ │ ├── TestClass19.java │ │ │ │ ├── TestClass2.java │ │ │ │ ├── TestClass20.java │ │ │ │ ├── TestClass21.java │ │ │ │ ├── TestClass22.java │ │ │ │ ├── TestClass23.java │ │ │ │ ├── TestClass24.java │ │ │ │ ├── TestClass25.java │ │ │ │ ├── TestClass26.java │ │ │ │ ├── TestClass27.java │ │ │ │ ├── TestClass28.java │ │ │ │ ├── TestClass29.java │ │ │ │ ├── TestClass3.java │ │ │ │ ├── TestClass30.java │ │ │ │ ├── TestClass31.java │ │ │ │ ├── TestClass32.java │ │ │ │ ├── TestClass33.java │ │ │ │ ├── TestClass34.java │ │ │ │ ├── TestClass35.java │ │ │ │ ├── TestClass36.java │ │ │ │ ├── TestClass37.java │ │ │ │ ├── TestClass38.java │ │ │ │ ├── TestClass39.java │ │ │ │ ├── TestClass4.java │ │ │ │ ├── TestClass40.java │ │ │ │ ├── TestClass41.java │ │ │ │ ├── TestClass42.java │ │ │ │ ├── TestClass43.java │ │ │ │ ├── TestClass44.java │ │ │ │ ├── TestClass45.java │ │ │ │ ├── TestClass46.java │ │ │ │ ├── TestClass47.java │ │ │ │ ├── TestClass48.java │ │ │ │ ├── TestClass49.java │ │ │ │ ├── TestClass5.java │ │ │ │ ├── TestClass50.java │ │ │ │ ├── TestClass51.java │ │ │ │ ├── TestClass52.java │ │ │ │ ├── TestClass53.java │ │ │ │ ├── TestClass54.java │ │ │ │ ├── TestClass55.java │ │ │ │ ├── TestClass56.java │ │ │ │ ├── TestClass57.java │ │ │ │ ├── TestClass58.java │ │ │ │ ├── TestClass59.java │ │ │ │ ├── TestClass6.java │ │ │ │ ├── TestClass60.java │ │ │ │ ├── TestClass61.java │ │ │ │ ├── TestClass62.java │ │ │ │ ├── TestClass7.java │ │ │ │ ├── TestClass8.java │ │ │ │ └── TestClass9.java │ │ │ ├── javadoc/ │ │ │ │ └── ResponseCommentExtractorTest.java │ │ │ ├── project/ │ │ │ │ └── classes/ │ │ │ │ ├── ClassAnalyzerTest.java │ │ │ │ └── testclasses/ │ │ │ │ ├── TestClass1.java │ │ │ │ ├── TestClass10.java │ │ │ │ ├── TestClass11.java │ │ │ │ ├── TestClass12.java │ │ │ │ ├── TestClass13.java │ │ │ │ ├── TestClass14.java │ │ │ │ ├── TestClass15.java │ │ │ │ ├── TestClass16.java │ │ │ │ ├── TestClass2.java │ │ │ │ ├── TestClass3.java │ │ │ │ ├── TestClass4.java │ │ │ │ ├── TestClass5.java │ │ │ │ ├── TestClass6.java │ │ │ │ ├── TestClass7.java │ │ │ │ ├── TestClass8.java │ │ │ │ └── TestClass9.java │ │ │ ├── results/ │ │ │ │ ├── DynamicTypeAnalyzerTest.java │ │ │ │ ├── JavaTypeAnalyzerTest.java │ │ │ │ ├── PathNormalizerTest.java │ │ │ │ ├── ResultInterpreterTest.java │ │ │ │ ├── TypeUtils.java │ │ │ │ └── testclasses/ │ │ │ │ └── typeanalyzer/ │ │ │ │ ├── TestClass1.java │ │ │ │ ├── TestClass10.java │ │ │ │ ├── TestClass11.java │ │ │ │ ├── TestClass12.java │ │ │ │ ├── TestClass13.java │ │ │ │ ├── TestClass14.java │ │ │ │ ├── TestClass15.java │ │ │ │ ├── TestClass16.java │ │ │ │ ├── TestClass17.java │ │ │ │ ├── TestClass18.java │ │ │ │ ├── TestClass19.java │ │ │ │ ├── TestClass2.java │ │ │ │ ├── TestClass20.java │ │ │ │ ├── TestClass21.java │ │ │ │ ├── TestClass22.java │ │ │ │ ├── TestClass23.java │ │ │ │ ├── TestClass24.java │ │ │ │ ├── TestClass25.java │ │ │ │ ├── TestClass26.java │ │ │ │ ├── TestClass27.java │ │ │ │ ├── TestClass28.java │ │ │ │ ├── TestClass29.java │ │ │ │ ├── TestClass3.java │ │ │ │ ├── TestClass30.java │ │ │ │ ├── TestClass4.java │ │ │ │ ├── TestClass5.java │ │ │ │ ├── TestClass6.java │ │ │ │ ├── TestClass7.java │ │ │ │ └── TestClass8.java │ │ │ └── utils/ │ │ │ ├── JavaUtilSignatureTest.java │ │ │ ├── JavaUtilsTest.java │ │ │ └── TestClassUtils.java │ │ ├── backend/ │ │ │ ├── JsonRepresentationAppenderTest.java │ │ │ ├── asciidoc/ │ │ │ │ └── AsciiDocBackendTest.java │ │ │ ├── markdown/ │ │ │ │ └── MarkdownBackendTest.java │ │ │ ├── plaintext/ │ │ │ │ └── PlainTextBackendTest.java │ │ │ └── swagger/ │ │ │ ├── SchemaBuilderTest.java │ │ │ ├── SwaggerBackendTest.java │ │ │ └── TypeIdentifierTestSupport.java │ │ ├── builder/ │ │ │ ├── ClassResultBuilder.java │ │ │ ├── HttpResponseBuilder.java │ │ │ ├── MethodResultBuilder.java │ │ │ ├── ResourceMethodBuilder.java │ │ │ ├── ResourcesBuilder.java │ │ │ └── ResponseBuilder.java │ │ └── model/ │ │ ├── rest/ │ │ │ └── CollectionTypeRepresentationTest.java │ │ └── types/ │ │ └── TypeTest.java │ └── test/ │ ├── Enumeration.java │ └── Model.java └── jaxrs-test/ └── com/ └── sebastian_daschner/ └── jaxrs_test/ ├── AbstractResources.java ├── ComplexResources.java ├── Enumeration.java ├── IgnoredTestResources.java ├── JsonResources.java ├── Manager.java ├── Model.java ├── ResourceWithoutClassLevelJavadoc.java ├── Resources.java ├── RestActivator.java ├── SomeSubResource.java ├── SubResources.java ├── Test.java ├── TestResources.java └── TestStore.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties dependency-reduced-pom.xml *.iml .idea/ *.class ================================================ FILE: Changelog.adoc ================================================ = Changelog Sebastian Daschner // new versions are placed on the top == v0.18 SNAPSHOT == v0.17 - Markdown support - Added resource description in remaining backends - Support to ignore JAX-RS boundary classes - fixed minor JavaDoc issues == v0.16 - Added AsciiDoc prettification - Fixed error on empty application path - Minor fixes == v0.15 - Fixed JavaDoc erased types == v0.14 - Fixed test related execution phase == v0.13 - Improved class loading functionality while analyzing - Support empty domain (https://github.com/sdaschner/jaxrs-analyzer/issues/42[#42^]) - Output dir is now editable in maven plugin (https://github.com/sdaschner/jaxrs-analyzer-maven-plugin/issues/13[#13^]) - Ignore properties in representations via Jackson annotations (https://github.com/sdaschner/jaxrs-analyzer/issues/87[#87^]) == v0.12 - Fixed JavaDoc related class loading == v0.11 - Added JavaDoc analysis for JAX-RS resources - Added backend SPI - Improved path parameter resolution for extended path regexes - Fixed local variable analysis for corner cases == v0.10 - Changed internals to ASM - Improved type analysis - Improved analysis of POJO type inheritance - Supported Swagger tags - Added option to change Swagger schemes - Supported `@DefaultValue` - Improved entity body support for "`Stringifiable`" types - Changed Swagger output to pretty printed JSON == v0.9 - Enhanced type resolution for generic types and generic methods - Improved Swagger type output - Added actual type names to Swagger output - Sorted Swagger JSON == v0.8 - Improved type resolution for type arguments - Fixed errors in swagger specification - Fixed Maven plugin error on missing Java EE 7 dependency == v0.7 - Restructured Java type representation - Improved type resolution for nested JSR-353 calls - Improved type analysis for nested & recursive types & methods - Improved method resolution on inherited types - Fixed CLI calls with relative paths == v0.6 - Fixed class loading issue on Windows systems - Fixed potential error on invoke interface in nested methods - Added type workaround for Map request / response body types - Fixed potential error when using `Stream#collect` == v0.5 - Improved handling of abstract JAX-RS methods - Added more functionality to the CLI tool (e.g. ability to specify several class paths) - Changed CLI tool interface to convenient Unix-style parameters - Added more log information -- especially on debug level == v0.4 - Added AsciiDoc backend - Improved variable type resolution - Added WebApplicationException handling (thrown WAE's in the code are recognized for the result) - Improved `void` method analysis - Added project information (name, version, etc.) to backends == v0.3 - Improved POJO getter analysis for return types (e.g. isXY() -> boolean, etc.) - Added JDK 1.8 Streams as known methods (analysis will notice and simulate these) - Improved JAXB analysis of POJOs (all `XmlAccessorType's`, `XmlElement` and `XmlTransient` supported) == v0.2 - Minor updated needed for Sonatype release == v0.1 - JAX-RS 2.0 annotations analysis - `Response` return types analysis (determines where the returned objects ``come from'', follows method invocations, actual arguments, etc.) - JSON-P API analysis (e.g. methods with return type `JsonObject` or JSON-P contained in `Response#entity()`) - Analysis of POJO responses - JAXB analysis of POJOs (only `XmlAccessorType#PUBLIC_MEMBER`) - JDK 1.8 lambdas analysis - Swagger API JSON backend format - Plain text backend format ================================================ FILE: Documentation.adoc ================================================ = JAX-RS Analyzer Sebastian Daschner The JAX-RS Analyzer generates an overview of all JAX-RS resources in a JavaEE project. Besides other approaches this tool uses Bytecode analysis to maximise the extracted information. The Analyzer is available as https://github.com/sdaschner/jaxrs-analyzer-maven-plugin/[Maven plugin] or standalone version (executable jar file). == Quickstart (tl;dr) Just add the latest release version of the JAX-RS Analyzer to your Maven pom.xml in the ``-section: ---- com.sebastian-daschner jaxrs-analyzer-maven-plugin 0.17 analyze-jaxrs swagger ---- For an introductional video see https://blog.sebastian-daschner.com/entries/jaxrs_analyzer_explained_video[JAX-RS Analyzer explained (Video)]. == Maven Plugin Please see the https://github.com/sdaschner/jaxrs-analyzer-maven-plugin[Maven plugin project] and a https://github.com/sdaschner/jaxrs-analyzer-maven-plugin/blob/master/Documentation.adoc[documentation] of all parameters. == Standalone Instead of using the Maven plugin, the tool can also run directly from the jar file. You can download the latest version https://github.com/sdaschner/jaxrs-analyzer/releases[here]. Alternatively the executable can be build with `mvn clean install`. Run the jar file with `java -jar jaxrs-analyzer.jar [options] [projectPathToCompiledClasses...]`, e.g. `java -jar jaxrs-analyzer.jar -b swagger ../yourProject/target/classes`. The `projectPath` entries may be directories or jar files containing the classes to be analyzed. Following available options: * `-b ` The backend to choose: `swagger` (default), `plaintext`, `asciidoc`, `markdown` * `-cp [:class paths...]` The additional class paths containing classes which are used in the project (separated by colon); this may be directories or jar-files * `-X` Debug enabled (prints error debugging information on Standard error out) * `-n ` The name of the project * `-v ` The version of the project * `-d ` The domain of the project * `-o ` The location of the analysis output (will be printed to standard out if omitted) Following available backend specific options (only have effect if the corresponding backend is selected): * `--swaggerSchemes [,schemes]` The Swagger schemes: `http` (default), `https`, `ws`, `wss` * `--renderSwaggerTags` Enables rendering of Swagger tags (default tag will be used per default) * `--swaggerTagsPathOffset ` The number at which path position the Swagger tags will be extracted (0 will be used per default) * `--ignoredRootResources ` JAX-RS root resource classes which should be ignored by analyze (empty per default). Note that these ignores only cause the classes to be ignored as root resources; they might still be taken into account as JAX-RS sub-resources. == Backends The Analyzer supports Plaintext, AsciiDoc, Markdown and Swagger as output format. The latter three can be processed further. AsciiDoc and Markdown are human-readable, lightweight markup languages. They can easily converted to PDF, HTML, and many more. The Swagger JSON file can be used with Swagger UI or the http://editor.swagger.io[Swagger Editor]. Swagger UI is a dependency-free collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API. == Changelog For the latest changes see the https://github.com/sdaschner/jaxrs-analyzer/blob/master/Changelog.adoc[Changelog]. == Contributing Feedback, bug reports and ideas for improvement are very welcome! Feel free to fork, comment, file an issue, etc. ;-) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2015 Sebastian Daschner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.adoc ================================================ = JAX-RS Analyzer Sebastian Daschner Generates an overview of all JAX-RS resources in a project by bytecode analysis. *Yet another such JAX-RS tool?* + Yes, but this gathers the information about the JAX-RS resource classes by bytecode analysis (not just by reflection). This gains more information in several situations. You don't need additional annotations on your JAX-RS resource methods. Using the JSR 339 standard is sufficient. Java 8 is needed for the Analyzer. For usage see the https://github.com/sdaschner/jaxrs-analyzer/blob/master/Documentation.adoc[documentation]. == Maven Plugin The Analyzer can be added to your project via https://github.com/sdaschner/jaxrs-analyzer-maven-plugin[Maven plugin]. == Gradle Plugin For `Gradle` based projects - third-party https://github.com/grimmjo/jaxrs-analyzer-gradle-plugin[Gradle plugin] could be used. == Standalone Instead of using the Maven plugin, the tool can also run directly from the jar file. You can download the latest version https://github.com/sdaschner/jaxrs-analyzer/releases[here]. == Backends The Analyzer supports Plaintext, AsciiDoc, Markdown and Swagger as output format. == Documentation / Feature list +...+ can be found https://github.com/sdaschner/jaxrs-analyzer/blob/master/Documentation.adoc[here]. Feedback, bug reports and ideas for improvement are very welcome! Feel free to fork, comment, file an issue, etc. ;-) ================================================ FILE: pom.xml ================================================ 4.0.0 com.sebastian-daschner jaxrs-analyzer 0.18-SNAPSHOT JAX-RS Analyzer jar Generates REST documentation by analysing JAX-RS projects. https://github.com/sdaschner/jaxrs-analyzer The Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt Sebastian Daschner sebastian-daschner.com https://www.sebastian-daschner.com org.glassfish javax.json 1.1 javax javaee-api 7.0 org.ow2.asm asm 6.2.1 org.ow2.asm asm-util 6.2.1 com.fasterxml.jackson.core jackson-annotations 2.8.5 junit junit 4.12 test org.mockito mockito-core 1.10.19 test net.jcip jcip-annotations 1.0 test com.github.javaparser javaparser-core 3.5.3 jaxrs-analyzer org.apache.maven.plugins maven-shade-plugin 2.4.3 package shade com.sebastian_daschner.jaxrs_analyzer.Main *:* com/sun/javadoc/** UTF-8 1.8 1.8 false scm:git:git@github.com:sdaschner/jaxrs-analyzer.git scm:git:git@github.com:sdaschner/jaxrs-analyzer.git scm:git:git@github.com:sdaschner/jaxrs-analyzer.git HEAD ossrh-snapshots https://oss.sonatype.org/content/repositories/snapshots/ ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2/ release-sign-artifacts performRelease true org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar-no-fork org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign org.apache.maven.plugins maven-release-plugin 2.5.3 v@{project.version} ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/JAXRSAnalyzer.java ================================================ package com.sebastian_daschner.jaxrs_analyzer; import com.sebastian_daschner.jaxrs_analyzer.analysis.ProjectAnalyzer; import com.sebastian_daschner.jaxrs_analyzer.backend.Backend; import com.sebastian_daschner.jaxrs_analyzer.model.rest.Project; import com.sebastian_daschner.jaxrs_analyzer.model.rest.Resources; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Path; import java.util.*; import java.util.stream.StreamSupport; /** * Generates REST documentation of JAX-RS projects automatically by bytecode analysis. * * @author Sebastian Daschner */ public class JAXRSAnalyzer { private final Analysis analysis; /** * Constructs a JAX-RS Analyzer. */ public JAXRSAnalyzer(Analysis analysis) { Objects.requireNonNull(analysis); Objects.requireNonNull(analysis.projectClassPaths); Objects.requireNonNull(analysis.projectSourcePaths); Objects.requireNonNull(analysis.classPaths); Objects.requireNonNull(analysis.projectName); Objects.requireNonNull(analysis.projectVersion); Objects.requireNonNull(analysis.backend); if (analysis.projectClassPaths.isEmpty()) throw new IllegalArgumentException("At least one project path is mandatory"); this.analysis = analysis; } /** * Analyzes the JAX-RS project at the class path and produces the output as configured. */ public void analyze() { final Resources resources = new ProjectAnalyzer(analysis.classPaths) .analyze(analysis.projectClassPaths, analysis.projectSourcePaths, analysis.ignoredResources); if (resources.isEmpty()) { LogProvider.info("Empty JAX-RS analysis result, omitting output"); return; } final Project project = new Project(analysis.projectName, analysis.projectVersion, resources); final byte[] output = analysis.backend.render(project); if (analysis.outputLocation != null) { outputToFile(output, analysis.outputLocation); } else { outputToConsole(output); } } private void outputToConsole(final byte[] output) { try { System.out.write(output); System.out.flush(); } catch (IOException e) { LogProvider.error("Could not write the output, reason: " + e.getMessage()); LogProvider.debug(e); } } private static void outputToFile(final byte[] output, final Path outputLocation) { try (final OutputStream stream = new FileOutputStream(outputLocation.toFile())) { stream.write(output); stream.flush(); } catch (IOException e) { LogProvider.error("Could not write to the specified output location, reason: " + e.getMessage()); LogProvider.debug(e); } } public static Backend constructBackend(final String backendType) { final ServiceLoader backends = ServiceLoader.load(Backend.class); return StreamSupport.stream(backends.spliterator(), false) .filter(b -> backendType.equalsIgnoreCase(b.getName())) .findAny() .orElseThrow(() -> new IllegalArgumentException("Unknown backend type " + backendType)); } public static class Analysis { private final Set projectClassPaths = new HashSet<>(); private final Set projectSourcePaths = new HashSet<>(); private final Set classPaths = new HashSet<>(); private final Set ignoredResources = new HashSet<>(); private String projectName; private String projectVersion; private Path outputLocation; private Backend backend; public Set getProjectClassPaths() { return projectClassPaths; } public void addProjectClassPath(Path classPath) { projectClassPaths.add(classPath); } public void addProjectSourcePath(Path sourcePath) { projectSourcePaths.add(sourcePath); } public void addClassPath(Path classPath) { classPaths.add(classPath); } public void addIgnoredResource(String ignored) { ignoredResources.add(ignored); } public void configureBackend(Map attributes) { if (backend != null) backend.configure(attributes); } public void setProjectName(String projectName) { this.projectName = projectName; } public void setProjectVersion(String projectVersion) { this.projectVersion = projectVersion; } public void setOutputLocation(Path outputLocation) { this.outputLocation = outputLocation; } public void setBackend(Backend backend) { this.backend = backend; } public Backend getBackend() { return backend; } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/LogProvider.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer; import java.io.PrintWriter; import java.io.StringWriter; import java.util.function.Consumer; /** * Exposes functionality to replace / retrieve an external logger. * * @author Sebastian Daschner */ public final class LogProvider { private static Consumer infoLogger = System.err::println; private static Consumer debugLogger = s -> { // do nothing }; private static Consumer errorLogger = System.err::println; private LogProvider() { throw new UnsupportedOperationException(); } /** * Injects an own info logger functionality. Overwrites the previously associated info logger. * * @param logger The new info logger */ public static void injectInfoLogger(final Consumer logger) { LogProvider.infoLogger = logger; } /** * Injects an own debug logger functionality. Overwrites the previously associated debug logger. * * @param logger The new debug logger */ public static void injectDebugLogger(final Consumer logger) { LogProvider.debugLogger = logger; } /** * Injects an own error logger functionality. Overwrites the previously associated error logger. * * @param logger The new error logger */ public static void injectErrorLogger(final Consumer logger) { LogProvider.errorLogger = logger; } /** * Logs a message to the configured info logger. * * @param message The message to log */ public static void info(final String message) { infoLogger.accept(message); } /** * Logs a message to the configured debug logger. * * @param message The message to log */ public static void debug(final String message) { debugLogger.accept(message); } /** * Logs the stacktrace of the throwable to the debug logger. * * @param throwable The throwable to log */ public static void debug(final Throwable throwable) { final StringWriter errors = new StringWriter(); throwable.printStackTrace(new PrintWriter(errors)); debugLogger.accept(errors.toString()); } /** * Logs a message to the configured error logger. * * @param message The message to log */ public static void error(final String message) { errorLogger.accept(message); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/Main.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer; import com.sebastian_daschner.jaxrs_analyzer.backend.Backend; import com.sebastian_daschner.jaxrs_analyzer.backend.StringBackend; import com.sebastian_daschner.jaxrs_analyzer.backend.swagger.SwaggerOptions; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Inspects the information of JAX-RS classes via bytecode analysis. * * @author Sebastian Daschner */ public class Main { private static final String DEFAULT_NAME = "project"; private static final String DEFAULT_VERSION = "0.1-SNAPSHOT"; private static final String DEFAULT_BACKEND = "swagger"; private static final String COMMA_LIST_SEPARATOR = ","; private static final JAXRSAnalyzer.Analysis analysis = new JAXRSAnalyzer.Analysis(); private static final Map attributes = new HashMap<>(); /** * Inspects JAX-RS projects and outputs the gathered information. *

* Argument usage: {@code [options] projectPath [projectPaths...]} *

* The {@code projectPath} entries may be directories or jar-files containing the classes to be analyzed *

* Following available options: *

    *
  • {@code -b backend} The backend to choose: {@code swagger} (default), {@code plaintext}, {@code asciidoc}, {@code markdown}
  • *
  • {@code -cp class path[:class paths...]} The additional class paths which contain classes which are used in the project
  • *
  • {@code -sp source path[:source paths...]} The optional source paths needed for JavaDoc analysis
  • *
  • {@code -X} Debug enabled (prints error debugging information on Standard error out)
  • *
  • {@code -n project name} The name of the project
  • *
  • {@code -v project version} The version of the project
  • *
  • {@code -d project domain} The domain of the project
  • *
  • {@code -o output file} The location of the analysis output (will be printed to standard out if omitted)
  • *
  • {@code -e encoding} The source file encoding
  • *
*

* Following available backend specific options (only have effect if the corresponding backend is selected): *

    *
  • {@code --swaggerSchemes scheme[,schemes]} The Swagger schemes: {@code http} (default), {@code https}, {@code ws}, {@code wss}")
  • *
  • {@code --renderSwaggerTags} Enables rendering of Swagger tags (will not be rendered per default)
  • *
  • {@code --swaggerTagsPathOffset path offset} The number at which path position the Swagger tags should be extracted ({@code 0} per default)
  • *
  • {@code --ignoredRootResources class[,classes]} JAX-RS root resource classes which should be ignored by analyze (empty per default)
  • *
* * @param args The arguments */ public static void main(final String... args) { if (args.length < 1) { printUsageAndExit(); } try { setDefaults(); extractArgs(args); } catch (IllegalArgumentException e) { System.err.println(e.getMessage() + '\n'); printUsageAndExit(); } validateArgs(); configureBackend(); new JAXRSAnalyzer(analysis).analyze(); } private static void setDefaults() { analysis.setProjectName(DEFAULT_NAME); analysis.setProjectVersion(DEFAULT_VERSION); } private static void extractArgs(String[] args) { try { for (int i = 0; i < args.length; i++) { if (args[i].startsWith("-")) { switch (args[i]) { case "-b": analysis.setBackend(extractBackend(args[++i])); break; case "-cp": extractClassPaths(args[++i]).forEach(analysis::addClassPath); break; case "-sp": extractClassPaths(args[++i]).forEach(analysis::addProjectSourcePath); break; case "-X": LogProvider.injectDebugLogger(System.err::println); break; case "-n": analysis.setProjectName(args[++i]); break; case "-v": analysis.setProjectVersion(args[++i]); break; case "-d": attributes.put(SwaggerOptions.DOMAIN, args[++i]); break; case "-o": analysis.setOutputLocation(Paths.get(args[++i])); break; case "-e": System.setProperty("project.build.sourceEncoding", args[++i]); break; case "--swaggerSchemes": attributes.put(SwaggerOptions.SWAGGER_SCHEMES, args[++i]); break; case "--renderSwaggerTags": attributes.put(SwaggerOptions.RENDER_SWAGGER_TAGS, "true"); break; case "--swaggerTagsPathOffset": attributes.put(SwaggerOptions.SWAGGER_TAGS_PATH_OFFSET, args[++i]); break; case "--noInlinePrettify": attributes.put(StringBackend.INLINE_PRETTIFY, "false"); break; case "--ignoredRootResources": extractList(args[++i]).forEach(analysis::addIgnoredResource); break; case "-a": addAttribute(args[++i]); break; default: throw new IllegalArgumentException("Unknown option " + args[i]); } } else { final Path path = Paths.get(args[i].replaceFirst("^~", System.getProperty("user.home"))); if (!path.toFile().exists()) { System.err.println("Location " + path.toFile() + " doesn't exist\n"); printUsageAndExit(); } analysis.addProjectClassPath(path); } } } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException("Please provide valid number of arguments"); } } static Map addAttribute(String attribute) { int separatorIndex = attribute.indexOf('='); if (separatorIndex < 0) { attributes.put(attribute, ""); } else { attributes.put(attribute.substring(0, separatorIndex).trim(), attribute.substring(separatorIndex + 1).trim()); } return attributes; } private static Backend extractBackend(final String name) { return JAXRSAnalyzer.constructBackend(name.toLowerCase()); } private static List extractClassPaths(final String classPaths) { final List paths = Stream.of(classPaths.split(File.pathSeparator)) .map(s -> s.replaceFirst("^~", System.getProperty("user.home"))) .map(Paths::get).collect(Collectors.toList()); paths.forEach(p -> { if (!p.toFile().exists()) { throw new IllegalArgumentException("Class path " + p.toFile() + " doesn't exist"); } }); return paths; } private static List extractList(String list) { return Stream.of(list.split(COMMA_LIST_SEPARATOR)) .map(String::trim) .filter(item -> !item.isEmpty()) .collect(Collectors.toList()); } private static void validateArgs() { if (analysis.getProjectClassPaths().isEmpty()) { System.err.println("Please provide at least one project path\n"); printUsageAndExit(); } } private static void configureBackend() { if (analysis.getBackend() == null) analysis.setBackend(JAXRSAnalyzer.constructBackend(DEFAULT_BACKEND)); analysis.configureBackend(attributes); } private static void printUsageAndExit() { System.err.println("Usage: java -jar jaxrs-analyzer.jar [options] classPath [classPaths...]"); System.err.println("The classPath entries may be directories or jar-files containing the classes to be analyzed\n"); System.err.println("Following available options:\n"); System.err.println(" -b The backend to choose: swagger (default), plaintext, asciidoc, markdown"); System.err.println(" -cp [:class paths] Additional class paths (separated with colon) which contain classes used in the project (may be directories or jar-files)"); System.err.println(" -sp [:source paths] Optional source paths (separated with colon) needed for JavaDoc analysis (may be directories or jar-files)"); System.err.println(" -X Debug enabled (enabled error debugging information)"); System.err.println(" -n The name of the project"); System.err.println(" -v The version of the project"); System.err.println(" -d The domain of the project"); System.err.println(" -o The location of the analysis output (will be printed to standard out if omitted)"); System.err.println(" -a = Set custom attributes for backends."); System.err.println(" -e The source file encoding"); System.err.println("\nFollowing available backend specific options (only have effect if the corresponding backend is selected):\n"); System.err.println(" --swaggerSchemes [,schemes] The Swagger schemes: http (default), https, ws, wss"); System.err.println(" --renderSwaggerTags Enables rendering of Swagger tags (default tag will be used per default)"); System.err.println(" --swaggerTagsPathOffset The number at which path position the Swagger tags will be extracted (0 will be used per default)"); System.err.println(" --ignoredRootResources JAX-RS root resource classes which should be ignored by analyze (empty per default)"); System.err.println(" --noPrettyPrint Don't pretty print inline JSON body representations (will be pretty printed per default)"); System.err.println("\nExample: java -jar jaxrs-analyzer.jar -b swagger -n \"My Project\" -cp ~/libs/lib1.jar:~/libs/project/bin ~/project/target/classes"); System.exit(1); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/JobRegistry.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.utils.Pair; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; /** * Thread-safe singleton of unhandled class analysis jobs. * * @author Sebastian Daschner */ public class JobRegistry { private static final JobRegistry INSTANCE = new JobRegistry(); private Queue> unhandledClasses = new ConcurrentLinkedQueue<>(); private JobRegistry() { // only one instance allowed } /** * Adds the (sub-)resource class name to the analysis list with the associated class result. */ public void analyzeResourceClass(final String className, final ClassResult classResult) { // TODO check if class has already been analyzed unhandledClasses.add(Pair.of(className, classResult)); } /** * Returns a class which has not been analyzed yet. * * @return An unhandled class or {@code null} if all classes have been analyzed */ public Pair nextUnhandledClass() { return unhandledClasses.poll(); } public static JobRegistry getInstance() { return INSTANCE; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/ProjectAnalyzer.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.BytecodeAnalyzer; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.ContextClassReader; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.JAXRSClassVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.javadoc.JavaDocAnalyzer; import com.sebastian_daschner.jaxrs_analyzer.analysis.results.ResultInterpreter; import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils; import com.sebastian_daschner.jaxrs_analyzer.model.rest.Resources; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.utils.Pair; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import javax.ws.rs.ApplicationPath; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.isAnnotationPresent; /** * Analyzes the JAX-RS project. This class is thread-safe. * * @author Sebastian Daschner */ public class ProjectAnalyzer { // TODO test following scenario: // 2 Maven modules -> a, b; a needs b // b contains interface with @Path & resource methods // a contains impl of iface without annotations // b should have result private final Lock lock = new ReentrantLock(); private final Set classes = new HashSet<>(); private final ResultInterpreter resultInterpreter = new ResultInterpreter(); private final BytecodeAnalyzer bytecodeAnalyzer = new BytecodeAnalyzer(); private final JavaDocAnalyzer javaDocAnalyzer = new JavaDocAnalyzer(); /** * Creates a project analyzer with given class path locations where to search for classes. * * @param classPaths The locations of additional class paths (can be directories or jar-files) */ public ProjectAnalyzer(final Set classPaths) { classPaths.forEach(this::addToClassPool); } /** * Analyzes all classes in the given project path. * * @param projectClassPaths The project class paths * @param projectSourcePaths The project source file paths * @param ignoredResources The fully-qualified root resource class names to be ignored * @return The REST resource representations */ public Resources analyze(Set projectClassPaths, Set projectSourcePaths, Set ignoredResources) { lock.lock(); try { projectClassPaths.forEach(this::addProjectPath); // analyze relevant classes final JobRegistry jobRegistry = JobRegistry.getInstance(); final Set classResults = new HashSet<>(); classes.stream() .filter(this::isJAXRSRootResource) .filter(r -> !ignoredResources.contains(r)) .forEach(c -> jobRegistry.analyzeResourceClass(c, new ClassResult())); Pair classResultPair; while ((classResultPair = jobRegistry.nextUnhandledClass()) != null) { final ClassResult classResult = classResultPair.getRight(); classResults.add(classResult); analyzeClass(classResultPair.getLeft(), classResult); bytecodeAnalyzer.analyzeBytecode(classResult); } javaDocAnalyzer.analyze(projectSourcePaths, classResults); return resultInterpreter.interpret(classResults); } finally { lock.unlock(); } } private boolean isJAXRSRootResource(String className) { final Class clazz = JavaUtils.loadClassFromName(className); return clazz != null && (isAnnotationPresent(clazz, javax.ws.rs.Path.class) || isAnnotationPresent(clazz, ApplicationPath.class)); } private void analyzeClass(final String className, ClassResult classResult) { try { final ClassReader classReader = new ContextClassReader(className); final ClassVisitor visitor = new JAXRSClassVisitor(classResult); classReader.accept(visitor, ClassReader.EXPAND_FRAMES); } catch (IOException e) { LogProvider.error("The class " + className + " could not be loaded!"); LogProvider.debug(e); } } /** * Adds the location to the class pool. * * @param location The location of a jar file or a directory */ private void addToClassPool(final Path location) { if (!location.toFile().exists()) throw new IllegalArgumentException("The location '" + location + "' does not exist!"); try { ContextClassReader.addClassPath(location.toUri().toURL()); } catch (Exception e) { throw new IllegalArgumentException("The location '" + location + "' could not be loaded to the class path!", e); } } /** * Adds the project paths and loads all classes. * * @param path The project path */ private void addProjectPath(final Path path) { addToClassPool(path); if (path.toFile().isFile() && path.toString().endsWith(".jar")) { addJarClasses(path); } else if (path.toFile().isDirectory()) { addDirectoryClasses(path, Paths.get("")); } else { throw new IllegalArgumentException("The project path '" + path + "' must be a jar file or a directory"); } } /** * Adds all classes in the given jar-file location to the set of known classes. * * @param location The location of the jar-file */ private void addJarClasses(final Path location) { try (final JarFile jarFile = new JarFile(location.toFile())) { final Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { final JarEntry entry = entries.nextElement(); final String entryName = entry.getName(); if (entryName.endsWith(".class")) classes.add(toQualifiedClassName(entryName)); } } catch (IOException e) { throw new IllegalArgumentException("Could not read jar-file '" + location + "', reason: " + e.getMessage()); } } /** * Adds all classes in the given directory location to the set of known classes. * * @param location The location of the current directory * @param subPath The sub-path which is relevant for the package names or {@code null} if currently in the root directory */ private void addDirectoryClasses(final Path location, final Path subPath) { for (final File file : location.toFile().listFiles()) { if (file.isDirectory()) addDirectoryClasses(location.resolve(file.getName()), subPath.resolve(file.getName())); else if (file.isFile() && file.getName().endsWith(".class")) { final String classFileName = subPath.resolve(file.getName()).toString(); classes.add(toQualifiedClassName(classFileName)); } } } /** * Converts the given file name of a class-file to the fully-qualified class name. * * @param fileName The file name (e.g. a/package/AClass.class) * @return The fully-qualified class name (e.g. a.package.AClass) */ private static String toQualifiedClassName(final String fileName) { final String replacedSeparators = fileName.replace(File.separatorChar, '.'); return replacedSeparators.substring(0, replacedSeparators.length() - ".class".length()); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/BytecodeAnalyzer.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; /** * @author Sebastian Daschner */ public class BytecodeAnalyzer { private final ResourceMethodContentAnalyzer methodContentAnalyzer = new ResourceMethodContentAnalyzer(); private final SubResourceLocatorMethodContentAnalyzer subResourceLocatorAnalyzer = new SubResourceLocatorMethodContentAnalyzer(); /** * Analyzes the bytecode instructions of the method results and interprets JAX-RS relevant information. */ public void analyzeBytecode(final ClassResult classResult) { classResult.getMethods().forEach(this::analyzeBytecode); } private void analyzeBytecode(final MethodResult methodResult) { if (methodResult.getHttpMethod() == null) { // sub-resource subResourceLocatorAnalyzer.analyze(methodResult); } else { methodContentAnalyzer.analyze(methodResult); } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/MethodContentAnalyzer.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.reduction.RelevantInstructionReducer; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.ContextClassReader; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.ProjectMethodClassVisitor; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.InvokeInstruction; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.methods.ProjectMethod; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * Analyzes the content of a method. Sub classes have to be thread-safe. * * @author Sebastian Daschner */ abstract class MethodContentAnalyzer { /** * The number of package hierarchies which are taken to identify project resources. */ private static final int PROJECT_PACKAGE_HIERARCHIES = 2; private final RelevantInstructionReducer instructionReducer = new RelevantInstructionReducer(); private String projectPackagePrefix; /** * Interprets the relevant instructions for the given method. * * @param instructions The instructions to reduce * @return The reduced instructions */ List interpretRelevantInstructions(final List instructions) { return instructionReducer.reduceInstructions(instructions); } /** * Builds the project package prefix for the class of given method. * The current project which is analyzed is identified by the first two package nodes. */ void buildPackagePrefix(final String className) { // TODO test final int lastPackageSeparator = className.lastIndexOf('/'); final String packageName = className.substring(0, lastPackageSeparator == -1 ? className.length() : lastPackageSeparator); final String[] splitPackage = packageName.split("/"); if (splitPackage.length >= PROJECT_PACKAGE_HIERARCHIES) { projectPackagePrefix = IntStream.range(0, PROJECT_PACKAGE_HIERARCHIES).mapToObj(i -> splitPackage[i]).collect(Collectors.joining("/")); } else { projectPackagePrefix = packageName; } } /** * Searches for own project method invoke instructions in the given list. * * @param instructions The instructions where to search * @return The found project methods */ Set findProjectMethods(final List instructions) { final Set projectMethods = new HashSet<>(); addProjectMethods(instructions, projectMethods); return projectMethods; } /** * Adds all project methods called in the given {@code instructions} to the {@code projectMethods} recursively. * * @param instructions The instructions of the current method * @param projectMethods All found project methods */ private void addProjectMethods(final List instructions, final Set projectMethods) { Set projectMethodIdentifiers = findUnhandledProjectMethodIdentifiers(instructions, projectMethods); for (MethodIdentifier identifier : projectMethodIdentifiers) { // TODO cache results -> singleton pool? final MethodResult methodResult = visitProjectMethod(identifier); if (methodResult == null) { continue; } final List nestedMethodInstructions = interpretRelevantInstructions(methodResult.getInstructions()); projectMethods.add(new ProjectMethod(identifier, nestedMethodInstructions)); addProjectMethods(nestedMethodInstructions, projectMethods); } } private MethodResult visitProjectMethod(MethodIdentifier identifier) { try { final ClassReader classReader = new ContextClassReader(identifier.getContainingClass()); final MethodResult methodResult = new MethodResult(); methodResult.setOriginalMethodSignature(identifier); final ClassVisitor visitor = new ProjectMethodClassVisitor(methodResult, identifier); classReader.accept(visitor, ClassReader.EXPAND_FRAMES); return methodResult; } catch (IOException e) { LogProvider.error("Could not analyze project method " + identifier.getContainingClass() + "#" + identifier.getMethodName()); LogProvider.debug(e); return null; } } /** * Returns project method identifiers of invoke instructions which are not included in the {@code projectMethods}. * * @param instructions The instructions of the current method * @param projectMethods All found project methods * @return The new method identifiers of unhandled project method invoke instructions */ private Set findUnhandledProjectMethodIdentifiers(final List instructions, final Set projectMethods) { // find own methods return instructions.stream().filter(i -> i.getType() == Instruction.InstructionType.INVOKE || i.getType() == Instruction.InstructionType.METHOD_HANDLE) .map(i -> (InvokeInstruction) i).filter(this::isProjectMethod).map(InvokeInstruction::getIdentifier) .filter(i -> projectMethods.stream().noneMatch(m -> m.matches(i))) .collect(Collectors.toSet()); } /** * Checks if the given instruction invokes a method defined in the analyzed project. * * @param instruction The invoke instruction * @return {@code true} if method was defined in the project */ private boolean isProjectMethod(final InvokeInstruction instruction) { final MethodIdentifier identifier = instruction.getIdentifier(); // check if method is in own package return identifier.getContainingClass().startsWith(projectPackagePrefix); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/ResourceMethodContentAnalyzer.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation.MethodPool; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation.MethodSimulator; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.elements.HttpResponse; import com.sebastian_daschner.jaxrs_analyzer.model.elements.JsonValue; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import com.sebastian_daschner.jaxrs_analyzer.model.methods.ProjectMethod; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; /** * Analyzes JAX-RS resource methods. This class is thread-safe. * * @author Sebastian Daschner */ class ResourceMethodContentAnalyzer extends MethodContentAnalyzer { private final Lock lock = new ReentrantLock(); /** * Analyzes the method (including own project methods). * * @param methodResult The method result */ void analyze(final MethodResult methodResult) { lock.lock(); try { buildPackagePrefix(methodResult.getParentResource().getOriginalClass()); final List visitedInstructions = interpretRelevantInstructions(methodResult.getInstructions()); // find project defined methods in invoke occurrences final Set projectMethods = findProjectMethods(visitedInstructions); // add project methods to global method pool projectMethods.forEach(MethodPool.getInstance()::addProjectMethod); Element returnedElement = new MethodSimulator().simulate(visitedInstructions); final String returnType = methodResult.getOriginalMethodSignature().getReturnType(); // void resource methods are interpreted later; stop analyzing on error if (Types.PRIMITIVE_VOID.equals(returnType)) { return; } if (returnedElement == null) { // happens for abstract methods or if there is no return return; } final Set possibleObjects = returnedElement.getPossibleValues().stream().filter(o -> !(o instanceof HttpResponse)) .collect(Collectors.toSet()); // for non-Response methods add a default if there are non-Response objects or none objects at all if (!Types.RESPONSE.equals(returnType)) { final HttpResponse defaultResponse = new HttpResponse(); if (Types.OBJECT.equals(returnType)) defaultResponse.getEntityTypes().addAll(returnedElement.getTypes()); else defaultResponse.getEntityTypes().add(returnType); possibleObjects.stream().filter(o -> o instanceof JsonValue).map(o -> (JsonValue) o).forEach(defaultResponse.getInlineEntities()::add); defaultResponse.getContentTypes().addAll(methodResult.getResponseMediaTypes()); methodResult.getResponses().add(defaultResponse); } // add Response results as well returnedElement.getPossibleValues().stream().filter(o -> o instanceof HttpResponse).map(o -> (HttpResponse) o).forEach(methodResult.getResponses()::add); } finally { lock.unlock(); } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/SubResourceLocatorMethodContentAnalyzer.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode; import com.sebastian_daschner.jaxrs_analyzer.analysis.JobRegistry; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation.MethodPool; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation.MethodSimulator; import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import com.sebastian_daschner.jaxrs_analyzer.model.methods.ProjectMethod; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.singleton; /** * Analyzes sub-resource-locator methods. This class is thread-safe. * * @author Sebastian Daschner */ class SubResourceLocatorMethodContentAnalyzer extends MethodContentAnalyzer { private final Lock lock = new ReentrantLock(); private final MethodSimulator simulator = new MethodSimulator(); /** * Analyzes the sub-resource locator method as a class result (which will be the content of a method result). * * @param methodResult The method result of the sub-resource locator (containing the instructions, and a sub-resource class result) */ void analyze(final MethodResult methodResult) { lock.lock(); try { buildPackagePrefix(methodResult.getParentResource().getOriginalClass()); determineReturnTypes(methodResult).stream() // FEATURE handle several sub-resource impl's .reduce((l, r) -> JavaUtils.determineMostSpecificType(l, r)) .ifPresent(t -> registerSubResourceJob(t, methodResult.getSubResource())); } finally { lock.unlock(); } } /** * Determines the possible return types of the sub-resource-locator by analyzing the bytecode. * This will analyze the concrete returned types (which then are further analyzed). */ private Set determineReturnTypes(final MethodResult result) { final List visitedInstructions = interpretRelevantInstructions(result.getInstructions()); // find project defined methods in invoke occurrences final Set projectMethods = findProjectMethods(visitedInstructions); // add project methods to global method pool projectMethods.forEach(MethodPool.getInstance()::addProjectMethod); final Element returnedElement = simulator.simulate(visitedInstructions); if (returnedElement == null) { // happens for abstract methods or if there is no return return singleton(result.getOriginalMethodSignature().getReturnType()); } return returnedElement.getTypes(); } private void registerSubResourceJob(final String type, final ClassResult classResult) { final String className = JavaUtils.toClassName(type); JobRegistry.getInstance().analyzeResourceClass(className, classResult); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/collection/InstructionBuilder.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.collection; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.*; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import java.lang.reflect.Field; import static com.sebastian_daschner.jaxrs_analyzer.model.Types.*; import static com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier.of; import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.util.Printer.OPCODES; /** * @author Sebastian Daschner */ public final class InstructionBuilder { private InstructionBuilder() { throw new UnsupportedOperationException(); } public static Instruction buildFieldInstruction(final int opcode, final String ownerClass, final String name, final String desc, final Label label) { // TODO remove if (org.objectweb.asm.Type.getObjectType(ownerClass).getClassName().equals(ownerClass.replace('.', '/'))) throw new AssertionError("!"); final String opcodeName = OPCODES[opcode]; switch (opcode) { case GETSTATIC: final Object value = getStaticValue(name, ownerClass); return new GetStaticInstruction(ownerClass, name, desc, value, label); case PUTSTATIC: return new SizeChangingInstruction(opcodeName, 0, 1, label); case GETFIELD: return new GetFieldInstruction(ownerClass, name, desc, label); case PUTFIELD: return new SizeChangingInstruction(opcodeName, 0, 2, label); default: throw new IllegalArgumentException("Opcode " + opcode + " not a field instruction"); } } public static Instruction buildInstruction(final int opcode, final Label label) { final String opcodeName = OPCODES[opcode]; switch (opcode) { case ICONST_0: return new PushInstruction(0, PRIMITIVE_INT, label); case ICONST_1: return new PushInstruction(1, PRIMITIVE_INT, label); case ICONST_2: return new PushInstruction(2, PRIMITIVE_INT, label); case ICONST_3: return new PushInstruction(3, PRIMITIVE_INT, label); case ICONST_4: return new PushInstruction(4, PRIMITIVE_INT, label); case ICONST_5: return new PushInstruction(5, PRIMITIVE_INT, label); case ICONST_M1: return new PushInstruction(-1, PRIMITIVE_INT, label); case DCONST_0: return new PushInstruction(0d, PRIMITIVE_DOUBLE, label); case DCONST_1: return new PushInstruction(1d, PRIMITIVE_DOUBLE, label); case FCONST_0: return new PushInstruction(1f, PRIMITIVE_FLOAT, label); case FCONST_1: return new PushInstruction(1f, PRIMITIVE_FLOAT, label); case FCONST_2: return new PushInstruction(2f, PRIMITIVE_FLOAT, label); case LCONST_0: return new PushInstruction(0L, PRIMITIVE_LONG, label); case LCONST_1: return new PushInstruction(1L, PRIMITIVE_LONG, label); case IALOAD: case LALOAD: case FALOAD: case DALOAD: case AALOAD: case BALOAD: case CALOAD: case SALOAD: return new SizeChangingInstruction(opcodeName, 1, 2, label); case IASTORE: case LASTORE: case FASTORE: case DASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: return new SizeChangingInstruction(opcodeName, 0, 3, label); case DUP_X1: case DUP2_X1: return new SizeChangingInstruction(opcodeName, 3, 2, label); case DUP_X2: case DUP2_X2: return new SizeChangingInstruction(opcodeName, 4, 3, label); case ARRAYLENGTH: case I2L: case I2F: case I2D: case L2I: case L2F: case L2D: case F2I: case F2L: case F2D: case D2I: case D2L: case D2F: case I2B: case I2C: case I2S: case INEG: case LNEG: case FNEG: case DNEG: case SWAP: return new SizeChangingInstruction(opcodeName, 1, 1, label); case IADD: case LADD: case FADD: case DADD: case ISUB: case LSUB: case FSUB: case DSUB: case IMUL: case LMUL: case FMUL: case DMUL: case IDIV: case LDIV: case FDIV: case DDIV: case IREM: case LREM: case FREM: case DREM: case ISHL: case LSHL: case ISHR: case LSHR: case IUSHR: case LUSHR: case IAND: case LAND: case IOR: case LOR: case IXOR: case LXOR: case LCMP: case FCMPL: case FCMPG: case DCMPL: case DCMPG: return new SizeChangingInstruction(opcodeName, 1, 2, label); case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: return new ReturnInstruction(label); case ATHROW: return new ThrowInstruction(label); case RETURN: case NOP: return new DefaultInstruction(opcodeName, label); case POP: case POP2: case MONITORENTER: case MONITOREXIT: return new SizeChangingInstruction(opcodeName, 0, 1, label); case ACONST_NULL: return new SizeChangingInstruction(opcodeName, 1, 0, label); case DUP: case DUP2: return new DupInstruction(label); default: throw new IllegalArgumentException("Unexpected opcode " + opcode); } } public static Instruction buildLoadStoreInstruction(int opcode, int index, Label label) { switch (opcode) { case ILOAD: case LLOAD: case FLOAD: case DLOAD: case ALOAD: return new LoadStoreInstructionPlaceholder(Instruction.InstructionType.LOAD_PLACEHOLDER, index, label); case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: return new LoadStoreInstructionPlaceholder(Instruction.InstructionType.STORE_PLACEHOLDER, index, label); case RET: return new DefaultInstruction(OPCODES[opcode], label); default: throw new IllegalArgumentException("Unexpected opcode " + opcode); } } public static Instruction buildTypeInstruction(int opcode, String className, final Label label) { final String opcodeName = OPCODES[opcode]; switch (opcode) { case NEW: return new NewInstruction(className, label); case ANEWARRAY: case INSTANCEOF: return new SizeChangingInstruction(opcodeName, 1, 1, label); case CHECKCAST: return new DefaultInstruction(opcodeName, label); default: throw new IllegalArgumentException("Unexpected opcode " + opcode); } } public static InvokeInstruction buildInvokeInstruction(final int opcode, String containingClass, String name, String desc, final Label label) { switch (opcode) { case INVOKEINTERFACE: case INVOKEVIRTUAL: case INVOKESPECIAL: return new InvokeInstruction(of(containingClass, name, desc, false), label); case INVOKESTATIC: return new InvokeInstruction(of(containingClass, name, desc, true), label); default: throw new IllegalArgumentException("Unexpected opcode " + opcode); } } public static Instruction buildInvokeDynamic(final String className, final String name, final String desc, final Handle handle, final Label label) { final MethodIdentifier actualIdentifier = of(handle.getOwner(), handle.getName(), handle.getDesc(), handle.getTag() == Opcodes.H_INVOKESTATIC); final MethodIdentifier dynamicIdentifier = of(className, name, desc, true); return new InvokeDynamicInstruction(actualIdentifier, dynamicIdentifier, label); } public static Instruction buildJumpInstruction(int opcode, final Label label) { final String opcodeName = OPCODES[opcode]; switch (opcode) { case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: case IFNULL: case IFNONNULL: return new SizeChangingInstruction(opcodeName, 0, 1, label); case JSR: return new SizeChangingInstruction(opcodeName, 1, 0, label); case GOTO: return new DefaultInstruction(opcodeName, label); case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: return new SizeChangingInstruction(opcodeName, 0, 2, label); default: throw new IllegalArgumentException("Unexpected opcode " + opcode); } } public static Instruction buildIntInstruction(int opcode, int operand, final Label label) { switch (opcode) { case BIPUSH: case SIPUSH: return new PushInstruction(operand, PRIMITIVE_INT, label); case NEWARRAY: return new SizeChangingInstruction(OPCODES[NEWARRAY], 1, 1, label); default: throw new IllegalArgumentException("Unexpected opcode " + opcode); } } private static Object getStaticValue(String name, String containingClass) { final Field field; try { // needs to load same class instance in Maven plugin, not from extended classloader final Class clazz = Class.forName(containingClass.replace('/', '.')); field = clazz.getDeclaredField(name); field.setAccessible(true); return field.get(null); } catch (Exception e) { LogProvider.error("Could not access static property, reason: " + e.getMessage()); LogProvider.debug(e); return null; } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/reduction/InstructionFinder.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.reduction; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.LoadInstruction; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.LoadStoreInstruction; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.function.Predicate; /** * Searches for specific instruction occurrences in the byte code. * * @author Sebastian Daschner */ final class InstructionFinder { private InstructionFinder() { throw new UnsupportedOperationException(); } /** * Searches for all LOAD indexes which occur in the given instructions. * The LOAD instruction is checked against the given predicate if it should be ignored. * * @param instructions The instructions where to search * @param isLoadIgnored The ignore predicate * @return All found LOAD indexes */ static Set findLoadIndexes(final List instructions, final Predicate isLoadIgnored) { return instructions.stream().filter(i -> i.getType() == Instruction.InstructionType.LOAD).map(i -> (LoadInstruction) i) .filter(i -> !isLoadIgnored.test(i)).map(LoadInstruction::getNumber).collect(TreeSet::new, Set::add, Set::addAll); } /** * Searches for all LOAD & STORE occurrences with {@code index} in the given instructions. * * @param index The LOAD / STORE index * @param instructions The instructions where to search * @return The positions of all found LOAD_{@code index} / STORE_{@code index} */ static Set findLoadStores(final int index, final List instructions) { final Predicate loadStoreType = instruction -> instruction.getType() == Instruction.InstructionType.LOAD || instruction.getType() == Instruction.InstructionType.STORE; return find(loadStoreType.and(instruction -> ((LoadStoreInstruction) instruction).getNumber() == index), instructions); } /** * Searches for return instructions in the given instructions. * * @param instructions The instructions where to search * @return The positions of all found return instructions */ static Set findReturnsAndThrows(final List instructions) { return find(instruction -> instruction.getType() == Instruction.InstructionType.RETURN || instruction.getType() == Instruction.InstructionType.THROW, instructions); } /** * Searches for certain instruction positions be testing against the predicate. * * @param predicate The criteria predicate * @param instructions The instructions where to search * @return The positions of all matching instructions */ private static Set find(final Predicate predicate, final List instructions) { final Set positions = new HashSet<>(); for (int i = 0; i < instructions.size(); i++) { final Instruction instruction = instructions.get(i); if (predicate.test(instruction)) { positions.add(i); } } return positions; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/reduction/RelevantInstructionReducer.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.reduction; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.LoadInstruction; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Determines the instructions, which are relevant for the return value of a method by simulating a runtime stack with the byte code. This class is thread-safe. * * @author Sebastian Daschner */ public class RelevantInstructionReducer { /** * These variable names will not be backtracked. */ private static final String[] VARIABLE_NAMES_TO_IGNORE = {"this"}; private final Lock lock = new ReentrantLock(); private final StackSizeSimulator stackSizeSimulator = new StackSizeSimulator(); private List instructions; /** * Returns all instructions which are somewhat "relevant" for the returned object of the method. * The instructions are visited backwards - starting from the return statement. * Load and Store operations are handled as well. * * @param instructions The instructions to reduce * @return The relevant instructions */ public List reduceInstructions(final List instructions) { lock.lock(); try { this.instructions = instructions; stackSizeSimulator.buildStackSizes(instructions); return reduceInstructionsInternal(instructions); } finally { lock.unlock(); } } /** * Returns all reduced instructions. * * @param instructions All instructions * @return The relevant instructions */ private List reduceInstructionsInternal(final List instructions) { final List visitedInstructions = new LinkedList<>(); final Set visitedInstructionPositions = new HashSet<>(); final Set handledLoadIndexes = new HashSet<>(); final Set backtrackPositions = new LinkedHashSet<>(findSortedBacktrackPositions()); while (!visitedInstructionPositions.containsAll(backtrackPositions)) { // unvisited backtrack position final int backtrackPosition = backtrackPositions.stream().filter(pos -> !visitedInstructionPositions.contains(pos)) .findFirst().orElseThrow(IllegalStateException::new); final List lastVisitedPositions = stackSizeSimulator.simulateStatementBackwards(backtrackPosition); final List lastVisitedInstructions = lastVisitedPositions.stream().map(instructions::get).collect(Collectors.toList()); visitedInstructionPositions.addAll(lastVisitedPositions); visitedInstructions.addAll(lastVisitedInstructions); // unhandled load indexes final Set unhandledLoadIndexes = findUnhandledLoadIndexes(handledLoadIndexes, lastVisitedInstructions); // for each load occurrence index -> find load/store backtrack positions (reverse order matters here) final SortedSet loadStoreBacktrackPositions = findLoadStoreBacktrackPositions(unhandledLoadIndexes); handledLoadIndexes.addAll(unhandledLoadIndexes); loadStoreBacktrackPositions.stream().forEach(backtrackPositions::add); } // sort in method natural order Collections.reverse(visitedInstructions); return visitedInstructions; } private List findSortedBacktrackPositions() { final List startPositions = new LinkedList<>(InstructionFinder.findReturnsAndThrows(instructions)); // start with last return Collections.sort(startPositions, Comparator.reverseOrder()); return startPositions; } /** * Searches for load indexes in the {@code lastVisitedInstructions} which are not contained in {@code handledLoadIndexes}. * * @param handledLoadIndexes The load indexed which have been handled so far * @param lastVisitedInstructions The last visited instructions * @return The unhandled load indexes */ private Set findUnhandledLoadIndexes(final Set handledLoadIndexes, final List lastVisitedInstructions) { final Set lastLoadIndexes = InstructionFinder.findLoadIndexes(lastVisitedInstructions, RelevantInstructionReducer::isLoadIgnored); return lastLoadIndexes.stream().filter(k -> !handledLoadIndexes.contains(k)).collect(Collectors.toSet()); } /** * Checks if the given LOAD instruction should be ignored for backtracking. * * @param instruction The instruction to check * @return {@code true} if the LOAD instruction will be ignored */ private static boolean isLoadIgnored(final LoadInstruction instruction) { return Stream.of(VARIABLE_NAMES_TO_IGNORE).anyMatch(instruction.getName()::equals); } /** * Returns all backtrack positions of the given LOAD / STORE indexes in the instructions. * The backtrack positions of both LOAD and store instructions are the next positions where the runtime stack size is {@code 0}. * * @param unhandledLoadIndexes The LOAD/STORE indexes to find * @return The backtrack positions of the LOAD / STORE indexes */ private SortedSet findLoadStoreBacktrackPositions(final Set unhandledLoadIndexes) { return unhandledLoadIndexes.stream() .map(index -> stackSizeSimulator.findLoadStoreBacktrackPositions(InstructionFinder.findLoadStores(index, instructions))) .collect(() -> new TreeSet<>(Comparator.reverseOrder()), Set::addAll, Set::addAll); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/reduction/StackSizeSimulator.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.reduction; import com.sebastian_daschner.jaxrs_analyzer.utils.Pair; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Simulates runtime stack sizes of instructions. * * @author Sebastian Daschner */ class StackSizeSimulator { private List> stackSizes; /** * Initializes the runtime stack sizes with the given instructions. This has to be called before {@link StackSizeSimulator#simulateStatementBackwards} * * @param instructions The instructions to simulate */ void buildStackSizes(final List instructions) { stackSizes = new ArrayList<>(); int stackSize = 0; for (Instruction instruction : instructions) { final int previousStackSize = stackSize; stackSize += instruction.getStackSizeDifference(); if (isStackCleared(instruction)) stackSize = 0; if (stackSize < 0) { throw new IllegalStateException("Runtime stack under-flow occurred."); } stackSizes.add(Pair.of(previousStackSize, stackSize)); } } /** * Checks if the stack will be cleared on invoking the given instruction. * * @param instruction The instruction * @return {@code true} if the stack will be cleared */ private static boolean isStackCleared(final Instruction instruction) { return instruction.getType() == Instruction.InstructionType.RETURN || instruction.getType() == Instruction.InstructionType.THROW; } /** * Returns the instruction positions which are visited backwards from {@code backtrackPosition} * until the runtime stack is empty. * * @param backtrackPosition The backtrack position where to start * @return All positions backwards until the previous empty position */ List simulateStatementBackwards(final int backtrackPosition) { // search for previous zero-position in stackSizes int currentPosition = backtrackPosition; // check against stack size before the instruction was executed while (stackSizes.get(currentPosition).getLeft() > 0) { currentPosition--; } return Stream.iterate(backtrackPosition, c -> --c).limit(backtrackPosition - currentPosition + 1) .collect(LinkedList::new, Collection::add, Collection::addAll); } /** * Returns all backtrack positions of the given instruction positions. * The backtrack positions of both LOAD and store instructions are the next positions where the runtime stack size is {@code 0}. * * @param loadStorePositions The LOAD/STORE positions * @return The backtrack positions */ Set findLoadStoreBacktrackPositions(final Set loadStorePositions) { // go to this or next zero-position return loadStorePositions.stream().map(this::findBacktrackPosition).collect(Collectors.toSet()); } /** * Returns the next position where the stack will be empty. * * @param position The current position * @return The next empty position */ private int findBacktrackPosition(final int position) { int currentPosition = position; // check against stack size after the instruction was executed while (stackSizes.get(currentPosition).getRight() > 0) { currentPosition++; } return currentPosition; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/simulation/InjectableArgumentMethodSimulator.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.IntStream; /** * Simulates the the instructions of a project method. The parameters of the method can be set with the actual arguments. This class is thread-safe. * * @author Sebastian Daschner */ public class InjectableArgumentMethodSimulator extends MethodSimulator { /** * The called methods in a single recursive method simulation. Used to prevent infinite loops while analysing recursion. */ private static final List EXECUTED_PATH_METHODS = Collections.synchronizedList(new LinkedList<>()); private final Lock lock = new ReentrantLock(); /** * Simulates the instructions of the method which will be called with the given arguments. * * @param arguments The argument values * @param instructions The instructions of the method * @param identifier The identifier of the method * @return The return value or {@code null} if return type is void */ public Element simulate(final List arguments, final List instructions, final MethodIdentifier identifier) { // prevent infinite loops on analysing recursion if (EXECUTED_PATH_METHODS.contains(identifier)) return new Element(); lock.lock(); EXECUTED_PATH_METHODS.add(identifier); try { injectArguments(arguments, identifier); return simulateInternal(instructions); } finally { EXECUTED_PATH_METHODS.remove(identifier); lock.unlock(); } } /** * Injects the arguments of the method invocation to the local variables. * * @param arguments The argument values */ private void injectArguments(final List arguments, final MethodIdentifier identifier) { final boolean staticMethod = identifier.isStaticMethod(); final int startIndex = staticMethod ? 0 : 1; final int endIndex = staticMethod ? arguments.size() - 1 : arguments.size(); IntStream.rangeClosed(startIndex, endIndex).forEach(i -> localVariables.put(i, arguments.get(staticMethod ? i : i - 1))); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/simulation/KnownJsonResultMethod.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.elements.JsonArray; import com.sebastian_daschner.jaxrs_analyzer.model.elements.JsonObject; import com.sebastian_daschner.jaxrs_analyzer.model.methods.IdentifiableMethod; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import java.util.List; import java.util.function.BiFunction; import static com.sebastian_daschner.jaxrs_analyzer.model.Types.*; /** * Known JSON methods which apply logic to the result or to the return element. * * @author Sebastian Daschner */ enum KnownJsonResultMethod implements IdentifiableMethod { JSON_ARRAY_BUILDER_CREATE(MethodIdentifier.ofStatic(CLASS_JSON, "createArrayBuilder", JSON_ARRAY_BUILDER), (object, arguments) -> new Element(JSON_ARRAY, new JsonArray())), JSON_ARRAY_BUILDER_ADD_BIG_DECIMAL(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, BIG_DECIMAL), (object, arguments) -> addToArray(object, arguments, BIG_DECIMAL)), JSON_ARRAY_BUILDER_ADD_BIG_INTEGER(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, BIG_INTEGER), (object, arguments) -> addToArray(object, arguments, BIG_INTEGER)), JSON_ARRAY_BUILDER_ADD_STRING(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, STRING), (object, arguments) -> addToArray(object, arguments, STRING)), JSON_ARRAY_BUILDER_ADD_INT(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, PRIMITIVE_INT), (object, arguments) -> addToArray(object, arguments, INTEGER)), JSON_ARRAY_BUILDER_ADD_LONG(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, PRIMITIVE_LONG), (object, arguments) -> addToArray(object, arguments, LONG)), JSON_ARRAY_BUILDER_ADD_DOUBLE(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, PRIMITIVE_DOUBLE), (object, arguments) -> addToArray(object, arguments, DOUBLE)), JSON_ARRAY_BUILDER_ADD_BOOLEAN(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, PRIMITIVE_BOOLEAN), (object, arguments) -> addToArray(object, arguments, PRIMITIVE_BOOLEAN)), JSON_ARRAY_BUILDER_ADD_JSON(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, JSON_VALUE), KnownJsonResultMethod::addToArray), JSON_ARRAY_BUILDER_ADD_JSON_OBJECT(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, JSON_OBJECT_BUILDER), (object, arguments) -> addToArray(object, arguments, JSON_OBJECT)), JSON_ARRAY_BUILDER_ADD_JSON_ARRAY(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "add", JSON_ARRAY_BUILDER, JSON_ARRAY_BUILDER), (object, arguments) -> addToArray(object, arguments, JSON_ARRAY)), JSON_ARRAY_BUILDER_ADD_NULL(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "addNull", JSON_ARRAY_BUILDER), (object, arguments) -> { object.getPossibleValues().stream().filter(o -> o instanceof JsonArray).map(o -> (JsonArray) o) .forEach(a -> a.getElements().add(new Element(OBJECT, null))); return object; }), JSON_ARRAY_BUILDER_BUILD(MethodIdentifier.ofNonStatic(CLASS_JSON_ARRAY_BUILDER, "build", JSON_ARRAY), (object, arguments) -> { Element json = new Element(JSON_ARRAY); json.getPossibleValues().addAll(object.getPossibleValues()); return json; }), JSON_OBJECT_BUILDER_CREATE(MethodIdentifier.ofStatic(CLASS_JSON, "createObjectBuilder", JSON_OBJECT_BUILDER), (object, arguments) -> new Element(JSON_OBJECT, new JsonObject())), JSON_OBJECT_BUILDER_ADD_BIG_DECIMAL(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, BIG_DECIMAL), (object, arguments) -> mergeJsonStructure(object, arguments, BIG_DECIMAL)), JSON_OBJECT_BUILDER_ADD_BIG_INTEGER(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, BIG_INTEGER), (object, arguments) -> mergeJsonStructure(object, arguments, BIG_INTEGER)), JSON_OBJECT_BUILDER_ADD_STRING(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, STRING), (object, arguments) -> mergeJsonStructure(object, arguments, STRING)), JSON_OBJECT_BUILDER_ADD_INT(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, PRIMITIVE_INT), (object, arguments) -> mergeJsonStructure(object, arguments, INTEGER)), JSON_OBJECT_BUILDER_ADD_LONG(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, PRIMITIVE_LONG), (object, arguments) -> mergeJsonStructure(object, arguments, LONG)), JSON_OBJECT_BUILDER_ADD_DOUBLE(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, PRIMITIVE_DOUBLE), (object, arguments) -> mergeJsonStructure(object, arguments, DOUBLE)), JSON_OBJECT_BUILDER_ADD_BOOLEAN(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, PRIMITIVE_BOOLEAN), (object, arguments) -> mergeJsonStructure(object, arguments, PRIMITIVE_BOOLEAN)), JSON_OBJECT_BUILDER_ADD_JSON(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, JSON_VALUE), KnownJsonResultMethod::mergeJsonStructure), JSON_OBJECT_BUILDER_ADD_JSON_OBJECT(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, JSON_OBJECT_BUILDER), (object, arguments) -> mergeJsonStructure(object, arguments, JSON_OBJECT)), JSON_OBJECT_BUILDER_ADD_JSON_ARRAY(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "add", JSON_OBJECT_BUILDER, STRING, JSON_ARRAY_BUILDER), (object, arguments) -> mergeJsonStructure(object, arguments, JSON_ARRAY)), JSON_OBJECT_BUILDER_ADD_NULL(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "addNull", JSON_OBJECT_BUILDER, STRING), (object, arguments) -> { object.getPossibleValues().stream() .filter(o -> o instanceof JsonObject).map(o -> (JsonObject) o) .forEach(o -> arguments.get(0).getPossibleValues().stream().map(s -> (String) s) .forEach(s -> o.getStructure().merge(s, new Element(OBJECT, null), Element::merge))); return object; }), JSON_OBJECT_BUILDER_BUILD(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT_BUILDER, "build", JSON_OBJECT), (object, arguments) -> { final Element json = new Element(JSON_OBJECT); json.getPossibleValues().addAll(object.getPossibleValues()); return json; }), JSON_OBJECT_GET_BOOLEAN(MethodIdentifier.ofNonStatic(CLASS_JSON_OBJECT, "getBoolean", PRIMITIVE_BOOLEAN, STRING), (object, arguments) -> object.getPossibleValues().stream() .filter(o -> o instanceof JsonObject).map(o -> (JsonObject) o) .map(o -> arguments.get(0).getPossibleValues().stream() .map(s -> (String) s).map(s -> o.getStructure().get(s)) .reduce(new Element(PRIMITIVE_BOOLEAN), Element::merge)) .reduce(new Element(PRIMITIVE_BOOLEAN), Element::merge)); private final MethodIdentifier identifier; private final BiFunction, Element> function; KnownJsonResultMethod(final MethodIdentifier identifier, final BiFunction, Element> function) { this.identifier = identifier; this.function = function; } @Override public Element invoke(final Element object, final List arguments) { if (arguments.size() != identifier.getParameters().size()) throw new IllegalArgumentException("Method arguments do not match expected signature!"); return function.apply(object, arguments); } @Override public boolean matches(final MethodIdentifier identifier) { return this.identifier.equals(identifier); } private static Element addToArray(final Element object, final List arguments) { return addToArray(object, arguments.get(0)); } private static Element addToArray(final Element object, final List arguments, final String typeOverride) { final Element element = new Element(typeOverride); element.getPossibleValues().addAll(arguments.get(0).getPossibleValues()); return addToArray(object, element); } private static Element addToArray(final Element object, final Element argument) { object.getPossibleValues().stream() .filter(o -> o instanceof JsonArray).map(o -> (JsonArray) o) .forEach(a -> a.getElements().add(argument)); return object; } private static Element mergeJsonStructure(final Element object, final List arguments) { final Element element = new Element(arguments.get(1).getTypes()); element.merge(arguments.get(1)); return mergeJsonStructure(object, arguments.get(0), element); } private static Element mergeJsonStructure(final Element object, final List arguments, final String typeOverride) { final Element element = new Element(typeOverride); element.getPossibleValues().addAll(arguments.get(1).getPossibleValues()); return mergeJsonStructure(object, arguments.get(0), element); } private static Element mergeJsonStructure(final Element object, final Element key, final Element argument) { object.getPossibleValues().stream() .filter(o -> o instanceof JsonObject).map(o -> (JsonObject) o) .forEach(o -> key.getPossibleValues().stream().map(s -> (String) s) .forEach(s -> o.getStructure().merge(s, argument, Element::merge))); return object; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/simulation/KnownResponseResultMethod.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.elements.HttpResponse; import com.sebastian_daschner.jaxrs_analyzer.model.elements.JsonValue; import com.sebastian_daschner.jaxrs_analyzer.model.elements.MethodHandle; import com.sebastian_daschner.jaxrs_analyzer.model.methods.IdentifiableMethod; import com.sebastian_daschner.jaxrs_analyzer.model.methods.Method; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.BiFunction; import java.util.stream.Collectors; import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.INITIALIZER_NAME; import static com.sebastian_daschner.jaxrs_analyzer.model.Types.*; import static com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier.ofNonStatic; import static com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier.ofStatic; /** * Known methods which apply logic to the result or to the return element. * * @author Sebastian Daschner */ enum KnownResponseResultMethod implements IdentifiableMethod { // non-static methods in ResponseBuilder -------------------------- RESPONSE_BUILDER_BUILD(ofNonStatic(CLASS_RESPONSE_BUILDER, "build", RESPONSE), (object, arguments) -> object), RESPONSE_BUILDER_CACHE_CONTROL(ofNonStatic(CLASS_RESPONSE_BUILDER, "cacheControl", RESPONSE_BUILDER, "Ljavax/ws/rs/core/CacheControl;"), (object, arguments) -> addHeader(object, HttpHeaders.CACHE_CONTROL)), RESPONSE_BUILDER_CONTENT_LOCATION(ofNonStatic(CLASS_RESPONSE_BUILDER, "contentLocation", RESPONSE_BUILDER, URI), (object, arguments) -> addHeader(object, HttpHeaders.CONTENT_LOCATION)), RESPONSE_BUILDER_COOKIE(ofNonStatic(CLASS_RESPONSE_BUILDER, "cookie", RESPONSE_BUILDER, "[Ljavax/ws/rs/core/NewCookie;"), (object, arguments) -> addHeader(object, HttpHeaders.SET_COOKIE)), RESPONSE_BUILDER_ENCODING(ofNonStatic(CLASS_RESPONSE_BUILDER, "encoding", RESPONSE_BUILDER, STRING), (object, arguments) -> addHeader(object, HttpHeaders.CONTENT_ENCODING)), RESPONSE_BUILDER_ENTITY(ofNonStatic(CLASS_RESPONSE_BUILDER, "entity", RESPONSE_BUILDER, OBJECT), (object, arguments) -> addEntity(object, arguments.get(0))), RESPONSE_BUILDER_ENTITY_ANNOTATION(ofNonStatic(CLASS_RESPONSE_BUILDER, "entity", RESPONSE_BUILDER, OBJECT, "[Ljava/lang/annotation/Annotation;"), (object, arguments) -> addEntity(object, arguments.get(0))), RESPONSE_BUILDER_EXPIRES(ofNonStatic(CLASS_RESPONSE_BUILDER, "expires", RESPONSE_BUILDER, DATE), (object, arguments) -> addHeader(object, HttpHeaders.EXPIRES)), RESPONSE_BUILDER_HEADER(ofNonStatic(CLASS_RESPONSE_BUILDER, "header", RESPONSE_BUILDER, STRING, OBJECT), (object, arguments) -> { arguments.get(0).getPossibleValues().stream() .map(header -> (String) header).forEach(h -> addHeader(object, h)); return object; }), RESPONSE_BUILDER_LANGUAGE_LOCALE(ofNonStatic(CLASS_RESPONSE_BUILDER, "language", RESPONSE_BUILDER, "Ljava/util/Locale;"), (object, arguments) -> addHeader(object, HttpHeaders.CONTENT_LANGUAGE)), RESPONSE_BUILDER_LANGUAGE_STRING(ofNonStatic(CLASS_RESPONSE_BUILDER, "language", RESPONSE_BUILDER, STRING), (object, arguments) -> addHeader(object, HttpHeaders.CONTENT_LANGUAGE)), RESPONSE_BUILDER_LAST_MODIFIED(ofNonStatic(CLASS_RESPONSE_BUILDER, "lastModified", RESPONSE_BUILDER, DATE), (object, arguments) -> addHeader(object, HttpHeaders.LAST_MODIFIED)), RESPONSE_BUILDER_LINK_URI(ofNonStatic(CLASS_RESPONSE_BUILDER, "link", RESPONSE_BUILDER, URI, STRING), (object, arguments) -> addHeader(object, HttpHeaders.LINK)), RESPONSE_BUILDER_LINK_STRING(ofNonStatic(CLASS_RESPONSE_BUILDER, "link", RESPONSE_BUILDER, STRING, STRING), (object, arguments) -> addHeader(object, HttpHeaders.LINK)), RESPONSE_BUILDER_LINKS(ofNonStatic(CLASS_RESPONSE_BUILDER, "links", RESPONSE_BUILDER, "[Ljavax/ws/rs/core/Link;"), (object, arguments) -> addHeader(object, HttpHeaders.LINK)), RESPONSE_BUILDER_LOCATION(ofNonStatic(CLASS_RESPONSE_BUILDER, "location", RESPONSE_BUILDER, URI), (object, arguments) -> addHeader(object, HttpHeaders.LOCATION)), RESPONSE_BUILDER_STATUS_ENUM(ofNonStatic(CLASS_RESPONSE_BUILDER, "status", RESPONSE_BUILDER, RESPONSE_STATUS), (object, arguments) -> { arguments.get(0).getPossibleValues().stream() .map(status -> ((Response.Status) status).getStatusCode()).forEach(s -> addStatus(object, s)); return object; }), RESPONSE_BUILDER_STATUS_INT(ofNonStatic(CLASS_RESPONSE_BUILDER, "status", RESPONSE_BUILDER, PRIMITIVE_INT), (object, arguments) -> { arguments.get(0).getPossibleValues().stream() .map(status -> (int) status).forEach(s -> addStatus(object, s)); return object; }), RESPONSE_BUILDER_TAG_ENTITY(ofNonStatic(CLASS_RESPONSE_BUILDER, "tag", RESPONSE_BUILDER, ENTITY_TAG), (object, arguments) -> addHeader(object, HttpHeaders.ETAG)), RESPONSE_BUILDER_TAG_STRING(ofNonStatic(CLASS_RESPONSE_BUILDER, "tag", RESPONSE_BUILDER, STRING), (object, arguments) -> addHeader(object, HttpHeaders.ETAG)), RESPONSE_BUILDER_TYPE(ofNonStatic(CLASS_RESPONSE_BUILDER, "type", RESPONSE_BUILDER, "Ljavax/ws/rs/core/MediaType;"), (object, arguments) -> { arguments.get(0).getPossibleValues().stream() .map(m -> (MediaType) m).map(m -> m.getType() + '/' + m.getSubtype()).forEach(t -> addContentType(object, t)); return object; }), RESPONSE_BUILDER_TYPE_STRING(ofNonStatic(CLASS_RESPONSE_BUILDER, "type", RESPONSE_BUILDER, STRING), (object, arguments) -> { arguments.get(0).getPossibleValues().stream() .map(t -> (String) t).forEach(t -> addContentType(object, t)); return object; }), RESPONSE_BUILDER_VARIANT(ofNonStatic(CLASS_RESPONSE_BUILDER, "variant", RESPONSE_BUILDER, VARIANT), (object, arguments) -> { addHeader(object, HttpHeaders.CONTENT_LANGUAGE); addHeader(object, HttpHeaders.CONTENT_ENCODING); return object; }), RESPONSE_BUILDER_VARIANTS_LIST(ofNonStatic(CLASS_RESPONSE_BUILDER, "variants", RESPONSE_BUILDER, LIST), (object, arguments) -> addHeader(object, HttpHeaders.VARY)), RESPONSE_BUILDER_VARIANTS_ARRAY(ofNonStatic(CLASS_RESPONSE_BUILDER, "variants", RESPONSE_BUILDER, "[Ljavax/ws/rs/core/Variant;"), (object, arguments) -> addHeader(object, HttpHeaders.VARY)), // static methods in Response -------------------------- RESPONSE_STATUS_ENUM(ofStatic(CLASS_RESPONSE, "status", RESPONSE_BUILDER, RESPONSE_STATUS), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(0).getPossibleValues().stream() .map(status -> ((Response.Status) status).getStatusCode()).forEach(s -> addStatus(object, s)); return object; }), RESPONSE_STATUS_INT(ofStatic(CLASS_RESPONSE, "status", RESPONSE_BUILDER, PRIMITIVE_INT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(0).getPossibleValues().stream() .map(status -> (int) status).forEach(s -> addStatus(object, s)); return object; }), RESPONSE_OK(ofStatic(CLASS_RESPONSE, "ok", RESPONSE_BUILDER), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.OK.getStatusCode()); }), RESPONSE_OK_ENTITY(ofStatic(CLASS_RESPONSE, "ok", RESPONSE_BUILDER, OBJECT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.OK.getStatusCode()); return addEntity(object, arguments.get(0)); }), RESPONSE_OK_VARIANT(ofStatic(CLASS_RESPONSE, "ok", RESPONSE_BUILDER, OBJECT, VARIANT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.OK.getStatusCode()); addEntity(object, arguments.get(0)); addHeader(object, HttpHeaders.CONTENT_LANGUAGE); return addHeader(object, HttpHeaders.CONTENT_ENCODING); }), RESPONSE_OK_MEDIATYPE(ofStatic(CLASS_RESPONSE, "ok", RESPONSE_BUILDER, OBJECT, "Ljavax/ws/rs/core/MediaType;"), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.OK.getStatusCode()); arguments.get(1).getPossibleValues().stream().map(m -> (MediaType) m) .map(m -> m.getType() + '/' + m.getSubtype()).forEach(t -> addContentType(object, t)); return addEntity(object, arguments.get(0)); }), RESPONSE_OK_MEDIATYPE_STRING(ofStatic(CLASS_RESPONSE, "ok", RESPONSE_BUILDER, OBJECT, STRING), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.OK.getStatusCode()); arguments.get(1).getPossibleValues().stream() .map(t -> (String) t).forEach(t -> addContentType(object, t)); return addEntity(object, arguments.get(0)); }), RESPONSE_ACCEPTED(ofStatic(CLASS_RESPONSE, "accepted", RESPONSE_BUILDER), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.ACCEPTED.getStatusCode()); }), RESPONSE_ACCEPTED_ENTITY(ofStatic(CLASS_RESPONSE, "accepted", RESPONSE_BUILDER, OBJECT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.ACCEPTED.getStatusCode()); return addEntity(object, arguments.get(0)); }), RESPONSE_CREATED(ofStatic(CLASS_RESPONSE, "created", RESPONSE_BUILDER, URI), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.CREATED.getStatusCode()); return addHeader(object, HttpHeaders.LOCATION); }), RESPONSE_NO_CONTENT(ofStatic(CLASS_RESPONSE, "noContent", RESPONSE_BUILDER), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.NO_CONTENT.getStatusCode()); }), RESPONSE_NOT_ACCEPTABLE(ofStatic(CLASS_RESPONSE, "notAcceptable", RESPONSE_BUILDER, LIST), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.NOT_ACCEPTABLE.getStatusCode()); return addHeader(object, HttpHeaders.VARY); }), RESPONSE_NOT_MODIFIED(ofStatic(CLASS_RESPONSE, "notModified", RESPONSE_BUILDER), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.NOT_MODIFIED.getStatusCode()); }), RESPONSE_NOT_MODIFIED_ENTITYTAG(ofStatic(CLASS_RESPONSE, "notModified", RESPONSE_BUILDER, ENTITY_TAG), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.NOT_MODIFIED.getStatusCode()); return addHeader(object, HttpHeaders.ETAG); }), RESPONSE_NOT_MODIFIED_ENTITYTAG_STRING(ofStatic(CLASS_RESPONSE, "notModified", RESPONSE_BUILDER, STRING), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.NOT_MODIFIED.getStatusCode()); return addHeader(object, HttpHeaders.ETAG); }), RESPONSE_SEE_OTHER(ofStatic(CLASS_RESPONSE, "seeOther", RESPONSE_BUILDER, URI), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.SEE_OTHER.getStatusCode()); return addHeader(object, HttpHeaders.LOCATION); }), RESPONSE_TEMPORARY_REDIRECT(ofStatic(CLASS_RESPONSE, "temporaryRedirect", RESPONSE_BUILDER, URI), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); addStatus(object, Response.Status.TEMPORARY_REDIRECT.getStatusCode()); return addHeader(object, HttpHeaders.LOCATION); }), RESPONSE_SERVER_ERROR(ofStatic(CLASS_RESPONSE, "serverError", RESPONSE_BUILDER), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); }), // WebApplicationExceptions -------------------------- WEB_APPLICATION_EXCEPTION_EMPTY(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); }), WEB_APPLICATION_EXCEPTION_MESSAGE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); }), WEB_APPLICATION_EXCEPTION_RESPONSE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, RESPONSE), (notAvailable, arguments) -> arguments.get(0)), WEB_APPLICATION_EXCEPTION_MESSAGE_RESPONSE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, RESPONSE), (notAvailable, arguments) -> arguments.get(1)), WEB_APPLICATION_EXCEPTION_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, PRIMITIVE_INT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(0).getPossibleValues().stream() .map(status -> (int) status).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_MESSAGE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, PRIMITIVE_INT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(1).getPossibleValues().stream() .map(status -> (int) status).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_RESPONSE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, RESPONSE_STATUS), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(0).getPossibleValues().stream() .map(status -> ((Response.Status) status).getStatusCode()).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_MESSAGE_RESPONSE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, RESPONSE_STATUS), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(1).getPossibleValues().stream() .map(status -> ((Response.Status) status).getStatusCode()).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_CAUSE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, THROWABLE), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); }), WEB_APPLICATION_EXCEPTION_MESSAGE_CAUSE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, THROWABLE), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); return addStatus(object, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); }), WEB_APPLICATION_EXCEPTION_CAUSE_RESPONSE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, THROWABLE, RESPONSE), (notAvailable, arguments) -> arguments.get(1)), WEB_APPLICATION_EXCEPTION_MESSAGE_CAUSE_RESPONSE(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, THROWABLE, RESPONSE), (notAvailable, arguments) -> arguments.get(2)), WEB_APPLICATION_EXCEPTION_CAUSE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, THROWABLE, PRIMITIVE_INT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(1).getPossibleValues().stream() .map(status -> (int) status).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_MESSAGE_CAUSE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, THROWABLE, PRIMITIVE_INT), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(2).getPossibleValues().stream() .map(status -> (int) status).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_CAUSE_RESPONSE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, THROWABLE, RESPONSE_STATUS), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(1).getPossibleValues().stream() .map(status -> ((Response.Status) status).getStatusCode()).forEach(s -> addStatus(object, s)); return object; }), WEB_APPLICATION_EXCEPTION_MESSAGE_CAUSE_RESPONSE_STATUS(ofNonStatic(CLASS_WEB_APPLICATION_EXCEPTION, INITIALIZER_NAME, PRIMITIVE_VOID, STRING, THROWABLE, RESPONSE_STATUS), (notAvailable, arguments) -> { final Element object = new Element(RESPONSE, new HttpResponse()); arguments.get(2).getPossibleValues().stream() .map(status -> ((Response.Status) status).getStatusCode()).forEach(s -> addStatus(object, s)); return object; }), // other methods -------------------------- RESOURCE_CONTEXT_INIT(ofNonStatic(CLASS_RESOURCE_CONTEXT, "getResource", OBJECT, CLASS), (object, arguments) -> new Element(arguments.get(0).getPossibleValues().stream() .filter(s -> s instanceof String).map(s -> (String) s).collect(Collectors.toSet())) ), RESOURCE_CONTEXT_GET(ofNonStatic(CLASS_RESOURCE_CONTEXT, "initResource", OBJECT, OBJECT), (object, arguments) -> new Element(arguments.get(0).getTypes())), INTEGER_VALUE_OF(ofStatic(CLASS_INTEGER, "valueOf", PRIMITIVE_INT, INTEGER), (object, arguments) -> new Element(INTEGER, arguments.get(0).getPossibleValues().toArray())), DOUBLE_VALUE_OF(ofStatic(CLASS_DOUBLE, "valueOf", PRIMITIVE_DOUBLE, DOUBLE), (object, arguments) -> new Element(INTEGER, arguments.get(0).getPossibleValues().toArray())), LONG_VALUE_OF(ofStatic(CLASS_LONG, "valueOf", PRIMITIVE_LONG, LONG), (object, arguments) -> new Element(INTEGER, arguments.get(0).getPossibleValues().toArray())), // stream related methods -------------------------- LIST_STREAM(ofNonStatic(CLASS_LIST, "stream", STREAM), (object, arguments) -> new Element(object.getTypes())), LIST_FOR_EACH(ofNonStatic(CLASS_LIST, "forEach", PRIMITIVE_VOID, CONSUMER), (object, arguments) -> { if (arguments.get(0) instanceof MethodHandle) ((Method) arguments.get(0)).invoke(null, Collections.singletonList(object)); return null; }), SET_STREAM(ofNonStatic(CLASS_SET, "stream", STREAM), (object, arguments) -> new Element(object.getTypes())), SET_FOR_EACH(ofNonStatic(CLASS_SET, "forEach", PRIMITIVE_VOID, CONSUMER), (object, arguments) -> { if (arguments.get(0) instanceof MethodHandle) ((Method) arguments.get(0)).invoke(null, Collections.singletonList(object)); return null; }), STREAM_COLLECT(ofNonStatic(CLASS_STREAM, "collect", OBJECT, SUPPLIER, BI_CONSUMER, BI_CONSUMER), (object, arguments) -> { if (arguments.get(0) instanceof MethodHandle && arguments.get(1) instanceof MethodHandle) { final Element collectionElement = ((Method) arguments.get(0)).invoke(null, Collections.emptyList()); ((Method) arguments.get(1)).invoke(null, Arrays.asList(collectionElement, object)); return collectionElement; } return new Element(); }), STREAM_FOR_EACH(ofNonStatic(CLASS_STREAM, "forEach", PRIMITIVE_VOID, CONSUMER), (object, arguments) -> { if (arguments.get(0) instanceof MethodHandle) ((Method) arguments.get(0)).invoke(null, Collections.singletonList(object)); return null; }), STREAM_MAP(ofNonStatic(CLASS_STREAM, "map", STREAM, "Ljava/util/function/Function;"), (object, arguments) -> { if (arguments.get(0) instanceof MethodHandle) { return ((MethodHandle) arguments.get(0)).invoke(null, Collections.singletonList(object)); } return new Element(); }); private final MethodIdentifier identifier; private final BiFunction, Element> function; KnownResponseResultMethod(final MethodIdentifier identifier, final BiFunction, Element> function) { this.identifier = identifier; this.function = function; } @Override public Element invoke(final Element object, final List arguments) { if (arguments.size() != identifier.getParameters().size()) throw new IllegalArgumentException("Method arguments do not match expected signature!"); return function.apply(object, arguments); } @Override public boolean matches(final MethodIdentifier identifier) { return this.identifier.equals(identifier); } private static Element addHeader(final Element object, final String header) { object.getPossibleValues().stream().filter(r -> r instanceof HttpResponse).map(r -> (HttpResponse) r).forEach(r -> r.getHeaders().add(header)); return object; } private static Element addEntity(final Element object, final Element argument) { object.getPossibleValues().stream().filter(r -> r instanceof HttpResponse).map(r -> (HttpResponse) r) .forEach(r -> { r.getEntityTypes().addAll(argument.getTypes()); argument.getPossibleValues().stream().filter(j -> j instanceof JsonValue).map(j -> (JsonValue) j).forEach(j -> r.getInlineEntities().add(j)); }); return object; } private static Element addStatus(final Element object, final Integer status) { object.getPossibleValues().stream().filter(r -> r instanceof HttpResponse).map(r -> (HttpResponse) r).forEach(r -> r.getStatuses().add(status)); return object; } private static Element addContentType(final Element object, final String type) { object.getPossibleValues().stream().filter(r -> r instanceof HttpResponse).map(r -> (HttpResponse) r).forEach(r -> r.getContentTypes().add(type)); return object; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/simulation/MethodPool.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.methods.IdentifiableMethod; import com.sebastian_daschner.jaxrs_analyzer.model.methods.Method; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.methods.ProjectMethod; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import java.util.stream.Stream; import static com.sebastian_daschner.jaxrs_analyzer.model.Types.PRIMITIVE_VOID; /** * A thread-safe singleton pool of known {@link Method}s. * * @author Sebastian Daschner */ public class MethodPool { /** * The only instance of the method pool. */ private static final MethodPool INSTANCE = new MethodPool(); private static final Function DEFAULT_METHOD = identifier -> (object, arguments) -> { if (!PRIMITIVE_VOID.equals(identifier.getReturnType())) return new Element(identifier.getReturnType()); return null; }; private final List availableMethods; private final ReadWriteLock readWriteLock; private MethodPool() { availableMethods = new LinkedList<>(); // order matters, known methods are taken first Stream.of(KnownResponseResultMethod.values()).forEach(availableMethods::add); Stream.of(KnownJsonResultMethod.values()).forEach(availableMethods::add); readWriteLock = new ReentrantReadWriteLock(); } /** * Adds a project method to the pool. * * @param method The method to add */ public void addProjectMethod(final ProjectMethod method) { readWriteLock.writeLock().lock(); try { availableMethods.add(method); } finally { readWriteLock.writeLock().unlock(); } } /** * Returns a method identified by an method identifier. * * @param identifier The method identifier * @return The found method or a default handler */ public Method get(final MethodIdentifier identifier) { // search for available methods readWriteLock.readLock().lock(); try { final Optional method = availableMethods.stream().filter(m -> m.matches(identifier)).findAny(); if (method.isPresent()) return method.get(); } finally { readWriteLock.readLock().unlock(); } // apply default behaviour return DEFAULT_METHOD.apply(identifier); } /** * Returns the singleton instance. * * @return The method pool */ public static MethodPool getInstance() { return INSTANCE; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/bytecode/simulation/MethodSimulator.java ================================================ /* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.simulation; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.elements.Element; import com.sebastian_daschner.jaxrs_analyzer.model.elements.MethodHandle; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.*; import com.sebastian_daschner.jaxrs_analyzer.model.methods.Method; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import org.objectweb.asm.Label; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import java.util.stream.IntStream; import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.determineLeastSpecificType; import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.toType; /** * Simulates the instructions of a method. This class is thread-safe. * * @author Sebastian Daschner */ public class MethodSimulator { private final Lock lock = new ReentrantLock(); private final MethodPool methodPool = MethodPool.getInstance(); private final Stack runtimeStack = new Stack<>(); private final MultivaluedMap variableInvalidation = new MultivaluedHashMap<>(); private Label active; Map localVariables = new HashMap<>(); private Element returnElement; /** * Simulates the instructions and collects information about the resource method. * * @param instructions The instructions of the method * @return The return element merged with all possible values */ public Element simulate(final List instructions) { lock.lock(); try { returnElement = null; return simulateInternal(instructions); } finally { lock.unlock(); } } /** * Simulates the instructions of the method. * * @param instructions The instructions to simulate * @return The return element of the method */ Element simulateInternal(final List instructions) { instructions.forEach(this::simulate); return returnElement; } /** * Simulates the instruction. * * @param instruction The instruction to simulate */ private void simulate(final Instruction instruction) { switch (instruction.getType()) { case PUSH: final PushInstruction pushInstruction = (PushInstruction) instruction; runtimeStack.push(new Element(pushInstruction.getValueType(), pushInstruction.getValue())); break; case METHOD_HANDLE: simulateMethodHandle((InvokeDynamicInstruction) instruction); break; case INVOKE: simulateInvoke((InvokeInstruction) instruction); break; case GET_FIELD: runtimeStack.pop(); runtimeStack.push(new Element(((GetFieldInstruction) instruction).getPropertyType())); break; case GET_STATIC: final GetStaticInstruction getStaticInstruction = (GetStaticInstruction) instruction; final Object value = getStaticInstruction.getValue(); if (value != null) runtimeStack.push(new Element(getStaticInstruction.getPropertyType(), value)); else runtimeStack.push(new Element(getStaticInstruction.getPropertyType())); break; case LOAD: final LoadInstruction loadInstruction = (LoadInstruction) instruction; runtimeStack.push(localVariables.getOrDefault(loadInstruction.getNumber(), new Element(loadInstruction.getVariableType()))); runtimeStack.peek().getTypes().add(loadInstruction.getVariableType()); variableInvalidation.add(loadInstruction.getValidUntil(), loadInstruction.getNumber()); break; case STORE: simulateStore((StoreInstruction) instruction); break; case SIZE_CHANGE: simulateSizeChange((SizeChangingInstruction) instruction); break; case NEW: final NewInstruction newInstruction = (NewInstruction) instruction; runtimeStack.push(new Element(toType(newInstruction.getClassName()))); break; case DUP: runtimeStack.push(runtimeStack.peek()); break; case OTHER: // do nothing break; case RETURN: mergeReturnElement(runtimeStack.pop()); case THROW: mergePossibleResponse(); // stack has to be empty for further analysis runtimeStack.clear(); break; default: throw new IllegalArgumentException("Instruction without type!"); } if (instruction.getLabel() != active && variableInvalidation.containsKey(active)) { variableInvalidation.get(active).forEach(localVariables::remove); } active = instruction.getLabel(); } /** * Simulates the invoke dynamic call. Pushes a method handle on the stack. * * @param instruction The instruction to simulate */ private void simulateMethodHandle(final InvokeDynamicInstruction instruction) { final List arguments = IntStream.range(0, instruction.getDynamicIdentifier().getParameters().size()) .mapToObj(t -> runtimeStack.pop()).collect(Collectors.toList()); Collections.reverse(arguments); if (!instruction.getDynamicIdentifier().isStaticMethod()) // first parameter is `this` arguments.remove(0); // adds the transferred arguments of the bootstrap call runtimeStack.push(new MethodHandle(instruction.getDynamicIdentifier().getReturnType(), instruction.getIdentifier(), arguments)); } /** * Simulates the invoke instruction. * * @param instruction The instruction to simulate */ private void simulateInvoke(final InvokeInstruction instruction) { final List arguments = new LinkedList<>(); MethodIdentifier identifier = instruction.getIdentifier(); IntStream.range(0, identifier.getParameters().size()).forEach(i -> arguments.add(runtimeStack.pop())); Collections.reverse(arguments); Element object = null; Method method; if (!identifier.isStaticMethod()) { object = runtimeStack.pop(); if (object instanceof MethodHandle) { method = (Method) object; } else { method = methodPool.get(identifier); } } else { method = methodPool.get(identifier); } final Element returnedElement = method.invoke(object, arguments); if (returnedElement != null) runtimeStack.push(returnedElement); else if (!identifier.getReturnType().equals(Types.PRIMITIVE_VOID)) runtimeStack.push(new Element(identifier.getReturnType())); } /** * Simulates the store instruction. * * @param instruction The instruction to simulate */ private void simulateStore(final StoreInstruction instruction) { final int index = instruction.getNumber(); final Element elementToStore = runtimeStack.pop(); if (elementToStore instanceof MethodHandle) mergeMethodHandleStore(index, (MethodHandle) elementToStore); else mergeElementStore(index, instruction.getVariableType(), elementToStore); } /** * Merges a stored element to the local variables. * * @param index The index of the variable * @param type The type of the variable or the element (whatever is more specific) * @param element The element to merge */ private void mergeElementStore(final int index, final String type, final Element element) { // new element must be created for immutability final String elementType = type.equals(Types.OBJECT) ? determineLeastSpecificType(element.getTypes().toArray(new String[element.getTypes().size()])) : type; final Element created = new Element(elementType); created.merge(element); localVariables.merge(index, created, Element::merge); } /** * Merges a stored method handle to the local variables. * * @param index The index of the variable * @param methodHandle The method handle to merge */ private void mergeMethodHandleStore(final int index, final MethodHandle methodHandle) { localVariables.merge(index, new MethodHandle(methodHandle), Element::merge); } /** * Checks if the current stack element is eligible for being merged with the returned element. */ private void mergePossibleResponse() { // TODO only HttpResponse element? if (!runtimeStack.isEmpty() && runtimeStack.peek().getTypes().contains(Types.RESPONSE)) { mergeReturnElement(runtimeStack.peek()); } } /** * Simulates the size change instruction. * * @param instruction The instruction to simulate */ private void simulateSizeChange(final SizeChangingInstruction instruction) { IntStream.range(0, instruction.getNumberOfPops()).forEach(i -> runtimeStack.pop()); IntStream.range(0, instruction.getNumberOfPushes()).forEach(i -> runtimeStack.push(new Element())); } /** * Merges the {@code returnElement} with the given element which was popped from the stack. * If the {@code returnElement} existed before, the values are merged. * * @param stackElement The popped element */ private void mergeReturnElement(final Element stackElement) { if (returnElement != null) stackElement.merge(returnElement); returnElement = stackElement; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/ContextClassReader.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import org.objectweb.asm.ClassReader; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; /** * A {@link ClassReader} that is able to use a separate {@link ClassLoader}. * * @author Sebastian Daschner */ public class ContextClassReader extends ClassReader { private static final ExtensibleClassLoader CLASS_LOADER = new ExtensibleClassLoader(); public ContextClassReader(final String className) throws IOException { super(CLASS_LOADER.getResourceAsStream(className.replace('.', '/') + ".class")); } public static ClassLoader getClassLoader() { return CLASS_LOADER; } public static void addClassPath(final URL url) { CLASS_LOADER.addURL(url); } private static class ExtensibleClassLoader extends URLClassLoader { ExtensibleClassLoader() { super(new URL[]{}); } @Override public void addURL(final URL url) { super.addURL(url); } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/JAXRSAnnotatedSuperMethodClassVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import java.lang.reflect.Method; import static org.objectweb.asm.Opcodes.*; /** * @author Sebastian Daschner */ class JAXRSAnnotatedSuperMethodClassVisitor extends ClassVisitor { private final MethodResult methodResult; private final Method method; JAXRSAnnotatedSuperMethodClassVisitor(final MethodResult methodResult, final Method method) { super(ASM5); this.methodResult = methodResult; this.method = method; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final boolean legalModifiers = ((access & ACC_SYNTHETIC) | (access & ACC_STATIC) | (access & ACC_NATIVE)) == 0; final String descriptor = Type.getMethodDescriptor(method); if (legalModifiers && method.getName().equals(name) && (descriptor.equals(desc) || descriptor.equals(signature))) return new JAXRSAnnotatedSuperMethodVisitor(methodResult); return null; } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/JAXRSAnnotatedSuperMethodVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.*; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.rest.HttpMethod; import com.sebastian_daschner.jaxrs_analyzer.model.rest.MethodParameter; import com.sebastian_daschner.jaxrs_analyzer.model.rest.ParameterType; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.MethodVisitor; import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.objectweb.asm.Opcodes.ASM5; /** * @author Sebastian Daschner */ class JAXRSAnnotatedSuperMethodVisitor extends MethodVisitor { private final MethodResult methodResult; private final List parameterTypes; private final Map methodParameters; private final BitSet annotatedParameters; JAXRSAnnotatedSuperMethodVisitor(final MethodResult methodResult) { super(ASM5); this.methodResult = methodResult; parameterTypes = methodResult.getOriginalMethodSignature().getParameters(); annotatedParameters = new BitSet(parameterTypes.size()); methodParameters = new HashMap<>(); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { switch (desc) { case Types.GET: methodResult.setHttpMethod(HttpMethod.GET); break; case Types.POST: methodResult.setHttpMethod(HttpMethod.POST); break; case Types.PUT: methodResult.setHttpMethod(HttpMethod.PUT); break; case Types.DELETE: methodResult.setHttpMethod(HttpMethod.DELETE); break; case Types.HEAD: methodResult.setHttpMethod(HttpMethod.HEAD); break; case Types.OPTIONS: methodResult.setHttpMethod(HttpMethod.OPTIONS); break; case Types.DEPRECATED: methodResult.setDeprecated(true); break; case Types.PATH: return new PathAnnotationVisitor(methodResult); case Types.CONSUMES: return new ConsumesAnnotationVisitor(methodResult); case Types.PRODUCES: return new ProducesAnnotationVisitor(methodResult); } return null; } @Override public AnnotationVisitor visitParameterAnnotation(final int index, final String annotationDesc, final boolean visible) { switch (annotationDesc) { case Types.PATH_PARAM: return paramAnnotationVisitor(index, ParameterType.PATH); case Types.QUERY_PARAM: return paramAnnotationVisitor(index, ParameterType.QUERY); case Types.HEADER_PARAM: return paramAnnotationVisitor(index, ParameterType.HEADER); case Types.FORM_PARAM: return paramAnnotationVisitor(index, ParameterType.FORM); case Types.COOKIE_PARAM: return paramAnnotationVisitor(index, ParameterType.COOKIE); case Types.MATRIX_PARAM: return paramAnnotationVisitor(index, ParameterType.MATRIX); case Types.DEFAULT_VALUE: return defaultAnnotationVisitor(index); case Types.SUSPENDED: LogProvider.debug("Handling of " + annotationDesc + " not yet implemented"); case Types.CONTEXT: annotatedParameters.set(index); default: return null; } } private AnnotationVisitor paramAnnotationVisitor(final int index, final ParameterType parameterType) { annotatedParameters.set(index); final String type = parameterTypes.get(index); MethodParameter methodParameter = methodParameters.get(index); if (methodParameter == null) { methodParameter = new MethodParameter(TypeIdentifier.ofType(type), parameterType); methodParameters.put(index, methodParameter); } else { methodParameter.setParameterType(parameterType); } return new ParamAnnotationVisitor(methodParameter); } private AnnotationVisitor defaultAnnotationVisitor(final int index) { final String type = parameterTypes.get(index); MethodParameter methodParameter = methodParameters.get(index); if (methodParameter == null) { methodParameter = new MethodParameter(TypeIdentifier.ofType(type)); methodParameters.put(index, methodParameter); } return new DefaultValueAnnotationVisitor(methodParameter); } @Override public void visitEnd() { if (annotatedParameters.cardinality() != parameterTypes.size()) { final String requestBodyType = parameterTypes.get(annotatedParameters.nextClearBit(0)); methodResult.setRequestBodyType(requestBodyType); } methodResult.getMethodParameters().addAll(methodParameters.values()); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/JAXRSClassVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ApplicationPathAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ConsumesAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.PathAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ProducesAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.*; import javax.ws.rs.*; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Stream; import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.isAnnotationPresent; import static org.objectweb.asm.Opcodes.*; /** * @author Sebastian Daschner */ public class JAXRSClassVisitor extends ClassVisitor { private static final Class[] RELEVANT_METHOD_ANNOTATIONS = new Class[]{Path.class, GET.class, PUT.class, POST.class, DELETE.class, OPTIONS.class, HEAD.class}; private final ClassResult classResult; public JAXRSClassVisitor(final ClassResult classResult) { super(ASM5); this.classResult = classResult; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { classResult.setOriginalClass(name); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { switch (desc) { case Types.PATH: return new PathAnnotationVisitor(classResult); case Types.APPLICATION_PATH: return new ApplicationPathAnnotationVisitor(classResult); case Types.CONSUMES: return new ConsumesAnnotationVisitor(classResult); case Types.PRODUCES: return new ProducesAnnotationVisitor(classResult); case Types.DEPRECATED: classResult.setDeprecated(true); break; } return null; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if ((access & ACC_STATIC) == 0) return new JAXRSFieldVisitor(classResult, desc, signature); return null; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final boolean legalModifiers = ((access & ACC_SYNTHETIC) | (access & ACC_STATIC) | (access & ACC_NATIVE)) == 0; final String methodSignature = signature == null ? desc : signature; final MethodIdentifier identifier = MethodIdentifier.of(classResult.getOriginalClass(), name, methodSignature, false); if (legalModifiers && !"".equals(name)) { final MethodResult methodResult = new MethodResult(); if (hasJAXRSAnnotations(classResult.getOriginalClass(), name, methodSignature)) return new JAXRSMethodVisitor(identifier, classResult, methodResult, true); else { final Method annotatedSuperMethod = searchAnnotatedSuperMethod(classResult.getOriginalClass(), name, methodSignature); if (annotatedSuperMethod != null) { try { return new JAXRSMethodVisitor(identifier, classResult, methodResult, false); } finally { classResult.getMethods().stream().filter(m -> m.equals(methodResult)).findAny().ifPresent(m -> visitJAXRSSuperMethod(annotatedSuperMethod, m)); } } } } return null; } private static boolean hasJAXRSAnnotations(final String className, final String methodName, final String signature) { final Method method = JavaUtils.findMethod(className, methodName, signature); return method != null && hasJAXRSAnnotations(method); } private static Method searchAnnotatedSuperMethod(final String className, final String methodName, final String methodSignature) { final List> superTypes = determineSuperTypes(className); return superTypes.stream().map(c -> { final Method superAnnotatedMethod = JavaUtils.findMethod(c, methodName, methodSignature); if (superAnnotatedMethod != null && hasJAXRSAnnotations(superAnnotatedMethod)) return superAnnotatedMethod; return null; }).filter(Objects::nonNull).findAny().orElse(null); } private static List> determineSuperTypes(final String className) { final Class loadedClass = JavaUtils.loadClassFromName(className); if (loadedClass == null) return Collections.emptyList(); final List> superClasses = new ArrayList<>(); final Queue> classesToCheck = new LinkedBlockingQueue<>(); Class currentClass = loadedClass; do { if (currentClass.getSuperclass() != null && Object.class != currentClass.getSuperclass()) classesToCheck.add(currentClass.getSuperclass()); Stream.of(currentClass.getInterfaces()).forEach(classesToCheck::add); if (currentClass != loadedClass) superClasses.add(currentClass); } while ((currentClass = classesToCheck.poll()) != null); return superClasses; } private static boolean hasJAXRSAnnotations(final Method method) { for (final Object annotation : method.getDeclaredAnnotations()) { // TODO test both if (Stream.of(RELEVANT_METHOD_ANNOTATIONS).map(a -> JavaUtils.getAnnotation(method, a)) .filter(Objects::nonNull).anyMatch(a -> a.getClass().isAssignableFrom(annotation.getClass()))) return true; if (isAnnotationPresent(annotation.getClass(), HttpMethod.class)) return true; } return false; } private void visitJAXRSSuperMethod(Method method, MethodResult methodResult) { try { final ClassReader classReader = new ContextClassReader(method.getDeclaringClass().getCanonicalName()); final ClassVisitor visitor = new JAXRSAnnotatedSuperMethodClassVisitor(methodResult, method); classReader.accept(visitor, ClassReader.EXPAND_FRAMES); } catch (IOException e) { LogProvider.error("Could not analyze JAX-RS super annotated method " + method); LogProvider.debug(e); } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/JAXRSFieldVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.DefaultValueAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.ParamAnnotationVisitor; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.rest.MethodParameter; import com.sebastian_daschner.jaxrs_analyzer.model.rest.ParameterType; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Opcodes; /** * @author Sebastian Daschner */ class JAXRSFieldVisitor extends FieldVisitor { private final ClassResult classResult; private final String signature; private MethodParameter parameter; JAXRSFieldVisitor(final ClassResult classResult, final String desc, final String signature) { super(Opcodes.ASM5); this.classResult = classResult; this.signature = signature == null ? desc : signature; } @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { switch (desc) { case Types.PATH_PARAM: return paramAnnotationVisitor(ParameterType.PATH); case Types.QUERY_PARAM: return paramAnnotationVisitor(ParameterType.QUERY); case Types.HEADER_PARAM: return paramAnnotationVisitor(ParameterType.HEADER); case Types.FORM_PARAM: return paramAnnotationVisitor(ParameterType.FORM); case Types.COOKIE_PARAM: return paramAnnotationVisitor(ParameterType.COOKIE); case Types.MATRIX_PARAM: return paramAnnotationVisitor(ParameterType.MATRIX); case Types.DEFAULT_VALUE: return defaultAnnotationVisitor(); default: return null; } } private AnnotationVisitor paramAnnotationVisitor(final ParameterType parameterType) { if (parameter == null) parameter = new MethodParameter(TypeIdentifier.ofType(signature), parameterType); else parameter.setParameterType(parameterType); return new ParamAnnotationVisitor(parameter); } private AnnotationVisitor defaultAnnotationVisitor() { if (parameter == null) parameter = new MethodParameter(TypeIdentifier.ofType(signature)); return new DefaultValueAnnotationVisitor(parameter); } @Override public void visitEnd() { if (parameter != null) classResult.getClassFields().add(parameter); } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/JAXRSMethodVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.collection.InstructionBuilder; import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.annotation.*; import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.rest.HttpMethod; import com.sebastian_daschner.jaxrs_analyzer.model.rest.MethodParameter; import com.sebastian_daschner.jaxrs_analyzer.model.rest.ParameterType; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Opcodes; import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author Sebastian Daschner */ class JAXRSMethodVisitor extends ProjectMethodVisitor { private final List parameterTypes; private final Map methodParameters; private final BitSet annotatedParameters; private final boolean methodAnnotated; JAXRSMethodVisitor(final MethodIdentifier identifier, final ClassResult classResult, final MethodResult methodResult, final boolean methodAnnotated) { super(methodResult, identifier.getContainingClass()); this.methodAnnotated = methodAnnotated; parameterTypes = identifier.getParameters(); annotatedParameters = new BitSet(parameterTypes.size()); methodParameters = new HashMap<>(); methodResult.setOriginalMethodSignature(identifier); classResult.add(methodResult); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { switch (desc) { case Types.GET: methodResult.setHttpMethod(HttpMethod.GET); break; case Types.POST: methodResult.setHttpMethod(HttpMethod.POST); break; case Types.PUT: methodResult.setHttpMethod(HttpMethod.PUT); break; case Types.DELETE: methodResult.setHttpMethod(HttpMethod.DELETE); break; case Types.HEAD: methodResult.setHttpMethod(HttpMethod.HEAD); break; case Types.OPTIONS: methodResult.setHttpMethod(HttpMethod.OPTIONS); break; case Types.DEPRECATED: methodResult.setDeprecated(true); break; case Types.PATH: return new PathAnnotationVisitor(methodResult); case Types.CONSUMES: return new ConsumesAnnotationVisitor(methodResult); case Types.PRODUCES: return new ProducesAnnotationVisitor(methodResult); } return null; } @Override public AnnotationVisitor visitParameterAnnotation(final int index, final String annotationDesc, final boolean visible) { switch (annotationDesc) { case Types.PATH_PARAM: return paramAnnotationVisitor(index, ParameterType.PATH); case Types.QUERY_PARAM: return paramAnnotationVisitor(index, ParameterType.QUERY); case Types.HEADER_PARAM: return paramAnnotationVisitor(index, ParameterType.HEADER); case Types.FORM_PARAM: return paramAnnotationVisitor(index, ParameterType.FORM); case Types.COOKIE_PARAM: return paramAnnotationVisitor(index, ParameterType.COOKIE); case Types.MATRIX_PARAM: return paramAnnotationVisitor(index, ParameterType.MATRIX); case Types.DEFAULT_VALUE: return defaultAnnotationVisitor(index); case Types.SUSPENDED: LogProvider.debug("Handling of " + annotationDesc + " not yet implemented"); case Types.CONTEXT: annotatedParameters.set(index); default: return null; } } private AnnotationVisitor paramAnnotationVisitor(final int index, final ParameterType parameterType) { annotatedParameters.set(index); final String type = parameterTypes.get(index); MethodParameter methodParameter = methodParameters.get(index); if (methodParameter == null) { methodParameter = new MethodParameter(TypeIdentifier.ofType(type), parameterType); methodParameters.put(index, methodParameter); } else { methodParameter.setParameterType(parameterType); } return new ParamAnnotationVisitor(methodParameter); } private AnnotationVisitor defaultAnnotationVisitor(final int index) { final String type = parameterTypes.get(index); MethodParameter methodParameter = methodParameters.computeIfAbsent(index, i -> new MethodParameter(TypeIdentifier.ofType(type))); return new DefaultValueAnnotationVisitor(methodParameter); } @Override public void visitEnd() { super.visitEnd(); // determine request body parameter if (methodAnnotated) { if (annotatedParameters.cardinality() != parameterTypes.size()) { final String requestBodyType = parameterTypes.get(annotatedParameters.nextClearBit(0)); methodResult.setRequestBodyType(requestBodyType); } methodResult.getMethodParameters().addAll(methodParameters.values()); final Class containingClass = JavaUtils.loadClassFromName(methodResult.getOriginalMethodSignature().getContainingClass()); if (containingClass != null && containingClass.isInterface()) { methodResult.getInstructions().add(InstructionBuilder.buildInstruction(Opcodes.ICONST_0, null)); methodResult.getInstructions().add(InstructionBuilder.buildInstruction(Opcodes.ARETURN, null)); } } // TODO determine potential super methods which are annotated with JAX-RS annotations. if (methodResult.getHttpMethod() == null) { // method is a sub resource locator final ClassResult classResult = new ClassResult(); methodResult.setSubResource(classResult); } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/ProjectMethodClassVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import java.io.IOException; import static org.objectweb.asm.Opcodes.*; /** * @author Sebastian Daschner */ public class ProjectMethodClassVisitor extends ClassVisitor { private final MethodResult methodResult; private final MethodIdentifier identifier; private boolean methodFound; private String superName; public ProjectMethodClassVisitor(final MethodResult methodResult, final MethodIdentifier identifier) { super(ASM5); this.methodResult = methodResult; this.identifier = identifier; } @Override public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { this.superName = superName; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { // TODO allow abstract? final boolean legalModifiers = (access & ACC_ABSTRACT | access & ACC_NATIVE) == 0; final String methodSignature = identifier.getSignature(); if (legalModifiers && identifier.getMethodName().equals(name) && (methodSignature.equals(desc) || methodSignature.equals(signature))) { methodFound = true; return new ProjectMethodVisitor(methodResult, identifier.getContainingClass()); } return null; } @Override public void visitEnd() { // if method hasn't been found it may be on a super class (invoke_virtual) if (!methodFound && !superName.equals(Types.CLASS_OBJECT)) { try { final ClassReader classReader = new ContextClassReader(superName); final ClassVisitor visitor = new ProjectMethodClassVisitor(methodResult, identifier); classReader.accept(visitor, ClassReader.EXPAND_FRAMES); } catch (IOException e) { LogProvider.error("Could not analyze project method " + superName + "#" + identifier.getMethodName()); LogProvider.debug(e); } } } } ================================================ FILE: src/main/java/com/sebastian_daschner/jaxrs_analyzer/analysis/classes/ProjectMethodVisitor.java ================================================ package com.sebastian_daschner.jaxrs_analyzer.analysis.classes; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.instructions.*; import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import java.util.*; import java.util.stream.Stream; import static com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.collection.InstructionBuilder.*; import static com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction.InstructionType.LOAD_PLACEHOLDER; import static com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction.InstructionType.STORE_PLACEHOLDER; import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.util.Printer.OPCODES; /** * @author Sebastian Daschner */ class ProjectMethodVisitor extends MethodVisitor { private final Set