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, String> 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, String>();
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 } else if ("remote".equals(qName)) {
196 String alias = attributes.getValue("alias");
197 String fetch = attributes.getValue("fetch");
198 remotes.put(attributes.getValue("name"), fetch);
199 if (alias != null)
200 remotes.put(alias, fetch);
201 } else if ("default".equals(qName)) {
202 defaultRemote = attributes.getValue("remote");
203 defaultRevision = attributes.getValue("revision");
204 if (defaultRevision == null)
205 defaultRevision = defaultBranch;
206 } else if ("copyfile".equals(qName)) {
207 if (currentProject == null)
208 throw new SAXException(RepoText.get().invalidManifest);
209 currentProject.addCopyFile(new CopyFile(
210 rootRepo,
211 currentProject.getPath(),
212 attributes.getValue("src"),
213 attributes.getValue("dest")));
214 } else if ("include".equals(qName)) {
215 String name = attributes.getValue("name");
216 InputStream is = null;
217 if (includedReader != null) {
218 try {
219 is = includedReader.readIncludeFile(name);
220 } catch (Exception e) {
221 throw new SAXException(MessageFormat.format(
222 RepoText.get().errorIncludeFile, name), e);
223 }
224 } else if (filename != null) {
225 int index = filename.lastIndexOf('/');
226 String path = filename.substring(0, index + 1) + name;
227 try {
228 is = new FileInputStream(path);
229 } catch (IOException e) {
230 throw new SAXException(MessageFormat.format(
231 RepoText.get().errorIncludeFile, path), e);
232 }
233 }
234 if (is == null) {
235 throw new SAXException(
236 RepoText.get().errorIncludeNotImplemented);
237 }
238 try {
239 read(is);
240 } catch (IOException e) {
241 throw new SAXException(e);
242 }
243 }
244 }
245
246 @Override
247 public void endElement(
248 String uri,
249 String localName,
250 String qName) throws SAXException {
251 if ("project".equals(qName)) {
252 projects.add(currentProject);
253 currentProject = null;
254 }
255 }
256
257 @Override
258 public void endDocument() throws SAXException {
259 xmlInRead--;
260 if (xmlInRead != 0)
261 return;
262
263
264 Map<String, String> remoteUrls = new HashMap<String, String>();
265 URI baseUri;
266 try {
267 baseUri = new URI(baseUrl);
268 } catch (URISyntaxException e) {
269 throw new SAXException(e);
270 }
271 for (RepoProject proj : projects) {
272 String remote = proj.getRemote();
273 if (remote == null) {
274 if (defaultRemote == null) {
275 if (filename != null)
276 throw new SAXException(MessageFormat.format(
277 RepoText.get().errorNoDefaultFilename,
278 filename));
279 else
280 throw new SAXException(
281 RepoText.get().errorNoDefault);
282 }
283 remote = defaultRemote;
284 }
285 String remoteUrl = remoteUrls.get(remote);
286 if (remoteUrl == null) {
287 remoteUrl = baseUri.resolve(remotes.get(remote)).toString();
288 if (!remoteUrl.endsWith("/"))
289 remoteUrl = remoteUrl + "/";
290 remoteUrls.put(remote, remoteUrl);
291 }
292 proj.setUrl(remoteUrl + proj.getName())
293 .setDefaultRevision(defaultRevision);
294 }
295
296 filteredProjects.addAll(projects);
297 removeNotInGroup();
298 removeOverlaps();
299 }
300
301
302
303
304
305
306 public List<RepoProject> getProjects() {
307 return projects;
308 }
309
310
311
312
313
314
315 public List<RepoProject> getFilteredProjects() {
316 return filteredProjects;
317 }
318
319
320 void removeNotInGroup() {
321 Iterator<RepoProject> iter = filteredProjects.iterator();
322 while (iter.hasNext())
323 if (!inGroups(iter.next()))
324 iter.remove();
325 }
326
327
328 void removeOverlaps() {
329 Collections.sort(filteredProjects);
330 Iterator<RepoProject> iter = filteredProjects.iterator();
331 if (!iter.hasNext())
332 return;
333 RepoProject last = iter.next();
334 while (iter.hasNext()) {
335 RepoProject p = iter.next();
336 if (last.isAncestorOf(p))
337 iter.remove();
338 else
339 last = p;
340 }
341 removeNestedCopyfiles();
342 }
343
344
345 void removeNestedCopyfiles() {
346 for (RepoProject proj : filteredProjects) {
347 List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
348 proj.clearCopyFiles();
349 for (CopyFile copyfile : copyfiles) {
350 if (!isNestedCopyfile(copyfile)) {
351 proj.addCopyFile(copyfile);
352 }
353 }
354 }
355 }
356
357 boolean inGroups(RepoProject proj) {
358 for (String group : minusGroups) {
359 if (proj.inGroup(group)) {
360
361 return false;
362 }
363 }
364 if (plusGroups.isEmpty() || plusGroups.contains("all")) {
365
366 return true;
367 }
368 for (String group : plusGroups) {
369 if (proj.inGroup(group))
370 return true;
371 }
372 return false;
373 }
374
375 private boolean isNestedCopyfile(CopyFile copyfile) {
376 if (copyfile.dest.indexOf('/') == -1) {
377
378 return false;
379 }
380 for (RepoProject proj : filteredProjects) {
381 if (proj.getPath().compareTo(copyfile.dest) > 0) {
382
383
384 return false;
385 }
386 if (proj.isAncestorOf(copyfile.dest)) {
387 return true;
388 }
389 }
390 return false;
391 }
392 }