1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.gitrepo;
44
45 import java.io.FileInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.net.URI;
49 import java.net.URISyntaxException;
50 import java.text.MessageFormat;
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59
60 import org.eclipse.jgit.annotations.NonNull;
61 import org.eclipse.jgit.api.errors.GitAPIException;
62 import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
63 import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
64 import org.eclipse.jgit.gitrepo.RepoProject.ReferenceFile;
65 import org.eclipse.jgit.gitrepo.internal.RepoText;
66 import org.eclipse.jgit.internal.JGitText;
67 import org.eclipse.jgit.lib.Repository;
68 import org.xml.sax.Attributes;
69 import org.xml.sax.InputSource;
70 import org.xml.sax.SAXException;
71 import org.xml.sax.XMLReader;
72 import org.xml.sax.helpers.DefaultHandler;
73 import org.xml.sax.helpers.XMLReaderFactory;
74
75
76
77
78
79
80
81 public class ManifestParser extends DefaultHandler {
82 private final String filename;
83 private final URI baseUrl;
84 private final String defaultBranch;
85 private final Repository rootRepo;
86 private final Map<String, Remote> remotes;
87 private final Set<String> plusGroups;
88 private final Set<String> minusGroups;
89 private final List<RepoProject> projects;
90 private final List<RepoProject> filteredProjects;
91 private final IncludedFileReader includedReader;
92
93 private String defaultRemote;
94 private String defaultRevision;
95 private int xmlInRead;
96 private RepoProject currentProject;
97
98
99
100
101 public interface IncludedFileReader {
102
103
104
105
106
107
108
109
110
111 public InputStream readIncludeFile(String path)
112 throws GitAPIException, IOException;
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 public ManifestParser(IncludedFileReader includedReader, String filename,
134 String defaultBranch, String baseUrl, String groups,
135 Repository rootRepo) {
136 this.includedReader = includedReader;
137 this.filename = filename;
138 this.defaultBranch = defaultBranch;
139 this.rootRepo = rootRepo;
140 this.baseUrl = normalizeEmptyPath(URI.create(baseUrl));
141
142 plusGroups = new HashSet<>();
143 minusGroups = new HashSet<>();
144 if (groups == null || groups.length() == 0
145 || groups.equals("default")) {
146
147 minusGroups.add("notdefault");
148 } else {
149 for (String group : groups.split(",")) {
150 if (group.startsWith("-"))
151 minusGroups.add(group.substring(1));
152 else
153 plusGroups.add(group);
154 }
155 }
156
157 remotes = new HashMap<>();
158 projects = new ArrayList<>();
159 filteredProjects = new ArrayList<>();
160 }
161
162
163
164
165
166
167
168
169 public void read(InputStream inputStream) throws IOException {
170 xmlInRead++;
171 final XMLReader xr;
172 try {
173 xr = XMLReaderFactory.createXMLReader();
174 } catch (SAXException e) {
175 throw new IOException(JGitText.get().noXMLParserAvailable);
176 }
177 xr.setContentHandler(this);
178 try {
179 xr.parse(new InputSource(inputStream));
180 } catch (SAXException e) {
181 throw new IOException(RepoText.get().errorParsingManifestFile, e);
182 }
183 }
184
185
186 @Override
187 public void startElement(
188 String uri,
189 String localName,
190 String qName,
191 Attributes attributes) throws SAXException {
192 if ("project".equals(qName)) {
193 if (attributes.getValue("name") == null) {
194 throw new SAXException(RepoText.get().invalidManifest);
195 }
196 currentProject = new RepoProject(
197 attributes.getValue("name"),
198 attributes.getValue("path"),
199 attributes.getValue("revision"),
200 attributes.getValue("remote"),
201 attributes.getValue("groups"));
202 currentProject.setRecommendShallow(
203 attributes.getValue("clone-depth"));
204 } else if ("remote".equals(qName)) {
205 String alias = attributes.getValue("alias");
206 String fetch = attributes.getValue("fetch");
207 String revision = attributes.getValue("revision");
208 Remote remote = new Remote(fetch, revision);
209 remotes.put(attributes.getValue("name"), remote);
210 if (alias != null)
211 remotes.put(alias, remote);
212 } else if ("default".equals(qName)) {
213 defaultRemote = attributes.getValue("remote");
214 defaultRevision = attributes.getValue("revision");
215 } else if ("copyfile".equals(qName)) {
216 if (currentProject == null)
217 throw new SAXException(RepoText.get().invalidManifest);
218 currentProject.addCopyFile(new CopyFile(
219 rootRepo,
220 currentProject.getPath(),
221 attributes.getValue("src"),
222 attributes.getValue("dest")));
223 } else if ("linkfile".equals(qName)) {
224 if (currentProject == null) {
225 throw new SAXException(RepoText.get().invalidManifest);
226 }
227 currentProject.addLinkFile(new LinkFile(
228 rootRepo,
229 currentProject.getPath(),
230 attributes.getValue("src"),
231 attributes.getValue("dest")));
232 } else if ("include".equals(qName)) {
233 String name = attributes.getValue("name");
234 if (includedReader != null) {
235 try (InputStream is = includedReader.readIncludeFile(name)) {
236 if (is == null) {
237 throw new SAXException(
238 RepoText.get().errorIncludeNotImplemented);
239 }
240 read(is);
241 } catch (Exception e) {
242 throw new SAXException(MessageFormat.format(
243 RepoText.get().errorIncludeFile, name), e);
244 }
245 } else if (filename != null) {
246 int index = filename.lastIndexOf('/');
247 String path = filename.substring(0, index + 1) + name;
248 try (InputStream is = new FileInputStream(path)) {
249 read(is);
250 } catch (IOException e) {
251 throw new SAXException(MessageFormat.format(
252 RepoText.get().errorIncludeFile, path), e);
253 }
254 }
255 }
256 }
257
258
259 @Override
260 public void endElement(
261 String uri,
262 String localName,
263 String qName) throws SAXException {
264 if ("project".equals(qName)) {
265 projects.add(currentProject);
266 currentProject = null;
267 }
268 }
269
270
271 @Override
272 public void endDocument() throws SAXException {
273 xmlInRead--;
274 if (xmlInRead != 0)
275 return;
276
277
278 Map<String, URI> remoteUrls = new HashMap<>();
279 if (defaultRevision == null && defaultRemote != null) {
280 Remote remote = remotes.get(defaultRemote);
281 if (remote != null) {
282 defaultRevision = remote.revision;
283 }
284 if (defaultRevision == null) {
285 defaultRevision = defaultBranch;
286 }
287 }
288 for (RepoProject proj : projects) {
289 String remote = proj.getRemote();
290 String revision = defaultRevision;
291 if (remote == null) {
292 if (defaultRemote == null) {
293 if (filename != null)
294 throw new SAXException(MessageFormat.format(
295 RepoText.get().errorNoDefaultFilename,
296 filename));
297 else
298 throw new SAXException(
299 RepoText.get().errorNoDefault);
300 }
301 remote = defaultRemote;
302 } else {
303 Remote r = remotes.get(remote);
304 if (r != null && r.revision != null) {
305 revision = r.revision;
306 }
307 }
308 URI remoteUrl = remoteUrls.get(remote);
309 if (remoteUrl == null) {
310 String fetch = remotes.get(remote).fetch;
311 if (fetch == null) {
312 throw new SAXException(MessageFormat
313 .format(RepoText.get().errorNoFetch, remote));
314 }
315 remoteUrl = normalizeEmptyPath(baseUrl.resolve(fetch));
316 remoteUrls.put(remote, remoteUrl);
317 }
318 proj.setUrl(remoteUrl.resolve(proj.getName()).toString())
319 .setDefaultRevision(revision);
320 }
321
322 filteredProjects.addAll(projects);
323 removeNotInGroup();
324 removeOverlaps();
325 }
326
327 static URI normalizeEmptyPath(URI u) {
328
329
330
331 if (u.getHost() != null && !u.getHost().isEmpty() &&
332 (u.getPath() == null || u.getPath().isEmpty())) {
333 try {
334 return new URI(u.getScheme(),
335 u.getUserInfo(), u.getHost(), u.getPort(),
336 "/", u.getQuery(), u.getFragment());
337 } catch (URISyntaxException x) {
338 throw new IllegalArgumentException(x.getMessage(), x);
339 }
340 }
341 return u;
342 }
343
344
345
346
347
348
349 public List<RepoProject> getProjects() {
350 return projects;
351 }
352
353
354
355
356
357
358 public @NonNull List<RepoProject> getFilteredProjects() {
359 return filteredProjects;
360 }
361
362
363 void removeNotInGroup() {
364 Iterator<RepoProject> iter = filteredProjects.iterator();
365 while (iter.hasNext())
366 if (!inGroups(iter.next()))
367 iter.remove();
368 }
369
370
371 void removeOverlaps() {
372 Collections.sort(filteredProjects);
373 Iterator<RepoProject> iter = filteredProjects.iterator();
374 if (!iter.hasNext())
375 return;
376 RepoProject last = iter.next();
377 while (iter.hasNext()) {
378 RepoProject p = iter.next();
379 if (last.isAncestorOf(p))
380 iter.remove();
381 else
382 last = p;
383 }
384 removeNestedCopyAndLinkfiles();
385 }
386
387 private void removeNestedCopyAndLinkfiles() {
388 for (RepoProject proj : filteredProjects) {
389 List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
390 proj.clearCopyFiles();
391 for (CopyFile copyfile : copyfiles) {
392 if (!isNestedReferencefile(copyfile)) {
393 proj.addCopyFile(copyfile);
394 }
395 }
396 List<LinkFile> linkfiles = new ArrayList<>(proj.getLinkFiles());
397 proj.clearLinkFiles();
398 for (LinkFile linkfile : linkfiles) {
399 if (!isNestedReferencefile(linkfile)) {
400 proj.addLinkFile(linkfile);
401 }
402 }
403 }
404 }
405
406 boolean inGroups(RepoProject proj) {
407 for (String group : minusGroups) {
408 if (proj.inGroup(group)) {
409
410 return false;
411 }
412 }
413 if (plusGroups.isEmpty() || plusGroups.contains("all")) {
414
415 return true;
416 }
417 for (String group : plusGroups) {
418 if (proj.inGroup(group))
419 return true;
420 }
421 return false;
422 }
423
424 private boolean isNestedReferencefile(ReferenceFile referencefile) {
425 if (referencefile.dest.indexOf('/') == -1) {
426
427 return false;
428 }
429 for (RepoProject proj : filteredProjects) {
430 if (proj.getPath().compareTo(referencefile.dest) > 0) {
431
432
433 return false;
434 }
435 if (proj.isAncestorOf(referencefile.dest)) {
436 return true;
437 }
438 }
439 return false;
440 }
441
442 private static class Remote {
443 final String fetch;
444 final String revision;
445
446 Remote(String fetch, String revision) {
447 this.fetch = fetch;
448 this.revision = revision;
449 }
450 }
451 }