2929import java .util .Optional ;
3030
3131import org .apache .maven .api .annotations .Nullable ;
32- import org .apache .maven .api .di .Inject ;
3332import org .apache .maven .api .di .Named ;
3433import org .apache .maven .api .di .Singleton ;
3534import org .apache .maven .api .model .Model ;
35+ import org .apache .maven .api .services .Source ;
3636import org .apache .maven .api .services .model .ModelProcessor ;
3737import org .apache .maven .api .services .xml .ModelXmlFactory ;
3838import org .apache .maven .api .services .xml .XmlReaderRequest ;
3939import org .apache .maven .api .spi .ModelParser ;
4040import org .apache .maven .api .spi .ModelParserException ;
4141
42+ import static org .apache .maven .api .spi .ModelParser .STRICT ;
43+
4244/**
4345 *
4446 * Note: uses @Typed to limit the types it is available for injection to just ModelProcessor.
45- *
47+ * <p>
4648 * This is because the ModelProcessor interface extends ModelLocator and ModelReader. If we
4749 * made this component available under all its interfaces then it could end up being injected
4850 * into itself leading to a stack overflow.
49- *
51+ * <p>
5052 * A side effect of using @Typed is that it translates to explicit bindings in the container.
5153 * So instead of binding the component under a 'wildcard' key it is now bound with an explicit
5254 * key. Since this is a default component; this will be a plain binding of ModelProcessor to
5355 * this implementation type; that is, no hint/name.
54- *
56+ * <p>
5557 * This leads to a second side effect in that any @Inject request for just ModelProcessor in
5658 * the same injector is immediately matched to this explicit binding, which means extensions
5759 * cannot override this binding. This is because the lookup is always short-circuited in this
5860 * specific situation (plain @Inject request, and plain explicit binding for the same type.)
59- *
61+ * <p>
6062 * The simplest solution is to use a custom @Named here so it isn't bound under the plain key.
6163 * This is only necessary for default components using @Typed that want to support overriding.
62- *
64+ * <p>
6365 * As a non-default component this now gets a negative priority relative to other implementations
6466 * of the same interface. Since we want to allow overriding this doesn't matter in this case.
6567 * (if it did we could add @Priority of 0 to match the priority given to default components.)
6971public class DefaultModelProcessor implements ModelProcessor {
7072
7173 private final ModelXmlFactory modelXmlFactory ;
72- private final List <ModelParser > modelParsers ;
74+ private final @ Nullable List <ModelParser > modelParsers ;
7375
74- @ Inject
7576 public DefaultModelProcessor (ModelXmlFactory modelXmlFactory , @ Nullable List <ModelParser > modelParsers ) {
7677 this .modelXmlFactory = modelXmlFactory ;
7778 this .modelParsers = modelParsers ;
7879 }
7980
81+ /**
82+ * @implNote The ModelProcessor#locatePom never returns null while the ModelParser#locatePom needs to return an existing path!
83+ */
8084 @ Override
8185 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 )) {
9298 throw new IllegalArgumentException ("The POM found does not belong to the given directory: " + pom );
9399 }
94100 return pom ;
@@ -97,47 +103,49 @@ public Path locateExistingPom(Path projectDirectory) {
97103 @ Override
98104 public Model read (XmlReaderRequest request ) throws IOException {
99105 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 <>();
101111 if (pomFile != null ) {
102- Path projectDirectory = pomFile .getParent ();
103- List <ModelParserException > exceptions = new ArrayList <>();
104112 for (ModelParser parser : modelParsers ) {
105113 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 );
113120 }
114121 }
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 );
123122 }
123+ return readPomWithSuppressedErrorRethrow (request , exceptions );
124124 }
125125
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 readPomWithSuppressedErrorRethrow (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 ;
137133 }
138134 }
139135
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 ;
142150 }
143151}
0 commit comments