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