1/*******************************************************************************
2 * Copyright (c) 2005, 2006 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *     IBM Corporation - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.releng.generators.rss;
12
13//TODO: bug - can't run CreateFeed and AddEntry together when debug=2 - file locking problem?
14
15import java.io.File;
16import java.io.FileNotFoundException;
17import java.io.FileOutputStream;
18import java.io.IOException;
19import java.io.OutputStreamWriter;
20import java.util.Date;
21
22import javax.xml.parsers.DocumentBuilder;
23import javax.xml.parsers.DocumentBuilderFactory;
24import javax.xml.parsers.ParserConfigurationException;
25import javax.xml.transform.OutputKeys;
26import javax.xml.transform.Transformer;
27import javax.xml.transform.TransformerException;
28import javax.xml.transform.TransformerFactory;
29import javax.xml.transform.dom.DOMSource;
30import javax.xml.transform.stream.StreamResult;
31
32import org.apache.tools.ant.BuildException;
33import org.apache.tools.ant.Task;
34import org.apache.tools.ant.util.DateUtils;
35
36import org.eclipse.releng.util.rss.Messages;
37import org.eclipse.releng.util.rss.RSSFeedUtil;
38
39import org.w3c.dom.Document;
40import org.w3c.dom.Element;
41import org.w3c.dom.Node;
42import org.xml.sax.SAXException;
43
44/**
45 * Parameters:
46 *   debug      - more output to console - eg., 0|1|2
47 *
48 *   file       - path to the XML file that will be created - eg., /path/to/file.to.create.xml
49 *   project    - project's name, used to label the feed - eg., Eclipse, EMF, UML2
50 *   branch     - build's branch, eg., 2.2.0
51 *   buildID    - build's ID, eg., S200605051234
52 *   feedURL    - URL of the feed where it will be published - eg., http://servername/path/to/feed.xml
53 *      note that feedURL is not required if the feed already exists, only if a new feed file must be created
54 *   buildURL   - URL of the build being added to the feed - eg., http://servername/path/to/project/branch/buildID/
55 *
56 *   buildAlias - build's alias, eg., 2.2.0RC2
57 *
58 *   dependencyURLs   - upstream dependencies, eg., UML2 depends on emf and eclipse, so specify TWO URLs in properties file or ant task
59 *
60 *   releaseNotesURL  - URL of the build's release notes page - eg., http://www.eclipse.org/project/news/release-notes.php
61 *   updateManagerURL - URL of the build's Update Manager site - eg., http://servername/path/to/project/updates/
62 *   downloadsURL     - URL of the build's downloads - eg., http://servername/path/to/project/downloads/
63 *
64 *   jarSigningStatus - code to define jar signing status - eg., one of:
65 *      NONE (or '')  - no status available or not participating
66 *      UNSIGNED      - no jar signage available or done yet
67 *      SIGNREADY     - jars promoted to eclipse.org, ready for signing
68 *      BUILDREADY    - signed on eclipse.org, ready to be collected and bundled as zips and copied to UM site
69 *      SIGNED        - signed & bundled on download page and on UM site
70 *
71 *   callistoStatus   - code to define Callisto status, eg., one of:
72 *      NONE (or '')         - not part of Callisto or unknown status
73 *      BUILDCOMPLETE        - Have you finished your RC1 bits?
74 *      2006-05-02T20:50:00Z - When do you expect to finish them?
75 *      TPTP                 - If you're waiting for another project, which one(s)? (TPTP is just an example)
76 *      UMSITEREADY          - Have you placed those bits in your update site?
77 *      CALLISTOSITEREADY    - Have you updated the features.xml file in the Callisto CVS directory?
78 *      COMPLETE             - Are you ready for RC1 to be declared?
79 *
80 *   buildType - code to define type of build, eg., one of:
81 *      N      - Nightly
82 *      I      - Integration
83 *      M      - Maintenance
84 *      S      - Stable (Milestone or Release Candidate)
85 *      R      - Release
86 *      MC     - Maintenance-Callisto
87 *      SC     - Stable-Callisto
88 *      RC     - Release-Callisto
89 *
90 *   Releases           - comma or space-separated list of releases in quints of os,ws,arch,type/name,filename,...
91 *                      - eg., win32,win,x86,SDK,eclipse-SDK-3.2RC5-win32.zip,linux,gtk,x86_64,SDK,eclipse-SDK-3.2RC5-linux-gtk.tar.gz
92 *                      - (for examples and definitions of ws, os + arch, see below)
93 *
94 *   JUnitTestURL       - URL of the build's JUnit test results - eg., http://servername/path/to/project/branch/buildID/testResults.php
95 *   performanceTestURL - URL of the build's performance tests - eg., http://servername/path/to/project/branch/buildID/performance/performance.php
96 *   APITestURL         - URL of the build's API test results - eg., http://servername/path/to/project/branch/buildID/testResults.php
97 *
98 *   JUnitTestResults       - comma or space-separated list of test results in quads of os,ws,arch,status,os,ws,status,arch,... - eg., win32,win,x86,PASS,linux,gtk,x86,PASS
99 *   performanceTestResults - comma or space-separated list of test results in quads of os,ws,arch,status,os,ws,status,arch,... - eg., win32,win,x86_64,PASS,linux,gtk,x86_64,PASS
100 *   APITestResults         - comma or space-separated list of test results in quads of os,ws,arch,status,os,ws,status,arch,... - eg., win32,win,ppc,PASS,linux,gtk,ppc,PASS
101 *      ws     - window system - eg., ALL, win32, win64, linux, macos...
102 *      os     - operating system - eg., ALL, win, gtk, motif, carbon, ...
103 *      arch   - architecture, eg., ALL, x86, x86_64, ppc, ...
104 *      status - status code for test results - eg., one of: PASS, PENDING, FAIL, UNKNOWN, SKIPPED
105 *
106 * @author nickb
107 *
108 */
109public class RSSFeedAddEntryTask extends Task {
110
111  private int debug = 0;
112
113  private static final String now = getTimestamp();
114
115  //$ANALYSIS-IGNORE codereview.java.rules.portability.RulePortabilityLineSeparators
116  private static final String NL="\n"; //$NON-NLS-1$
117  private static final String NS = ""; //$NON-NLS-1$
118  private static final String SEP = "----"; //$NON-NLS-1$
119  private static final String SP = " "; //$NON-NLS-1$
120
121  private static final String splitter = "[,\t " + NL + "]+"; //$NON-NLS-1$ //$NON-NLS-2$
122
123  //required fields
124  private File file;
125  private String project;
126  private String branch;
127  private String buildID;
128  private String feedURL;
129  private String buildURL;
130
131  //optional
132  private String buildAlias;
133
134  //optional
135  private String[] dependencyURLs = new String[] {};
136
137  //optional
138  private String releaseNotesURL;
139  private String updateManagerURL;
140  private String downloadsURL;
141  private String jarSigningStatus;
142  private String callistoStatus;
143  private String buildType;
144
145  //optional
146  private String[] releases = new String[] {};
147
148  //optional
149  private String JUnitTestURL;
150  private String performanceTestURL;
151  private String APITestURL;
152  private String[] JUnitTestResults;
153  private String[] performanceTestResults;
154  private String[] APITestResults;
155
156  //optional
157  public void setDebug(int debug) { this.debug = debug; }
158
159  //required fields
160  public void setFile(String file) {
161    if (isNullString(file))
162    { System.err.println(Messages.getString("RSSFeedCommon.FileError")); }  //$NON-NLS-1$
163    else
164    { this.file = new File(file); }
165  }
166  public void setProject(String project) {
167    if (isNullString(project))
168    { System.err.println(Messages.getString("RSSFeedCommon.ProjectError")); }  //$NON-NLS-1$
169    else
170    { this.project = project; }
171  }
172  public void setBranch(String branch) {
173    if (isNullString(branch))
174    { System.err.println(Messages.getString("RSSFeedAddEntryTask.BranchError")); }  //$NON-NLS-1$
175    else
176    { this.branch = branch; }
177  }
178  public void setBuildID(String buildID) {
179    if (isNullString(buildID))
180    { System.err.println(Messages.getString("RSSFeedAddEntryTask.BuildIDError")); }  //$NON-NLS-1$
181    else
182    { this.buildID = buildID; }
183  }
184  public void setFeedURL(String feedURL) {
185    if (isNullString(feedURL))
186    { System.err.println(Messages.getString("RSSFeedCommon.FeedURLError")); }  //$NON-NLS-1$
187    else
188    { this.feedURL = feedURL; }
189  }
190  public void setBuildURL(String buildURL) {
191    if (isNullString(buildURL))
192    { System.err.println(Messages.getString("RSSFeedAddEntryTask.BuildURLError")); }  //$NON-NLS-1$
193    else
194    { this.buildURL = buildURL; }
195  }
196
197  //optional: alias is usually something like "3.2.0M6"
198  public void setBuildAlias(String buildAlias) { this.buildAlias = buildAlias; }
199
200  //optional: upstream dependencies, eg., UML2 depends on emf and eclipse, so specify TWO URLs in properties file or ant task
201  public void setDependencyURLs(String dependencyURLs) { if (!isNullString(dependencyURLs)) { this.dependencyURLs = dependencyURLs.split(splitter); } }
202
203  //optional: define releases available in this build for a series of operating systems, windowing systems, and type
204  public void setReleases(String releases) { if (!isNullString(releases)) { this.releases = releases.split(splitter); } }
205
206  //optional: informational links to release notes, downloads, update manager
207  public void setReleaseNotesURL(String releaseNotesURL) { this.releaseNotesURL = releaseNotesURL; }
208  public void setUpdateManagerURL(String updateManagerURL) { this.updateManagerURL = updateManagerURL; }
209  public void setDownloadsURL(String downloadsURL) { this.downloadsURL = downloadsURL; }
210  public void setJarSigningStatus(String jarSigningStatus) { this.jarSigningStatus = jarSigningStatus; }
211  public void setCallistoStatus(String callistoStatus) { this.callistoStatus = callistoStatus; }
212  public void setBuildType(String buildType) {
213    if (!isNullString(buildType))
214    {
215      this.buildType = buildType;
216    }
217    else
218    {
219      this.buildType = buildID.replaceAll("[^NIMSR]", NS); //$NON-NLS-1$
220      if (this.buildType.length()>1)
221      {
222        this.buildType=this.buildType.substring(0, 1);
223      }
224    }
225
226  }
227
228  //optional: test URLs and results
229  public void setJUnitTestURL(String JUnitTestURL) { this.JUnitTestURL = JUnitTestURL; }
230  public void setPerformanceTestURL(String performanceTestURL) { this.performanceTestURL = performanceTestURL; }
231  public void setAPITestURL(String APITestURL) { this.APITestURL = APITestURL; }
232  public void setJUnitTestResults(String JUnitTestResults) { if (!isNullString(JUnitTestResults)) { this.JUnitTestResults = JUnitTestResults.split(splitter); } }
233  public void setPerformanceTestResults(String performanceTestResults) { if (!isNullString(performanceTestResults)) { this.performanceTestResults = performanceTestResults.split(splitter); } }
234  public void setAPITestResults(String APITestResults) { if (!isNullString(APITestResults)) { this.APITestResults = APITestResults.split(splitter); } }
235
236  // The method executing the task
237  public void execute() throws BuildException {
238    if (debug>0) {
239      System.out.println(Messages.getString("RSSFeedAddEntryTask.AddingEntryTo") + project + SP + Messages.getString("RSSFeedCommon.RSSFeedFile") + SP + file.toString() + ", " + Messages.getString("RSSFeedCommon.ToBePublishedAt") + feedURL); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
240    }
241    updateFeedXML(file); // load previous
242  }
243
244  //$ANALYSIS-IGNORE codereview.java.rules.exceptions.RuleExceptionsSpecificExceptions
245  private void updateFeedXML(File file){
246    if (!file.exists()) {
247      System.out.println(Messages.getString("RSSFeedCommon.RSSFeedFile") + SP + file.toString() + SP + Messages.getString("RSSFeedAddEntryTask.DoesNotExist")); //$NON-NLS-1$ //$NON-NLS-2$
248      RSSFeedCreateFeedTask creator=new RSSFeedCreateFeedTask();
249      creator.setFile(file.toString());
250      creator.setFeedURL(feedURL);
251      creator.setProject(project);
252      creator.setDebug(debug);
253      creator.execute();
254    }
255    DocumentBuilderFactory documentBuilderFactory=DocumentBuilderFactory.newInstance();
256    documentBuilderFactory.setNamespaceAware(true);
257    DocumentBuilder documentBuilder=null;
258    try {
259      documentBuilder=documentBuilderFactory.newDocumentBuilder();
260    }
261    catch (ParserConfigurationException e) {
262      e.printStackTrace();
263    }
264    Document document=null;
265    try {
266      document=documentBuilder.parse(file);
267    }
268    catch (SAXException e) {
269      e.printStackTrace();
270    }
271    catch (IOException e) {
272      e.printStackTrace();
273    }
274
275    Transformer transformer = null;
276    try {
277      transformer = createTransformer("UTF-8"); //$NON-NLS-1$
278    } catch (TransformerException e) {
279      e.printStackTrace();
280    }
281
282    Element element=document.getDocumentElement();
283    for (Node child=element.getFirstChild(); child != null; child=child.getNextSibling()) {
284      if ("updated".equals(child.getLocalName())) { //$NON-NLS-1$
285        if (debug > 0) {
286          System.out.println(Messages.getString("RSSFeedCommon.Set") + " <" + child.getLocalName()+ ">"+ now+ "</"+ child.getLocalName()+ ">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
287        }
288        ((Element)child).setTextContent(now);
289      }
290      else if ("id".equals(child.getLocalName())) { //$NON-NLS-1$
291        Node newNode=createEntry(document);
292        if (debug > 0) {
293          System.out.println(Messages.getString("RSSFeedAddEntryTask.AttachNew") + " <entry/>"); //$NON-NLS-1$ //$NON-NLS-2$
294        }
295        try {
296          if (debug > 0) {
297            System.out.println(SEP); //$NON-NLS-1$
298            transformer.transform(new DOMSource(newNode),new StreamResult(System.out));
299            System.out.println(SEP); //$NON-NLS-1$
300          }
301        }
302        catch (TransformerException e) {
303          e.printStackTrace();
304        }
305        Node refNode=child.getNextSibling();
306        element.insertBefore(document.createTextNode(NL + "  "),refNode); //$NON-NLS-1$
307        element.insertBefore(newNode,refNode);
308        break;
309      }
310    }
311    try {
312      transformer.transform(new DOMSource(document),new StreamResult(new OutputStreamWriter(new FileOutputStream(file))));
313      if (debug > 1) {
314        System.out.println(SEP); //$NON-NLS-1$
315        transformer.transform(new DOMSource(document),new StreamResult(System.out));
316        System.out.println(SEP); //$NON-NLS-1$
317      }
318    }
319    catch (FileNotFoundException e) {
320      e.printStackTrace();
321    }
322    catch (TransformerException e) {
323      e.printStackTrace();
324    }
325  }
326
327
328  private Element createEntry(Document document) {
329
330//  <entry>
331    Element entry =  document.createElement("entry"); //$NON-NLS-1$
332
333    String[] txt = { NL + "  ", NL + "    ", NL + "      ", NL + "        ", NL + "          " , NL + "            " }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
334    Element elem = null;
335
336    String projectVersionString = project + SP + (!isNullString(buildAlias)?  //$NON-NLS-1$
337      (buildAlias.startsWith(branch) ?
338        buildAlias + " (" + buildID + ")" :                   // 2.2.0RC2 (S200605051234) //$NON-NLS-1$ //$NON-NLS-2$
339          buildAlias + " (" + branch + "." + buildID + ")") : // Foobar (2.2.0.S200605051234)  //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
340            branch + SP + buildID);                                // 2.2.0.S200605051234 //$NON-NLS-1$
341
342    doVarSubs();
343
344//  <title>[announce] " + project + SP + branch + SP + buildID + " is available</title>
345    elem = document.createElement("title"); //$NON-NLS-1$
346    elem.setTextContent(Messages.getString("RSSFeedAddEntryTask.AnnouncePrefix") + projectVersionString + SP + Messages.getString("RSSFeedAddEntryTask.IsAvailable")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
347    attachNode(document, entry, elem, txt[1]);
348
349//  <link href=\"" + buildURL + "\"/>
350    elem = document.createElement("link"); //$NON-NLS-1$
351    elem.setAttribute("href", !isNullString(buildURL) ? buildURL : projectVersionString); //$NON-NLS-1$
352    attachNode(document, entry, elem, txt[1]);
353
354//  <id>" + buildURL + "</id>
355    elem = document.createElement("id"); //$NON-NLS-1$
356    elem.setTextContent(!isNullString(buildURL) ? buildURL : projectVersionString);
357    attachNode(document, entry, elem, txt[1]);
358
359//  <updated>" + getTimestamp() + "</updated>
360    elem = document.createElement("updated"); //$NON-NLS-1$
361    elem.setTextContent(now);
362    attachNode(document, entry, elem, txt[1]);
363
364//  <summary>
365    Element summary = document.createElement("summary"); //$NON-NLS-1$
366    attachNode(document, entry, summary, txt[1]);
367
368//  <build callisto="" jars="" type="" href="" xmlns="http://www.eclipse.org/2006/BuildFeed">
369    Element build = document.createElement("build"); //$NON-NLS-1$
370    build.setAttribute("jars", jarSigningStatus); //$NON-NLS-1$
371    build.setAttribute("callisto", callistoStatus); //$NON-NLS-1$
372    build.setAttribute("type", buildType); //$NON-NLS-1$
373    build.setAttribute("xmlns", "http://www.eclipse.org/2006/BuildFeed"); //$NON-NLS-1$ //$NON-NLS-2$
374    if (!isNullString(buildURL)) {
375      build.setAttribute("href",buildURL); //$NON-NLS-1$
376    }
377    attachNode(document, summary, build, txt[2]);
378
379//  <update>" + usiteURL + "</update>
380    if (!isNullString(updateManagerURL)) {
381      elem = document.createElement("update"); //$NON-NLS-1$
382      elem.setTextContent(updateManagerURL);
383      attachNode(document, build, elem, txt[3]);
384    }
385
386//  <downloads>" + dropsURL + "</downloads>
387    if (!isNullString(downloadsURL)) {
388      elem = document.createElement("downloads"); //$NON-NLS-1$
389      elem.setTextContent(downloadsURL);
390      attachNode(document, build, elem, txt[3]);
391    }
392
393//  <releasenotes>" + releaseNotesURL + "</releasenotes>
394    if (!isNullString(releaseNotesURL)) {
395      elem = document.createElement("releasenotes"); //$NON-NLS-1$
396      elem.setTextContent(releaseNotesURL);
397      attachNode(document, build, elem, txt[3]);
398    }
399
400//  <releases>
401//    <release os="" ws="" type=""> + filename + </release>
402    if (releases!=null && releases.length>0) {
403      if (releases.length % 5 != 0) {
404        System.err.println(Messages.getString("RSSFeedAddEntryTask.WrongNumberOfVariables") + SP + Messages.getString("RSSFeedAddEntryTask.MustBeMultipleOf5") + SP + Messages.getString("RSSFeedAddEntryTask.InProperty") + SP + "releases"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
405      }
406      Element releasesElem = document.createElement("releases"); //$NON-NLS-1$
407      for (int i = 0; i < releases.length; i+=5)
408      {
409        Element release = document.createElement("release"); //$NON-NLS-1$
410        release.setAttribute("os", releases[i]); //$NON-NLS-1$
411        release.setAttribute("ws", releases[i+1]); //$NON-NLS-1$
412        release.setAttribute("arch", releases[i+2]); //$NON-NLS-1$
413        release.setAttribute("type", releases[i+3]); //$NON-NLS-1$
414        release.setTextContent(varSub(releases[i+4]));
415        attachNode(document, releasesElem, release, txt[4]);
416      }
417      attachNode(document, build, releasesElem, txt[3]);
418    }
419
420//  <tests>
421    Element tests = document.createElement("tests"); //$NON-NLS-1$
422
423//    <test type=\"junit\" href=\"" + JUnitTestURL + "\"/>
424    if (!isNullString(JUnitTestURL)) {
425      Element test = document.createElement("test"); //$NON-NLS-1$
426      test.setAttribute("type", "junit"); //$NON-NLS-1$ //$NON-NLS-2$
427      test.setAttribute("href", JUnitTestURL); //$NON-NLS-1$
428      if (JUnitTestResults!=null && JUnitTestResults.length>0) {
429        if (JUnitTestResults.length % 4 != 0) {
430          System.err.println(Messages.getString("RSSFeedAddEntryTask.WrongNumberOfVariables") + SP + Messages.getString("RSSFeedAddEntryTask.MustBeMultipleOf4") + SP + Messages.getString("RSSFeedAddEntryTask.InProperty") + SP + "JUnitTestResults"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
431        }
432        for (int i = 0; i < JUnitTestResults.length; i+=4)
433        {
434          Element result = document.createElement("result"); //$NON-NLS-1$
435          result.setAttribute("os", JUnitTestResults[i]); //$NON-NLS-1$
436          result.setAttribute("ws", JUnitTestResults[i+1]); //$NON-NLS-1$
437          result.setAttribute("arch", JUnitTestResults[i+2]); //$NON-NLS-1$
438          result.setTextContent(JUnitTestResults[i+3]);
439          attachNode(document, test, result, txt[5]);
440        }
441        // extra space to close containing tag
442        elem.appendChild(document.createTextNode(txt[4]));
443      }
444      attachNode(document, tests, test, txt[4]);
445    }
446
447//    <test type=\"performance\" href=\"" + performanceTestURL + "\"/>
448    if (!isNullString(performanceTestURL)) {
449      Element test = document.createElement("test"); //$NON-NLS-1$
450      test.setAttribute("type", "performance"); //$NON-NLS-1$ //$NON-NLS-2$
451      test.setAttribute("href", performanceTestURL); //$NON-NLS-1$
452      if (performanceTestResults!=null && performanceTestResults.length>0) {
453        if (performanceTestResults.length % 4 != 0) {
454          System.err.println(Messages.getString("RSSFeedAddEntryTask.WrongNumberOfVariables") + SP + Messages.getString("RSSFeedAddEntryTask.MustBeMultipleOf4") + SP + Messages.getString("RSSFeedAddEntryTask.InProperty") + SP + "performanceTestResults"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
455        }
456        for (int i = 0; i < performanceTestResults.length; i+=4)
457        {
458          Element result = document.createElement("result"); //$NON-NLS-1$
459          result.setAttribute("os", performanceTestResults[i]); //$NON-NLS-1$
460          result.setAttribute("ws", performanceTestResults[i+1]); //$NON-NLS-1$
461          result.setAttribute("arch", performanceTestResults[i+2]); //$NON-NLS-1$
462          result.setTextContent(performanceTestResults[i+3]);
463          attachNode(document, test, result, txt[5]);
464        }
465        // extra space to close containing tag
466        test.appendChild(document.createTextNode(txt[4]));
467      }
468      attachNode(document, tests, test, txt[4]);
469    }
470
471//    <test type=\"performance\" href=\"" + performanceTestURL + "\"/>
472    if (!isNullString(APITestURL)) {
473      Element test = document.createElement("test"); //$NON-NLS-1$
474      test.setAttribute("type", "api"); //$NON-NLS-1$ //$NON-NLS-2$
475      test.setAttribute("href", APITestURL); //$NON-NLS-1$
476      if (APITestResults!=null && APITestResults.length>0) {
477        if (APITestResults.length % 4 != 0) {
478          System.err.println(Messages.getString("RSSFeedAddEntryTask.WrongNumberOfVariables") + SP + Messages.getString("RSSFeedAddEntryTask.MustBeMultipleOf4") + SP + Messages.getString("RSSFeedAddEntryTask.InProperty") + SP + "APITestResults"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
479        }
480        for (int i = 0; i < APITestResults.length; i+=4)
481        {
482          Element result = document.createElement("result"); //$NON-NLS-1$
483          result.setAttribute("os", APITestResults[i]); //$NON-NLS-1$
484          result.setAttribute("ws", APITestResults[i+1]); //$NON-NLS-1$
485          result.setAttribute("arch", APITestResults[i+2]); //$NON-NLS-1$
486          result.setTextContent(APITestResults[i+3]);
487          attachNode(document, tests, result, txt[5]);
488        }
489        // extra space to close containing tag
490        test.appendChild(document.createTextNode(txt[4]));
491      }
492      attachNode(document, tests, test, txt[4]);
493    }
494
495    attachNode(document, build, tests, txt[3]);
496
497    if (dependencyURLs!=null && dependencyURLs.length>0) {
498  //  <dependencies>
499  //    <dependency>" + dependencyURL + "</dependency>
500      Element dependencies = document.createElement("dependencies"); //$NON-NLS-1$
501      for (int i = 0; i < dependencyURLs.length; i++)
502      {
503        elem = document.createElement("dependency"); //$NON-NLS-1$
504        elem.setTextContent(dependencyURLs[i]);
505        attachNode(document, dependencies, elem, txt[4]);
506      }
507      attachNode(document, build, dependencies, txt[3]);
508    }
509
510    return entry;
511  }
512
513  //$ANALYSIS-IGNORE codereview.java.rules.exceptions.RuleExceptionsSpecificExceptions
514  private void attachNode(Document document,Element entry,Element elem,String txt){
515    entry.appendChild(document.createTextNode(txt));
516    entry.appendChild(elem);
517  }
518
519  private static String getTimestamp() { // eg., 2006-04-10T20:40:08Z
520    return DateUtils.format(new Date(), DateUtils.ISO8601_DATETIME_PATTERN) + "Z";  //$NON-NLS-1$
521  }
522
523  private void doVarSubs()
524  {
525    feedURL = varSub(feedURL);
526    buildURL = varSub(buildURL);
527
528    releaseNotesURL = varSub(releaseNotesURL);
529    updateManagerURL = varSub(updateManagerURL);
530    downloadsURL = varSub(downloadsURL);
531
532    JUnitTestURL = varSub(JUnitTestURL);
533    performanceTestURL = varSub(performanceTestURL);
534    APITestURL = varSub(APITestURL);
535  }
536
537  public static Transformer createTransformer(String encoding) throws TransformerException
538  {
539    TransformerFactory transformerFactory = TransformerFactory.newInstance();
540
541    try
542    {
543      transformerFactory.setAttribute("indent-number", new Integer(2)); //$NON-NLS-1$
544    }
545    catch (IllegalArgumentException exception)
546    {
547    }
548
549    Transformer transformer = transformerFactory.newTransformer();
550
551    transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
552    transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
553
554    // Unless a width is set, there will be only line breaks but no indentation.
555    // The IBM JDK and the Sun JDK don't agree on the property name,
556    // so we set them both.
557    //
558    transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$
559    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$
560    if (encoding != null)
561    {
562      transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
563    }
564    return transformer;
565  }
566
567  /*
568   * variable substitution in URLs - eg., replace %%branch%% and %%buildID%% in buildURL
569   */
570  private String varSub(String urlstring)
571  {
572    if (!isNullString(urlstring) && urlstring.indexOf("%%")>=0) //$NON-NLS-1$
573    {
574      return urlstring.replaceAll(Messages.getString("RSSFeedAddEntryTask.BranchKeyword"), branch).replaceAll(Messages.getString("RSSFeedAddEntryTask.BuildIDKeyword"), buildID).replaceAll(Messages.getString("RSSFeedAddEntryTask.BuildAliasKeyword"), buildAlias); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
575    }
576    return urlstring;
577  }
578
579  private static boolean isNullString(String str)
580  {
581    return RSSFeedUtil.isNullString(str);
582  }
583
584}