PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/netbeans-7.3/nbbuild/antsrc/org/netbeans/nbbuild/MakeUpdateDesc.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 820 lines | 690 code | 38 blank | 92 comment | 149 complexity | c458bbc6cf716d264264cb9790e1a3f3 MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2012 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.nbbuild;
  45. import java.io.BufferedReader;
  46. import java.util.HashMap;
  47. import java.util.LinkedHashMap;
  48. import java.util.Map;
  49. import java.util.jar.Manifest;
  50. import org.apache.tools.ant.types.FileSet;
  51. import org.apache.tools.ant.BuildException;
  52. import org.apache.tools.ant.taskdefs.MatchingTask;
  53. import org.apache.tools.ant.DirectoryScanner;
  54. import java.io.File;
  55. import java.io.FileFilter;
  56. import java.io.FileOutputStream;
  57. import java.io.IOException;
  58. import java.io.InputStream;
  59. import java.io.InputStreamReader;
  60. import java.io.OutputStream;
  61. import java.io.OutputStreamWriter;
  62. import java.io.PrintWriter;
  63. import java.text.Collator;
  64. import java.text.DateFormat;
  65. import java.text.SimpleDateFormat;
  66. import java.util.ArrayList;
  67. import java.util.Collection;
  68. import java.util.Comparator;
  69. import java.util.Date;
  70. import java.util.Enumeration;
  71. import java.util.List;
  72. import java.util.Properties;
  73. import java.util.TimeZone;
  74. import java.util.TreeMap;
  75. import java.util.TreeSet;
  76. import java.util.jar.Attributes;
  77. import java.util.jar.JarEntry;
  78. import java.util.jar.JarFile;
  79. import java.util.regex.Matcher;
  80. import java.util.regex.Pattern;
  81. import java.util.zip.ZipEntry;
  82. import org.apache.tools.ant.Project;
  83. import org.apache.tools.ant.types.Path;
  84. import org.w3c.dom.Document;
  85. import org.w3c.dom.Element;
  86. import org.w3c.dom.NodeList;
  87. import org.w3c.dom.Text;
  88. import org.xml.sax.InputSource;
  89. /** Makes an XML file representing update information from NBMs.
  90. *
  91. * @author Jesse Glick
  92. */
  93. public class MakeUpdateDesc extends MatchingTask {
  94. protected boolean usedMatchingTask = false;
  95. /** Set of NBMs presented as a folder in the Update Center. */
  96. public /*static*/ class Group {
  97. public List<FileSet> filesets = new ArrayList<FileSet>();
  98. public String name;
  99. /** Displayed name of the group. */
  100. public void setName (String s) {
  101. name = s;
  102. }
  103. /** Add fileset to the group of NetBeans modules **/
  104. public void addFileSet (FileSet set) {
  105. filesets.add(set);
  106. }
  107. }
  108. /** pointer to another xml entity to include **/
  109. public class Entityinclude {
  110. public String file;
  111. /** Path to the entity file.
  112. * It included as an xml-entity pointer in master .xml file.
  113. */
  114. public void setFile (String f) {
  115. file = f;
  116. }
  117. }
  118. private List<Entityinclude> entityincludes = new ArrayList<Entityinclude>();
  119. private List<Group> groups = new ArrayList<Group>();
  120. private List<FileSet> filesets = new ArrayList<FileSet>();
  121. private File desc;
  122. /** Description file to create. */
  123. public void setDesc(File d) {
  124. desc = d;
  125. }
  126. /** Module group to create **/
  127. public Group createGroup () {
  128. Group g = new Group ();
  129. groups.add (g);
  130. return g;
  131. }
  132. /** External XML entity include **/
  133. public Entityinclude createEntityinclude () {
  134. Entityinclude i = new Entityinclude ();
  135. entityincludes.add (i);
  136. return i;
  137. }
  138. /**
  139. * Adds a set of files (nested fileset attribute).
  140. */
  141. public void addFileset(FileSet set) {
  142. filesets.add(set);
  143. }
  144. private boolean automaticGrouping;
  145. /**
  146. * Turn on if you want modules added to the root fileset
  147. * to be automatically added to a group based on their display category (if set).
  148. */
  149. public void setAutomaticgrouping(boolean b) {
  150. automaticGrouping = b;
  151. }
  152. private String dist_base;
  153. /**
  154. * Set distribution base, which will be enforced
  155. */
  156. public void setDistBase(String dbase) {
  157. dist_base = dbase;
  158. }
  159. private boolean useLicenseUrl;
  160. public void setUseLicenseUrl(boolean useLicenseUrl) {
  161. this.useLicenseUrl = useLicenseUrl;
  162. }
  163. private Path updaterJar;
  164. /** Fileset for platform/modules/ext/updater.jar, to be used in DTD validation. */
  165. public Path createUpdaterJar() {
  166. return updaterJar = new Path(getProject());
  167. }
  168. private String notificationMessage;
  169. private String notificationURL;
  170. public void setNotificationMessage(String message) {
  171. this.notificationMessage = message;
  172. }
  173. public void setNotificationURL(String url) {
  174. this.notificationURL = url;
  175. }
  176. private String contentDescription;
  177. private String contentDescriptionURL;
  178. public void setContentDescription(String message) {
  179. this.contentDescription = message;
  180. }
  181. public void setContentDescriptionURL(String url) {
  182. this.contentDescriptionURL = url;
  183. }
  184. // Similar to org.openide.xml.XMLUtil methods.
  185. private static String xmlEscape(String s) {
  186. int max = s.length();
  187. StringBuffer s2 = new StringBuffer((int)(max * 1.1 + 1));
  188. for (int i = 0; i < max; i++) {
  189. char c = s.charAt(i);
  190. switch (c) {
  191. case '<':
  192. s2.append("&lt;"); //NOI18N
  193. break;
  194. case '>':
  195. s2.append("&gt;"); //NOI18N
  196. break;
  197. case '&':
  198. s2.append("&amp;"); //NOI18N
  199. break;
  200. case '"':
  201. s2.append("&quot;"); //NOI18N
  202. break;
  203. default:
  204. s2.append(c);
  205. break;
  206. }
  207. }
  208. return s2.toString();
  209. }
  210. public @Override void execute () throws BuildException {
  211. Group root = new Group();
  212. for (FileSet fs : filesets) {
  213. root.addFileSet(fs);
  214. }
  215. groups.add(root);
  216. if (desc.exists ()) {
  217. // Simple up-to-date check.
  218. long time = desc.lastModified ();
  219. boolean uptodate = true;
  220. CHECK:
  221. for (Group g : groups) {
  222. for (FileSet n : g.filesets) {
  223. if ( n != null ) {
  224. DirectoryScanner ds = n.getDirectoryScanner(getProject());
  225. String[] files = ds.getIncludedFiles();
  226. File bdir = ds.getBasedir();
  227. for (String file : files) {
  228. File n_file = new File(bdir, file);
  229. if (n_file.lastModified () > time) {
  230. uptodate = false;
  231. break CHECK;
  232. }
  233. }
  234. }
  235. }
  236. }
  237. if (uptodate) return;
  238. }
  239. log ("Creating update description " + desc.getAbsolutePath ());
  240. Map<String,Collection<Module>> modulesByGroup = loadNBMs();
  241. boolean targetClustersDefined = false;
  242. for (Collection<Module> modules : modulesByGroup.values()) {
  243. for (Module m : modules) {
  244. targetClustersDefined |= m.xml.getAttributeNode("targetcluster") != null;
  245. }
  246. }
  247. boolean isPreferredUpdateDefined = false;
  248. for (Collection<Module> modules : modulesByGroup.values()) {
  249. for (Module m : modules) {
  250. isPreferredUpdateDefined |= m.xml.getAttributeNode("preferredupdate") != null;
  251. }
  252. }
  253. boolean use25DTD = false;
  254. for (Collection<Module> modules : modulesByGroup.values()) {
  255. for (Module m : modules) {
  256. Element manifest = ((Element) m.xml.getElementsByTagName("manifest").item(0));
  257. use25DTD |= (m.autoload || m.eager ||
  258. manifest.getAttribute("AutoUpdate-Show-In-Client").length() > 0 ||
  259. manifest.getAttribute("AutoUpdate-Essential-Module").length() > 0);
  260. }
  261. }
  262. // XXX Apparently cannot create a doc with entities using DOM 2.
  263. try {
  264. desc.delete();
  265. OutputStream os = new FileOutputStream(desc);
  266. try {
  267. PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "UTF-8")); //NOI18N
  268. pw.println ("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"); //NOI18N
  269. pw.println ();
  270. DateFormat format = new SimpleDateFormat("ss/mm/HH/dd/MM/yyyy"); //NOI18N
  271. format.setTimeZone(TimeZone.getTimeZone("GMT")); //NOI18N
  272. String date = format.format(new Date());
  273. if ( entityincludes.size() > 0 ) {
  274. // prepare .ent file
  275. String ent_name = desc.getAbsolutePath();
  276. int xml_idx = ent_name.indexOf(".xml"); //NOI18N
  277. if (xml_idx != -1) {
  278. ent_name = ent_name.substring (0, xml_idx) + ".ent"; //NOI18N
  279. } else {
  280. ent_name = ent_name + ".ent"; //NOI18N
  281. }
  282. File desc_ent = new File(ent_name);
  283. desc_ent.delete();
  284. if (isPreferredUpdateDefined || (contentDescription != null && ! contentDescription.isEmpty())) {
  285. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.7//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_7.dtd\" [");
  286. } else if (useLicenseUrl) {
  287. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.6//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_6.dtd\" [");
  288. } else if (use25DTD) {
  289. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.5//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_5.dtd\" [");
  290. } else if (targetClustersDefined) {
  291. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.4//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_4.dtd\" [");
  292. } else {
  293. // #74866: no need for targetcluster, so keep compat w/ 5.0 AU.
  294. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.3//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_3.dtd\" [");
  295. }
  296. // Would be better to follow order of groups and includes
  297. pw.println (" <!ENTITY entity SYSTEM \"" + xmlEscape(desc_ent.getName()) + "\">"); //NOI18N
  298. int inc_num=0;
  299. for (int i=0; i<entityincludes.size(); i++) {
  300. Entityinclude ei = entityincludes.get(i);
  301. pw.println (" <!ENTITY include" + i + " SYSTEM \"" + xmlEscape(ei.file) + "\">"); //NOI18N
  302. }
  303. pw.println ("]>"); //NOI18N
  304. pw.println ();
  305. pw.println ("<module_updates timestamp=\"" + xmlEscape(date) + "\">"); //NOI18N
  306. pw.println (" &entity;"); //NOI18N
  307. for (int i=0; i<entityincludes.size(); i++) {
  308. pw.println (" &include" + i + ";"); //NOI18N
  309. }
  310. pw.println ("</module_updates>"); //NOI18N
  311. pw.println ();
  312. pw.flush ();
  313. pw.close ();
  314. os = new FileOutputStream(desc_ent);
  315. pw = new PrintWriter(new OutputStreamWriter(os, "UTF-8")); //NOI18N
  316. pw.println ("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"); //NOI18N
  317. pw.println ("<!-- external entity include " + date + " -->");
  318. pw.println ();
  319. } else {
  320. if (isPreferredUpdateDefined || (contentDescription != null && ! contentDescription.isEmpty())) {
  321. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.7//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_7.dtd\">");
  322. } else if (useLicenseUrl) {
  323. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.6//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_6.dtd\">");
  324. } else if (use25DTD) {
  325. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.5//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_5.dtd\">");
  326. } else if (targetClustersDefined) {
  327. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.4//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_4.dtd\">");
  328. } else {
  329. pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.3//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_3.dtd\">");
  330. }
  331. pw.println ("<module_updates timestamp=\"" + date + "\">"); //NOI18N
  332. pw.println ();
  333. }
  334. writeNotification(pw);
  335. pw.println ();
  336. writeContentDescription(pw);
  337. pw.println ();
  338. Map<String,Element> licenses = new HashMap<String,Element>();
  339. String prefix = null;
  340. if (dist_base != null) {
  341. // fix/enforce distribution URL base
  342. if (dist_base.equals(".")) {
  343. prefix = "";
  344. } else {
  345. prefix = dist_base + "/";
  346. }
  347. }
  348. final File licensesDir = new File(desc.getParentFile(), "licenses");
  349. if (useLicenseUrl) {
  350. if (licensesDir.exists()) {
  351. File [] licenseFiles = licensesDir.listFiles(new FileFilter() {
  352. public boolean accept(File pathname) {
  353. return pathname.getName().endsWith(".license");
  354. }
  355. });
  356. for (File f : licenseFiles) {
  357. f.delete();
  358. }
  359. } else {
  360. licensesDir.mkdir();
  361. }
  362. }
  363. for (Map.Entry<String,Collection<Module>> entry : modulesByGroup.entrySet()) {
  364. String groupName = entry.getKey();
  365. // Don't indent; embedded descriptions would get indented otherwise.
  366. log("Creating group \"" + groupName + "\"");
  367. if (groupName != null) {
  368. pw.println("<module_group name=\"" + xmlEscape(groupName) + "\">");
  369. pw.println();
  370. }
  371. for (Module m : entry.getValue()) {
  372. Element module = m.xml;
  373. if (module.getAttribute("downloadsize").equals("0")) {
  374. module.setAttribute("downloadsize", Long.toString(m.nbm.length() + m.externalDownloadSize));
  375. }
  376. Element manifest = (Element) module.getElementsByTagName("manifest").item(0);
  377. String name = manifest.getAttribute("OpenIDE-Module-Name");
  378. if (name.length() > 0) {
  379. log(" Adding module " + name + " (" + m.nbm.getAbsolutePath() + ")");
  380. }
  381. if(prefix!=null) {
  382. module.setAttribute("distribution", prefix + m.relativePath);
  383. }
  384. NodeList licenseList = module.getElementsByTagName("license");
  385. if (licenseList.getLength() > 0) {
  386. Element license = (Element) licenseList.item(0);
  387. if (useLicenseUrl) {
  388. String relativePath = "licenses/" + license.getAttribute("name") + ".license";
  389. String path = relativePath;
  390. if (prefix != null) {
  391. path = prefix + relativePath;
  392. }
  393. license.setAttribute("url", path);
  394. String licenseText = license.getTextContent();
  395. license.setTextContent("");
  396. FileOutputStream fos = new FileOutputStream(new File(desc.getParentFile(), relativePath));
  397. fos.write(licenseText.getBytes("UTF-8"));
  398. fos.close();
  399. }
  400. // XXX ideally would compare the license texts to make sure they actually match up
  401. licenses.put(license.getAttribute("name"), license);
  402. module.removeChild(license);
  403. }
  404. if (m.autoload) {
  405. module.setAttribute("autoload", "true");
  406. }
  407. if (m.eager) {
  408. module.setAttribute("eager", "true");
  409. }
  410. pw.flush();
  411. XMLUtil.write(module, os);
  412. pw.println();
  413. }
  414. if (groupName != null) {
  415. pw.println("</module_group>");
  416. pw.println();
  417. }
  418. }
  419. pw.flush();
  420. for (Element license : licenses.values()) {
  421. XMLUtil.write(license, os);
  422. }
  423. if ( entityincludes.size() <= 0 ) {
  424. pw.println ("</module_updates>"); //NOI18N
  425. pw.println ();
  426. }
  427. pw.flush ();
  428. pw.close ();
  429. } finally {
  430. os.flush ();
  431. os.close ();
  432. }
  433. } catch (IOException ioe) {
  434. desc.delete ();
  435. throw new BuildException("Cannot create update description", ioe, getLocation());
  436. }
  437. if (updaterJar != null && updaterJar.size() > 0) {
  438. try {
  439. MakeNBM.validateAgainstAUDTDs(new InputSource(desc.toURI().toString()), updaterJar, this);
  440. } catch (Exception x) {
  441. desc.delete();
  442. throw new BuildException("Could not validate " + desc + " after writing: " + x, x, getLocation());
  443. }
  444. } else {
  445. log("No updater.jar specified, cannot validate " + desc + " against DTD", Project.MSG_WARN);
  446. }
  447. }
  448. private static class Module {
  449. public Module() {}
  450. public Element xml;
  451. public File nbm;
  452. public String relativePath;
  453. public boolean autoload, eager;
  454. public long externalDownloadSize;
  455. }
  456. private void writeNotification(PrintWriter pw) {
  457. // write notification message/url if defined
  458. if (notificationMessage == null) {
  459. notificationMessage = "";
  460. }
  461. if (notificationURL == null) {
  462. notificationURL = "";
  463. }
  464. if (notificationMessage.length() > 0 || notificationURL.length() > 0) {
  465. if (notificationMessage.length() == 0) {
  466. pw.println("<notification url=\"" + xmlEscape(notificationURL.toString()) + "\"/>");
  467. } else if (notificationURL.length() == 0) {
  468. pw.println("<notification>" + xmlEscape(notificationMessage) + "</notification>");
  469. } else {
  470. pw.println(
  471. "<notification url=\"" + xmlEscape(notificationURL.toString()) + "\">" +
  472. xmlEscape(notificationMessage) +
  473. "</notification>");
  474. }
  475. pw.println();
  476. }
  477. }
  478. private void writeContentDescription(PrintWriter pw) {
  479. // write content_description message/url if defined
  480. if (contentDescription == null) {
  481. contentDescription = "";
  482. }
  483. if (contentDescriptionURL == null) {
  484. contentDescriptionURL = "";
  485. }
  486. if (contentDescription.length() > 0 || contentDescriptionURL.length() > 0) {
  487. if (contentDescription.length() == 0) {
  488. pw.println("<content_description url=\"" + xmlEscape(contentDescriptionURL.toString()) + "\"/>");
  489. } else if (contentDescriptionURL.length() == 0) {
  490. pw.println("<content_description>" + xmlEscape(contentDescription) + "</content_description>");
  491. } else {
  492. pw.println(
  493. "<content_description url=\"" + xmlEscape(contentDescriptionURL.toString()) + "\">" +
  494. xmlEscape(contentDescription) +
  495. "</content_description>");
  496. }
  497. pw.println();
  498. }
  499. }
  500. private Map<String,Collection<Module>> loadNBMs() throws BuildException {
  501. final Collator COLL = Collator.getInstance(/* XXX any particular locale? */);
  502. // like COLL but handles nulls ~ ungrouped modules (sorted to top):
  503. Comparator<String> groupNameComparator = new Comparator<String>() {
  504. public int compare(String gn1, String gn2) {
  505. return gn1 != null ?
  506. (gn2 != null ? COLL.compare(gn1, gn2) : 1) :
  507. (gn2 != null ? -1 : 0);
  508. }
  509. };
  510. Map<String,Collection<Module>> r = automaticGrouping ?
  511. // generally will be creating groups on the fly, so sort them:
  512. new TreeMap<String,Collection<Module>>(groupNameComparator) :
  513. // preserve explicit order of <group>s:
  514. new LinkedHashMap<String,Collection<Module>>();
  515. // sort modules by display name (where available):
  516. Comparator<Module> moduleDisplayNameComparator = new Comparator<Module>() {
  517. public int compare(Module m1, Module m2) {
  518. int res = COLL.compare(getName(m1), getName(m2));
  519. return res != 0 ? res : System.identityHashCode(m1) - System.identityHashCode(m2);
  520. }
  521. String getName(Module m) {
  522. Element mani = (Element) m.xml.getElementsByTagName("manifest").item(0);
  523. String displayName = mani.getAttribute("OpenIDE-Module-Name");
  524. if (displayName.length() > 0) {
  525. return displayName;
  526. } else {
  527. return mani.getAttribute("OpenIDE-Module");
  528. }
  529. }
  530. };
  531. for (Group g : groups) {
  532. Collection<Module> modules = r.get(g.name);
  533. if (modules == null) {
  534. modules = new TreeSet<Module>(moduleDisplayNameComparator);
  535. r.put(g.name, modules);
  536. }
  537. for (FileSet fs : g.filesets) {
  538. DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  539. for (String file : ds.getIncludedFiles()) {
  540. File n_file = new File(fs.getDir(getProject()), file);
  541. try {
  542. JarFile jar = new JarFile(n_file);
  543. try {
  544. Module m = new Module();
  545. m.nbm = n_file;
  546. m.relativePath = file.replace(File.separatorChar, '/');
  547. Manifest jarmani = jar.getManifest();
  548. if (jarmani != null && jarmani.getMainAttributes().getValue("Bundle-SymbolicName") != null) {
  549. // #181025: treat OSGi bundles specially.
  550. m.xml = fakeOSGiInfoXml(jar, n_file);
  551. modules.add(m);
  552. continue;
  553. }
  554. ZipEntry entry = jar.getEntry("Info/info.xml");
  555. if (entry == null) {
  556. throw new BuildException("NBM " + n_file + " was malformed: no Info/info.xml", getLocation());
  557. }
  558. InputStream is = jar.getInputStream(entry);
  559. try {
  560. m.xml = XMLUtil.parse(new InputSource(is), false, false, XMLUtil.rethrowHandler(), XMLUtil.nullResolver()).getDocumentElement();
  561. } finally {
  562. is.close();
  563. }
  564. Collection<Module> moduleCollection = modules;
  565. Element manifest = ((Element) m.xml.getElementsByTagName("manifest").item(0));
  566. if (automaticGrouping && g.name == null) {
  567. // insert modules with no explicit grouping into group acc. to manifest:
  568. String categ = manifest.getAttribute("OpenIDE-Module-Display-Category");
  569. if (categ.length() > 0) {
  570. moduleCollection = r.get(categ);
  571. if (moduleCollection == null) {
  572. moduleCollection = new TreeSet<Module>(moduleDisplayNameComparator);
  573. r.put(categ, moduleCollection);
  574. }
  575. }
  576. }
  577. boolean old = false; // #110661
  578. String destDir = getProject().getProperty("netbeans.dest.dir");
  579. if (destDir != null) {
  580. for (File cluster : getProject().resolveFile(destDir).listFiles()) {
  581. if (new File(cluster, "modules/org-netbeans-modules-autoupdate.jar").isFile()) {
  582. old = true;
  583. break;
  584. }
  585. }
  586. }
  587. if (!old) {
  588. String cnb = manifest.getAttribute("OpenIDE-Module").replaceFirst("/\\d+$", "");
  589. entry = jar.getEntry("netbeans/config/Modules/" + cnb.replace('.', '-') + ".xml");
  590. if (entry != null) {
  591. is = jar.getInputStream(entry);
  592. try {
  593. NodeList nl = XMLUtil.parse(new InputSource(is), false, false, XMLUtil.rethrowHandler(), XMLUtil.nullResolver()).getElementsByTagName("param");
  594. for (int i = 0; i < nl.getLength(); i++) {
  595. String name = ((Element) nl.item(i)).getAttribute("name");
  596. String value = ((Text) nl.item(i).getFirstChild()).getData();
  597. if (name.equals("autoload") && value.equals("true")) {
  598. m.autoload = true;
  599. }
  600. if (name.equals("eager") && value.equals("true")) {
  601. m.eager = true;
  602. }
  603. }
  604. } finally {
  605. is.close();
  606. }
  607. }
  608. }
  609. Enumeration<JarEntry> en = jar.entries();
  610. while (en.hasMoreElements()) {
  611. JarEntry e = en.nextElement();
  612. if (e.getName().endsWith(".external")) {
  613. InputStream eStream = jar.getInputStream(e);
  614. try {
  615. m.externalDownloadSize += externalSize(eStream);
  616. } finally {
  617. eStream.close();
  618. }
  619. }
  620. }
  621. moduleCollection.add(m);
  622. } finally {
  623. jar.close();
  624. }
  625. } catch (BuildException x) {
  626. throw x;
  627. } catch (Exception e) {
  628. throw new BuildException("Cannot process " + n_file + ": " + e, e, getLocation());
  629. }
  630. }
  631. }
  632. }
  633. return r;
  634. }
  635. private long externalSize(InputStream is) throws IOException {
  636. BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
  637. for (;;) {
  638. String line = br.readLine();
  639. if (line == null) {
  640. break;
  641. }
  642. if (line.startsWith("SIZE:")) {
  643. return Long.parseLong(line.substring(5).trim());
  644. }
  645. }
  646. return 0;
  647. }
  648. /**
  649. * Create the equivalent of {@code Info/info.xml} for an OSGi bundle.
  650. * @param jar a bundle
  651. * @return a {@code <module ...><manifest .../></module>} valid according to
  652. * <a href="http://www.netbeans.org/dtds/autoupdate-info-2_5.dtd">DTD</a>
  653. */
  654. private Element fakeOSGiInfoXml(JarFile jar, File whereFrom) throws IOException {
  655. Attributes attr = jar.getManifest().getMainAttributes();
  656. Properties localized = new Properties();
  657. String bundleLocalization = attr.getValue("Bundle-Localization");
  658. if (bundleLocalization != null) {
  659. InputStream is = jar.getInputStream(jar.getEntry(bundleLocalization + ".properties"));
  660. try {
  661. localized.load(is);
  662. } finally {
  663. is.close();
  664. }
  665. }
  666. return fakeOSGiInfoXml(attr, localized, whereFrom);
  667. }
  668. static Element fakeOSGiInfoXml(Attributes attr, Properties localized, File whereFrom) {
  669. Document doc = XMLUtil.createDocument("module");
  670. Element module = doc.getDocumentElement();
  671. String cnb = JarWithModuleAttributes.extractCodeName(attr);
  672. module.setAttribute("codenamebase", cnb);
  673. module.setAttribute("distribution", ""); // seems to be ignored anyway
  674. module.setAttribute("downloadsize", "0"); // recalculated anyway
  675. module.setAttribute("targetcluster", whereFrom.getParentFile().getName()); // #207075 comment #3
  676. Element manifest = doc.createElement("manifest");
  677. module.appendChild(manifest);
  678. manifest.setAttribute("AutoUpdate-Show-In-Client", "false");
  679. manifest.setAttribute("OpenIDE-Module", cnb);
  680. String bundleName = loc(localized, attr, "Bundle-Name");
  681. manifest.setAttribute("OpenIDE-Module-Name", bundleName != null ? bundleName : cnb);
  682. String bundleVersion = attr.getValue("Bundle-Version");
  683. manifest.setAttribute("OpenIDE-Module-Specification-Version",
  684. bundleVersion != null ? bundleVersion.replaceFirst("^(\\d+([.]\\d+([.]\\d+)?)?)([.].+)?$", "$1") : "0");
  685. String requireBundle = attr.getValue("Require-Bundle");
  686. if (requireBundle != null) {
  687. StringBuilder b = new StringBuilder();
  688. boolean needsNetbinox = false;
  689. // http://stackoverflow.com/questions/1757065/java-splitting-a-comma-separated-string-but-ignoring-commas-in-quotes
  690. for (String dep : requireBundle.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)")) {
  691. Matcher m = Pattern.compile("([^;]+)(.*)").matcher(dep);
  692. if (!m.matches()) {
  693. throw new BuildException("Could not parse dependency: " + dep + " in " + whereFrom);
  694. }
  695. String requiredBundleName = m.group(1); // dep CNB
  696. if(requiredBundleName.trim().equals("org.eclipse.osgi")) {
  697. needsNetbinox = true;
  698. continue;
  699. }
  700. Matcher m2 = Pattern.compile(";([^:=]+):?=\"?([^;\"]+)\"?").matcher(m.group(2));
  701. boolean isOptional = false;
  702. while(m2.find()) {
  703. if(m2.group(1).equals("resolution") && m2.group(2).equals("optional")) {
  704. isOptional = true;
  705. break;
  706. }
  707. }
  708. if(isOptional) {
  709. continue;
  710. }
  711. m2.reset();
  712. if (b.length() > 0) {
  713. b.append(", ");
  714. }
  715. b.append(requiredBundleName); // dep CNB
  716. while (m2.find()) {
  717. if (!m2.group(1).equals("bundle-version")) {
  718. continue;
  719. }
  720. String val = m2.group(2);
  721. if (val.matches("[0-9]+([.][0-9]+)*")) {
  722. // non-range dep occasionally used in OSGi; no exact equivalent in NB
  723. b.append(" > ").append(val);
  724. continue;
  725. }
  726. Matcher m3 = Pattern.compile("\\[([0-9]+)((?:[.][0-9]+)*),([0-9.]+)\\)").matcher(val);
  727. if (!m3.matches()) {
  728. throw new BuildException("Could not parse version range: " + val + " in " + whereFrom);
  729. }
  730. int major = Integer.parseInt(m3.group(1));
  731. String rest = m3.group(2);
  732. try {
  733. int max = Integer.parseInt(m3.group(3));
  734. if (major > 99) {
  735. b.append('/').append(major / 100);
  736. if (max > major + 100) {
  737. b.append('-').append(max / 100 - 1);
  738. }
  739. } else if (max > 100) {
  740. b.append("/0-").append(max / 100 - 1);
  741. }
  742. } catch (NumberFormatException x) {
  743. // never mind end boundary, does not match NB conventions
  744. }
  745. b.append(" > ").append(major % 100).append(rest);
  746. }
  747. }
  748. if(b.length() > 0) {
  749. manifest.setAttribute("OpenIDE-Module-Module-Dependencies", b.toString());
  750. }
  751. if(needsNetbinox) {
  752. manifest.setAttribute("OpenIDE-Module-Needs", "org.netbeans.Netbinox");
  753. }
  754. }
  755. String bundleCategory = loc(localized, attr, "Bundle-Category");
  756. if (bundleCategory != null) {
  757. manifest.setAttribute("OpenIDE-Module-Display-Category", bundleCategory);
  758. }
  759. String bundleDescription = loc(localized, attr, "Bundle-Description");
  760. if (bundleDescription != null) {
  761. manifest.setAttribute("OpenIDE-Module-Short-Description", bundleDescription);
  762. }
  763. // XXX anything else need to be set?
  764. return module;
  765. }
  766. private static String loc(Properties localized, Attributes attr, String key) {
  767. String val = attr.getValue(key);
  768. if (val == null) {
  769. return null;
  770. } else if (val.startsWith("%")) {
  771. return localized.getProperty(val.substring(1));
  772. } else {
  773. return val;
  774. }
  775. }
  776. }