29
29
import java .util .Optional ;
30
30
31
31
import org .apache .maven .api .annotations .Nullable ;
32
- import org .apache .maven .api .di .Inject ;
33
32
import org .apache .maven .api .di .Named ;
34
33
import org .apache .maven .api .di .Singleton ;
35
34
import org .apache .maven .api .model .Model ;
35
+ import org .apache .maven .api .services .Source ;
36
36
import org .apache .maven .api .services .model .ModelProcessor ;
37
37
import org .apache .maven .api .services .xml .ModelXmlFactory ;
38
38
import org .apache .maven .api .services .xml .XmlReaderRequest ;
39
39
import org .apache .maven .api .spi .ModelParser ;
40
40
import org .apache .maven .api .spi .ModelParserException ;
41
41
42
+ import static org .apache .maven .api .spi .ModelParser .STRICT ;
43
+
42
44
/**
43
45
*
44
46
* Note: uses @Typed to limit the types it is available for injection to just ModelProcessor.
45
- *
47
+ * <p>
46
48
* This is because the ModelProcessor interface extends ModelLocator and ModelReader. If we
47
49
* made this component available under all its interfaces then it could end up being injected
48
50
* into itself leading to a stack overflow.
49
- *
51
+ * <p>
50
52
* A side effect of using @Typed is that it translates to explicit bindings in the container.
51
53
* So instead of binding the component under a 'wildcard' key it is now bound with an explicit
52
54
* key. Since this is a default component; this will be a plain binding of ModelProcessor to
53
55
* this implementation type; that is, no hint/name.
54
- *
56
+ * <p>
55
57
* This leads to a second side effect in that any @Inject request for just ModelProcessor in
56
58
* the same injector is immediately matched to this explicit binding, which means extensions
57
59
* cannot override this binding. This is because the lookup is always short-circuited in this
58
60
* specific situation (plain @Inject request, and plain explicit binding for the same type.)
59
- *
61
+ * <p>
60
62
* The simplest solution is to use a custom @Named here so it isn't bound under the plain key.
61
63
* This is only necessary for default components using @Typed that want to support overriding.
62
- *
64
+ * <p>
63
65
* As a non-default component this now gets a negative priority relative to other implementations
64
66
* of the same interface. Since we want to allow overriding this doesn't matter in this case.
65
67
* (if it did we could add @Priority of 0 to match the priority given to default components.)
69
71
public class DefaultModelProcessor implements ModelProcessor {
70
72
71
73
private final ModelXmlFactory modelXmlFactory ;
72
- private final List <ModelParser > modelParsers ;
74
+ private final @ Nullable List <ModelParser > modelParsers ;
73
75
74
- @ Inject
75
76
public DefaultModelProcessor (ModelXmlFactory modelXmlFactory , @ Nullable List <ModelParser > modelParsers ) {
76
77
this .modelXmlFactory = modelXmlFactory ;
77
78
this .modelParsers = modelParsers ;
78
79
}
79
80
81
+ /**
82
+ * @implNote The ModelProcessor#locatePom never returns null while the ModelParser#locatePom needs to return an existing path!
83
+ */
80
84
@ Override
81
85
public Path locateExistingPom (Path projectDirectory ) {
82
- // Note that the ModelProcessor#locatePom never returns null
83
- // while the ModelParser#locatePom needs to return an existing path!
84
- Path pom = modelParsers .stream ()
85
- .map (m -> m .locate (projectDirectory )
86
- .map (org .apache .maven .api .services .Source ::getPath )
87
- .orElse (null ))
88
- .filter (Objects ::nonNull )
89
- .findFirst ()
90
- .orElseGet (() -> doLocateExistingPom (projectDirectory ));
91
- if (pom != null && !pom .equals (projectDirectory ) && !pom .getParent ().equals (projectDirectory )) {
86
+ return throwIfWrongProjectDirLocation (
87
+ modelParsers .stream ()
88
+ .map (m ->
89
+ m .locate (projectDirectory ).map (Source ::getPath ).orElse (null ))
90
+ .filter (Objects ::nonNull )
91
+ .findFirst ()
92
+ .orElseGet (() -> locateExistingPomWithUserDirDefault (projectDirectory )),
93
+ projectDirectory );
94
+ }
95
+
96
+ private static Path throwIfWrongProjectDirLocation (Path pom , Path projectDirectory ) {
97
+ if (!pom .equals (projectDirectory ) && !pom .getParent ().equals (projectDirectory )) {
92
98
throw new IllegalArgumentException ("The POM found does not belong to the given directory: " + pom );
93
99
}
94
100
return pom ;
@@ -97,47 +103,49 @@ public Path locateExistingPom(Path projectDirectory) {
97
103
@ Override
98
104
public Model read (XmlReaderRequest request ) throws IOException {
99
105
Objects .requireNonNull (request , "source cannot be null" );
100
- Path pomFile = request .getPath ();
106
+ return readPomWithParentInheritance (request , request .getPath ());
107
+ }
108
+
109
+ private Model readPomWithParentInheritance (XmlReaderRequest request , Path pomFile ) throws IOException {
110
+ List <ModelParserException > exceptions = new ArrayList <>();
101
111
if (pomFile != null ) {
102
- Path projectDirectory = pomFile .getParent ();
103
- List <ModelParserException > exceptions = new ArrayList <>();
104
112
for (ModelParser parser : modelParsers ) {
105
113
try {
106
- Optional <Model > model =
107
- parser .locateAndParse (projectDirectory , Map .of (ModelParser .STRICT , request .isStrict ()));
108
- if (model .isPresent ()) {
109
- return model .get ().withPomFile (pomFile );
110
- }
111
- } catch (ModelParserException e ) {
112
- exceptions .add (e );
114
+ // RV: is exit on runtime error problematic? (GIGO)
115
+ return readParent (request , pomFile , parser ).orElseThrow ().withPomFile (pomFile );
116
+ }
117
+ // RV: catch (ModelParserException | NoSuchElementException ex) {
118
+ catch (ModelParserException ex ) { // RV: what if | NoSuchElementException ?
119
+ exceptions .add (ex );
113
120
}
114
121
}
115
- try {
116
- return doRead (request );
117
- } catch (IOException e ) {
118
- exceptions .forEach (e ::addSuppressed );
119
- throw e ;
120
- }
121
- } else {
122
- return doRead (request );
123
122
}
123
+ return readPomWithErrorSuppression (request , exceptions );
124
124
}
125
125
126
- private Path doLocateExistingPom (Path project ) {
127
- if (project == null ) {
128
- project = Paths .get (System .getProperty ("user.dir" ));
129
- }
130
- if (Files .isDirectory (project )) {
131
- Path pom = project .resolve ("pom.xml" );
132
- return Files .isRegularFile (pom ) ? pom : null ;
133
- } else if (Files .isRegularFile (project )) {
134
- return project ;
135
- } else {
136
- return null ;
126
+ private Model readPomWithErrorSuppression (XmlReaderRequest request , List <ModelParserException > exceptions )
127
+ throws IOException {
128
+ try {
129
+ return modelXmlFactory .read (request );
130
+ } catch (IOException ex ) {
131
+ exceptions .forEach (ex ::addSuppressed );
132
+ throw ex ;
137
133
}
138
134
}
139
135
140
- private Model doRead (XmlReaderRequest request ) throws IOException {
141
- return modelXmlFactory .read (request );
136
+ private static Optional <Model > readParent (XmlReaderRequest request , Path pomFile , ModelParser parser ) {
137
+ return parser .locateAndParse (pomFile .getParent (), Map .of (STRICT , request .isStrict ()));
138
+ }
139
+
140
+ private Path locateExistingPomWithUserDirDefault (Path project ) {
141
+ return locateExistingPomInDirOrFile (project != null ? project : Paths .get (System .getProperty ("user.dir" )));
142
+ }
143
+
144
+ private static Path locateExistingPomInDirOrFile (Path project ) {
145
+ return Files .isDirectory (project ) ? isRegularFileOrNull (project .resolve ("pom.xml" )) : project ;
146
+ }
147
+
148
+ private static Path isRegularFileOrNull (Path pom ) {
149
+ return Files .isRegularFile (pom ) ? pom : null ;
142
150
}
143
151
}
0 commit comments