001: /*****************************************************************************
002: * Java Plug-in Framework (JPF)
003: * Copyright (C) 2006-2007 Dmitry Olshansky
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *****************************************************************************/package org.java.plugin.tools.ant;
019:
020: import java.io.BufferedInputStream;
021: import java.io.BufferedOutputStream;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.OutputStream;
028: import java.net.URL;
029: import java.text.DateFormat;
030: import java.text.ParseException;
031: import java.text.SimpleDateFormat;
032: import java.util.Date;
033: import java.util.HashMap;
034: import java.util.Locale;
035: import java.util.Map;
036: import java.util.Properties;
037: import javax.xml.parsers.DocumentBuilder;
038: import javax.xml.parsers.DocumentBuilderFactory;
039: import javax.xml.parsers.ParserConfigurationException;
040: import javax.xml.transform.OutputKeys;
041: import javax.xml.transform.Transformer;
042: import javax.xml.transform.TransformerConfigurationException;
043: import javax.xml.transform.TransformerFactory;
044: import javax.xml.transform.TransformerFactoryConfigurationError;
045: import javax.xml.transform.dom.DOMSource;
046: import javax.xml.transform.stream.StreamResult;
047: import org.apache.tools.ant.BuildException;
048: import org.java.plugin.registry.PluginDescriptor;
049: import org.java.plugin.registry.PluginFragment;
050: import org.java.plugin.registry.Version;
051: import org.java.plugin.util.IoUtil;
052: import org.w3c.dom.Document;
053: import org.w3c.dom.Element;
054: import org.w3c.dom.NodeList;
055:
056: /**
057: * <p>
058: * This class can upgrade all version and plugin-version tags in all plugin
059: * manifest files, to the latest version specified in a text file (in Java
060: * properties format). This class also handles updating the build number in the
061: * specified file.
062: * <p>
063: * This class will only upgrade 'version' and 'plugin-version' tags that already
064: * exist in the manifest files, so it won't add any to the manifest files.
065: * <p>
066: * This class tracks plug-in modification timestamp's and keep them together
067: * with versions info in the given text file. The actual plug-in version will be
068: * upgraded only if plug-in timestamp changes.
069: *
070: * @author Jonathan Giles
071: * @author Dmitry Olshansky
072: */
073: public class VersionUpdateTask extends BaseJpfTask {
074: private File versionsFile;
075: private boolean alterReferences = false;
076: private boolean timestampVersion = false;
077:
078: /**
079: * @param value <code>true</code> if version references should be upgraded
080: */
081: public final void setAlterReferences(final boolean value) {
082: alterReferences = value;
083: }
084:
085: /**
086: * @param value file where to store versioning related info
087: */
088: public void setVersionsFile(final File value) {
089: versionsFile = value;
090: }
091:
092: /**
093: * @param value if <code>true</code>, the plug-in timestamp will be included
094: * into version {@link Version#getName() name} attribute
095: */
096: public void setTimestampVersion(final boolean value) {
097: timestampVersion = value;
098: }
099:
100: /**
101: * @see org.apache.tools.ant.Task#execute()
102: */
103: @Override
104: public void execute() throws BuildException {
105: if (versionsFile == null) {
106: throw new BuildException(
107: "versionsfile attribute must be set!", //$NON-NLS-1$
108: getLocation());
109: }
110: initRegistry(true);
111: // reading contents of versions file
112: if (getVerbose()) {
113: log("Loading versions file " + versionsFile); //$NON-NLS-1$
114: }
115: Properties versions = new Properties();
116: if (versionsFile.exists()) {
117: try {
118: InputStream in = new BufferedInputStream(
119: new FileInputStream(versionsFile));
120: try {
121: versions.load(in);
122: } finally {
123: in.close();
124: }
125: } catch (IOException ioe) {
126: throw new BuildException(
127: "failed reading versions file " //$NON-NLS-1$
128: + versionsFile, ioe, getLocation());
129: }
130: } else {
131: log("Versions file " + versionsFile //$NON-NLS-1$
132: + " not found, new one will be created."); //$NON-NLS-1$
133: }
134: Map<String, PluginInfo> infos = new HashMap<String, PluginInfo>();
135: // collecting manifests
136: for (PluginDescriptor descr : getRegistry()
137: .getPluginDescriptors()) {
138: File manifestFile = IoUtil.url2file(descr.getLocation());
139: if (manifestFile == null) {
140: throw new BuildException(
141: "non-local plug-in manifest URL given " //$NON-NLS-1$
142: + descr.getLocation());
143: }
144: URL homeUrl = getPathResolver().resolvePath(descr, "/"); //$NON-NLS-1$
145: File homeFile = IoUtil.url2file(homeUrl);
146: if (homeFile == null) {
147: throw new BuildException(
148: "non-local plug-in home URL given " //$NON-NLS-1$
149: + homeUrl);
150: }
151: try {
152: infos.put(descr.getId(), new PluginInfo(descr.getId(),
153: versions, manifestFile, homeFile, descr
154: .getVersion()));
155: } catch (ParseException pe) {
156: throw new BuildException(
157: "failed parsing versions data " //$NON-NLS-1$
158: + " for manifest " + manifestFile, pe, getLocation()); //$NON-NLS-1$
159: }
160: if (getVerbose()) {
161: log("Collected manifest file " + manifestFile); //$NON-NLS-1$
162: }
163: }
164: for (PluginFragment descr : getRegistry().getPluginFragments()) {
165: File manifestFile = IoUtil.url2file(descr.getLocation());
166: if (manifestFile == null) {
167: throw new BuildException(
168: "non-local plug-in fragment manifest URL given " //$NON-NLS-1$
169: + descr.getLocation());
170: }
171: URL homeUrl = getPathResolver().resolvePath(descr, "/"); //$NON-NLS-1$
172: File homeFile = IoUtil.url2file(homeUrl);
173: if (homeFile == null) {
174: throw new BuildException(
175: "non-local plug-in fragment home URL given " //$NON-NLS-1$
176: + homeUrl);
177: }
178: try {
179: infos.put(descr.getId(), new PluginInfo(descr.getId(),
180: versions, manifestFile, homeFile, descr
181: .getVersion()));
182: } catch (ParseException pe) {
183: throw new BuildException(
184: "failed parsing versions data " //$NON-NLS-1$
185: + " for manifest " + manifestFile, pe, getLocation()); //$NON-NLS-1$
186: }
187: if (getVerbose()) {
188: log("Populated manifest file " + manifestFile); //$NON-NLS-1$
189: }
190: }
191: // processing manifest versions
192: DocumentBuilderFactory builderFactory = DocumentBuilderFactory
193: .newInstance();
194: builderFactory.setValidating(false);
195: DocumentBuilder builder;
196: try {
197: builder = builderFactory.newDocumentBuilder();
198: } catch (ParserConfigurationException pce) {
199: throw new BuildException(
200: "can't obtain XML document builder ", //$NON-NLS-1$
201: pce, getLocation());
202: }
203: if (getVerbose()) {
204: log("Processing versions"); //$NON-NLS-1$
205: }
206: for (PluginInfo info : infos.values()) {
207: try {
208: info
209: .processVersion(versions, builder,
210: timestampVersion);
211: } catch (Exception e) {
212: throw new BuildException("failed processing manifest " //$NON-NLS-1$
213: + info.getManifestFile(), e, getLocation());
214: }
215: }
216: if (alterReferences) {
217: if (getVerbose()) {
218: log("Processing version references"); //$NON-NLS-1$
219: }
220: for (PluginInfo info : infos.values()) {
221: try {
222: info.processVersionReferences(versions, builder);
223: } catch (Exception e) {
224: throw new BuildException(
225: "failed processing manifest " //$NON-NLS-1$
226: + info.getManifestFile(), e,
227: getLocation());
228: }
229: }
230: }
231: Transformer transformer;
232: try {
233: transformer = TransformerFactory.newInstance()
234: .newTransformer();
235: } catch (TransformerConfigurationException tce) {
236: throw new BuildException(
237: "can't obtain XML document transformer ", //$NON-NLS-1$
238: tce, getLocation());
239: } catch (TransformerFactoryConfigurationError tfce) {
240: throw new BuildException(
241: "can't obtain XML document transformer factory ", //$NON-NLS-1$
242: tfce, getLocation());
243: }
244: transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
245: transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
246: transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
247: "no"); //$NON-NLS-1$
248: transformer.setOutputProperty(OutputKeys.STANDALONE, "no"); //$NON-NLS-1$
249: transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
250: "-//JPF//Java Plug-in Manifest 1.0"); //$NON-NLS-1$
251: transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
252: "http://jpf.sourceforge.net/plugin_1_0.dtd"); //$NON-NLS-1$
253: if (getVerbose()) {
254: log("Saving manifests"); //$NON-NLS-1$
255: }
256: for (PluginInfo info : infos.values()) {
257: try {
258: info.save(versions, transformer);
259: } catch (Exception e) {
260: throw new BuildException("failed saving manifest " //$NON-NLS-1$
261: + info.getManifestFile(), e, getLocation());
262: }
263: }
264: // saving versions
265: if (getVerbose()) {
266: log("Saving versions file " + versionsFile); //$NON-NLS-1$
267: }
268: try {
269: OutputStream out = new BufferedOutputStream(
270: new FileOutputStream(versionsFile));
271: try {
272: versions.store(out,
273: "Plug-in and plug-in fragment versions file"); //$NON-NLS-1$
274: } finally {
275: out.close();
276: }
277: } catch (IOException ioe) {
278: throw new BuildException("failed writing versions file " //$NON-NLS-1$
279: + versionsFile, ioe, getLocation());
280: }
281: log("Plug-in versions update done"); //$NON-NLS-1$
282: }
283:
284: static final class PluginInfo {
285: private static Date getTimestamp(final File file,
286: final Date parentTimestamp) {
287: Date result;
288: if (parentTimestamp == null) {
289: result = new Date(file.lastModified());
290: } else {
291: result = new Date(Math.max(parentTimestamp.getTime(),
292: file.lastModified()));
293: }
294: File[] files = file.listFiles();
295: if (files != null) {
296: for (int i = 0; i < files.length; i++) {
297: result = getTimestamp(files[i], result);
298: }
299: }
300: return result;
301: }
302:
303: private static Version upgradeVersion(final Version ver) {
304: if (ver == null) {
305: return new Version(0, 0, 1, null);
306: }
307: return new Version(ver.getMajor(), ver.getMinor(), ver
308: .getBuild() + 1, ver.getName());
309: }
310:
311: private static Version upgradeVersion(final Version ver,
312: final Date timestamp) {
313: DateFormat dtf = new SimpleDateFormat(
314: "yyyyMMddHHmmss", Locale.ENGLISH); //$NON-NLS-1$
315: if (ver == null) {
316: return new Version(0, 0, 1, dtf.format(timestamp));
317: }
318: return new Version(ver.getMajor(), ver.getMinor(), ver
319: .getBuild() + 1, dtf.format(timestamp));
320: }
321:
322: private final String id;
323: private final File manifestFile;
324: private final Version oldVersion;
325: private final Date oldTimestamp;
326: private final DateFormat dtf = new SimpleDateFormat(
327: "yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); //$NON-NLS-1$
328: private Document doc;
329: private Version newVersion;
330: private Date currentTimestamp;
331:
332: PluginInfo(final String anId, final Properties versions,
333: final File aMnifestFile, final File homeFile,
334: final Version currentVersion) throws ParseException {
335: id = anId;
336: String versionStr = versions.getProperty(anId, null);
337: String timestampStr = versions.getProperty(anId
338: + ".timestamp", null); //$NON-NLS-1$
339: if (versionStr != null) {
340: oldVersion = Version.parse(versionStr);
341: } else {
342: oldVersion = currentVersion;
343: }
344: if (timestampStr != null) {
345: oldTimestamp = dtf.parse(timestampStr);
346: } else {
347: oldTimestamp = null;
348: }
349: manifestFile = aMnifestFile;
350: currentTimestamp = getTimestamp(homeFile, null);
351: versions.setProperty(id + ".timestamp", //$NON-NLS-1$
352: dtf.format(currentTimestamp));
353: }
354:
355: File getManifestFile() {
356: return manifestFile;
357: }
358:
359: void processVersion(final Properties versions,
360: final DocumentBuilder builder,
361: final boolean timestampVersion) throws Exception {
362: if (IoUtil.compareFileDates(oldTimestamp, currentTimestamp)) {
363: newVersion = oldVersion;
364: } else {
365: newVersion = !timestampVersion ? upgradeVersion(oldVersion)
366: : upgradeVersion(oldVersion, currentTimestamp);
367: }
368: versions.setProperty(id, newVersion.toString());
369: if (oldVersion.equals(newVersion)) {
370: return;
371: }
372: doc = builder.parse(manifestFile);
373: Element root = doc.getDocumentElement();
374: if (root.hasAttribute("version")) { //$NON-NLS-1$
375: root.setAttribute("version", newVersion.toString()); //$NON-NLS-1$
376: }
377: }
378:
379: void processVersionReferences(final Properties versions,
380: final DocumentBuilder builder) throws Exception {
381: if (doc == null) {
382: doc = builder.parse(manifestFile);
383: }
384: Element root = doc.getDocumentElement();
385: if ("plugin-fragment".equals(root.getNodeName())) { //$NON-NLS-1$
386: processVersionReference(versions, root);
387: }
388: NodeList imports = root.getElementsByTagName("import"); //$NON-NLS-1$
389: for (int i = 0; i < imports.getLength(); i++) {
390: processVersionReference(versions, (Element) imports
391: .item(i));
392: }
393: }
394:
395: private void processVersionReference(final Properties versions,
396: final Element elm) {
397: if (!elm.hasAttribute("plugin-version")) { //$NON-NLS-1$
398: return;
399: }
400: String version = versions.getProperty(elm
401: .getAttribute("plugin-id"), id); //$NON-NLS-1$
402: if (version != null) {
403: elm.setAttribute("plugin-version", version); //$NON-NLS-1$
404: }
405: }
406:
407: void save(final Properties versions,
408: final Transformer transformer) throws Exception {
409: if (doc == null) {
410: return;
411: }
412: long modified = manifestFile.lastModified();
413: transformer.transform(new DOMSource(doc), new StreamResult(
414: manifestFile));
415: manifestFile.setLastModified(modified);
416: }
417: }
418: }
|