-
Notifications
You must be signed in to change notification settings - Fork 342
add package matcher section to user guide #1672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,15 @@ | ||
| FROM jekyll/jekyll:stable | ||
| FROM ruby:3.3-slim | ||
|
|
||
| MAINTAINER Peter Gafert <peter.gafert@archunit.org> | ||
| LABEL maintainer="Peter Gafert <peter.gafert@archunit.org>" | ||
|
|
||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| build-essential \ | ||
| git \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| WORKDIR /srv/jekyll | ||
|
|
||
| COPY Gemfile . | ||
| COPY Gemfile.lock . | ||
| COPY Gemfile Gemfile.lock ./ | ||
|
|
||
| RUN bundle install | ||
| RUN gem install bundler -v 2.3.25 \ | ||
| && bundle install |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,12 @@ | ||
| version: "3.0" | ||
|
|
||
| services: | ||
| site: | ||
| build: . | ||
| image: archunit/jekyll:latest | ||
| command: bundle exec jekyll serve --verbose --trace --host 0.0.0.0 --watch --incremental | ||
| command: bundle exec jekyll serve --verbose --trace --host 0.0.0.0 --watch --force_polling | ||
| environment: | ||
| - JEKYLL_ENV=development | ||
| volumes: | ||
| - ./:/srv/jekyll | ||
| - ./_config-dev.yml:/srv/jekyll/_config.yml | ||
| ports: | ||
| - 4000:4000 | ||
| - "4000:4000" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -396,3 +396,120 @@ ArchUnit's own rule APIs (compare <<The Lang API>>) never rely on the | |||||
| classpath though. Thus the evaluation of default rules and syntax combinations, described in the | ||||||
| next section, does not depend on whether the classes were imported from the classpath or | ||||||
| some JAR / folder. | ||||||
|
|
||||||
| === Package Identifiers | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel that
Suggested change
|
||||||
|
|
||||||
| Several ArchUnit methods accept a `String` package identifier to describe a set of packages, | ||||||
| for example `resideInAPackage(..)` and `resideInAnyPackage(..)` (and their negated variants) | ||||||
| in the Lang API, `slices().matching(..)` and `modules().definedByPackages(..)` in the Library | ||||||
| API, or the component stereotypes of a PlantUML component diagram. All of them delegate to | ||||||
| the same underlying https://javadoc.io/doc/com.tngtech.archunit/archunit/latest/com/tngtech/archunit/core/domain/PackageMatcher.html[`PackageMatcher`], whose syntax is inspired by AspectJ type patterns and | ||||||
| extended with capturing groups. | ||||||
|
|
||||||
| ==== Wildcards | ||||||
|
|
||||||
| The syntax is built from the following elements: | ||||||
|
|
||||||
| [cols="1,4"] | ||||||
| |=== | ||||||
| | Pattern | Meaning | ||||||
|
|
||||||
| | `*` | ||||||
| | Matches any sequence of characters **not containing** the dot `.`, | ||||||
| i.e. exactly one package segment. | ||||||
|
|
||||||
| | `..` | ||||||
| | Matches any sequence of characters that **may contain** the dot `.`, | ||||||
| i.e. any number of package segments (including zero). | ||||||
|
|
||||||
| | `(*)` | ||||||
| | Like `*`, but additionally _captures_ the matched segment, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find no explanation of what it means that it is captured. I find no information about that part. which seems to be the only way to exclude the parent package
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hankem the file 8 talks about Slices and tests of them, so I think we mean the same thing.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The immediate effect is that captured substrings can be obtained from Slices according to package identifiers are just a prominent use case.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First of all: thanks Stefan for raising this question. To me the use-cases for capturing and groups here are also not that clear based on the JavaDoc in PackageMatcher.. Based on this discussion I think we should mention that they can be obtained programmatically, as outlined by Manfred. Furthermore, I like the idea of linking to file 8 where one exemplary use-case of this feature is documented.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's absolutely fair to document that capturing groups are relevant for the result of Slices are a good example; the architecture rules wouldn't allow a reference from |
||||||
| so it can be referenced later (e.g. as a slice identifier, see <<Slices>>). | ||||||
|
|
||||||
| | `(**)` | ||||||
| | Like `..`, but additionally _captures_ the matched segments. | ||||||
|
|
||||||
| | `[a\|b]` | ||||||
| | Alternation: matches either `a` or `b`. Alternations are only allowed inside brackets | ||||||
| `[...]` or inside capturing groups `(...)`. | ||||||
| |=== | ||||||
|
|
||||||
| Note that `(..)` is not a valid capturing group — use `(**)` instead. `()` and `[]` cannot | ||||||
| be nested inside each other. | ||||||
|
|
||||||
| The segments matched by capturing groups can be retrieved from the result of | ||||||
| https://javadoc.io/doc/com.tngtech.archunit/archunit/latest/com/tngtech/archunit/core/domain/PackageMatcher.html#match(java.lang.String)[`PackageMatcher#match(String)`] | ||||||
| via | ||||||
| https://javadoc.io/doc/com.tngtech.archunit/archunit/latest/com/tngtech/archunit/core/domain/PackageMatcher.Result.html#getGroup(int)[`PackageMatcher.Result#getGroup(int)`] | ||||||
| (groups are 1-based, in the order the capturing groups appear in the pattern). | ||||||
|
|
||||||
| ==== Worked Example | ||||||
|
|
||||||
| Consider the following five classes and the packages they reside in: | ||||||
|
|
||||||
| [cols="2,2"] | ||||||
| |=== | ||||||
| | Class | Package | ||||||
|
|
||||||
| | `com.myapp.controller.SomeController` | `com.myapp.controller` | ||||||
| | `com.myapp.service.SomeService` | `com.myapp.service` | ||||||
| | `com.myapp.service.impl.SomeServiceImpl` | `com.myapp.service.impl` | ||||||
| | `com.myapp.persistence.dao.SomeDao` | `com.myapp.persistence.dao` | ||||||
| | `com.myapp.persistence.dao.jpa.SomeJpa` | `com.myapp.persistence.dao.jpa` | ||||||
| |=== | ||||||
|
|
||||||
| The following table shows a range of package identifiers and which of the five packages | ||||||
| above each of them matches: | ||||||
|
|
||||||
| [cols="2,3"] | ||||||
| |=== | ||||||
| | Package Identifier | Matches | ||||||
|
|
||||||
| | `com.myapp.service` | ||||||
| | `com.myapp.service` only (exact match). | ||||||
|
|
||||||
| | `com.myapp.*` | ||||||
| | `com.myapp.controller` and `com.myapp.service` | ||||||
| (exactly one more segment after `com.myapp`). | ||||||
|
|
||||||
| | `com.myapp..` | ||||||
| | All five packages — `com.myapp..` matches `com.myapp` itself and every subpackage of it. | ||||||
|
|
||||||
| | `..service` | ||||||
| | `com.myapp.service` only (segment `service` at the end). | ||||||
|
|
||||||
| | `..service..` | ||||||
| | `com.myapp.service` and `com.myapp.service.impl` | ||||||
| (any package containing a segment `service`). | ||||||
|
|
||||||
| | `..dao..` | ||||||
| | `com.myapp.persistence.dao` and `com.myapp.persistence.dao.jpa`. | ||||||
|
|
||||||
| | `..impl` | ||||||
| | `com.myapp.service.impl` only. | ||||||
|
|
||||||
| | `com.myapp.(*)` | ||||||
| | `com.myapp.controller` (group 1 = `controller`) and | ||||||
| `com.myapp.service` (group 1 = `service`). | ||||||
| The deeper packages do not match because `(*)` allows only a single segment. | ||||||
|
|
||||||
| | `com.myapp.(*)..` | ||||||
| | All five packages; group 1 captures the first sub-package below `com.myapp` | ||||||
| (`controller`, `service`, `service`, `persistence`, `persistence` respectively). | ||||||
| This is the typical pattern used by `slices().matching(..)`. | ||||||
|
|
||||||
| | `..[service\|controller]..` | ||||||
| | `com.myapp.controller`, `com.myapp.service` and `com.myapp.service.impl`. | ||||||
| |=== | ||||||
|
|
||||||
| ==== What is Matched Against the Pattern | ||||||
|
|
||||||
| An important detail is that the package identifier is matched against the | ||||||
| **package name of a class**, not against the fully qualified class name. | ||||||
| So for `com.myapp.service.SomeService` the string that is checked against | ||||||
| the pattern is `com.myapp.service`, not `com.myapp.service.SomeService`. | ||||||
|
|
||||||
| This means that a pattern like `..SomeService` will _not_ match a class named `SomeService`, | ||||||
| because `SomeService` is the simple class name, not a package segment. To match by class | ||||||
| name use a name-based predicate such as | ||||||
| `haveSimpleName("SomeService")` or `haveNameMatching(".*SomeService")` instead. | ||||||
Uh oh!
There was an error while loading. Please reload this page.