/* * Definition of an ant type for jedit build environment. * :tabSize=2:indentSize=2:noTabs=true: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2012 Jarek Czekalski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.jedit.ant; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.Properties; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.DataType; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.PropertySet; import org.apache.tools.ant.types.resources.FileResource; import org.apache.tools.ant.types.resources.FileResourceIterator; /** Takes filesets as nested arguments: <code>fsSrc</code> and <code>fsExtras</code> and retrieves plugin information. Alternatively <code>jar</code> attibute may be given to parse the jar contents instead of filesets. */ public class PluginInfoType extends DataType implements Cloneable { // input parameters private FileSet fsSrc; private FileSet fsExtras; private String sJarIn; // plugin info private String sClass; private String sJar; private String sVersion; private String sJeditVersionShort; private String sJeditVersionFull; private DepList deps = new DepList(); /** Whether the info is already filled. */ private boolean bFilled; private Project p; //{{{ filled method /** Returns <code>PluginInfoType</code> object that is ok to operate on. That is: filled and being not a reference. Serves as a shorthand */ private PluginInfoType filled() { if (isReference()) { return getRef().filled(); } fill(); return this; } //}}} //{{{ get... methods public String getClassName() { return filled().sClass; } public String getJarName() { return filled().sJar; } public String getVersion() { return filled().sVersion; } public String getJeditVersionShort() { return filled().sJeditVersionShort; } public String getJeditVersionFull() { return filled().sJeditVersionFull; } public int getDepCount() { return filled().deps.size(); } public String getDepsString() { return filled().deps.toString(); } public Dep getDep(int i) { return filled().deps.get(i); } //}}} //{{{ getPluginClassName method /** Gets an <code>Iterator</code> over objects implementing <code>toString</code> and discovers the plugin name. The strings are treated as filenames and the filename ending with <code>Plugin.java</code> denotes the plugin name. This is the same as done in <code>org.gjt.sp.jedit.PluginJAR.generateCache()</code>. @param sBaseDir The base directory will be substracted from plugin filename to get only the part containing the package name. May be <code>null</code> @param it The <code>iterator</code> over <code>Object</code>s, which implement <code>toString()</code> @return <code>null</code> if not a plugin. */ public static String getPluginClassName(String sBaseDir, Iterator it) { String sPluginClass = null; while (it.hasNext()) { String sFile = it.next().toString(); sFile = sFile.replaceFirst("\\.class", ".java"); // There are some class ending with Plugin not being real plugins. // For example in XML plugin. // How to tell them? Donna. Inserting exceptions for them. // TODO: One coulde try all potential plugin classes until finds // the one specifing jedit version. Too difficult. if (sFile.endsWith("Plugin.java") && !sFile.endsWith("CssSideKickPlugin.java") && !sFile.endsWith("HtmlSideKickPlugin.java") && !sFile.endsWith("JavaScriptSideKickPlugin.java")) { sPluginClass = sFile; if (sBaseDir != null) { sPluginClass = sPluginClass.substring(sBaseDir.length()+1); } sPluginClass = sPluginClass.replaceFirst("\\.java$", ""); sPluginClass = sPluginClass.replaceAll("[/\\\\]", "."); break; } } return sPluginClass; } //}}} //{{{ fill() method public void fill() { if (isReference()) { getRef().fill(); return; } if (bFilled) { return; } Iterator itSrc, itExtras; String sBaseDir; ZipFile zip = null; p = getProject(); if (sJarIn != null) { // process jar file to get the info try { log("Property files will be read from jar: ", Project.MSG_VERBOSE); log(sJarIn, Project.MSG_VERBOSE); zip = new ZipFile(sJarIn); sBaseDir = null; itSrc = Collections.list(zip.entries()).iterator(); itExtras = itSrc; } catch (java.io.IOException ioe) { throw new BuildException(ioe); } } else { log("Property files will be read from fileset with parent directory: ", Project.MSG_VERBOSE); log(fsSrc.getDir().toString(), Project.MSG_VERBOSE); // filesets given as source for the info if (fsSrc == null) { throw new BuildException("fsSrc parameter not specified."); } if (fsExtras == null) { throw new BuildException("fsExtras parameter not specified."); } sBaseDir = fsSrc.getDir().toString(); itSrc = fsSrc.iterator(); itExtras = fsExtras.iterator(); } sClass = getPluginClassName(sBaseDir, itSrc); sJar = GetPluginJarNameTask.getJarName(sClass); // load all props files {{{ Properties props = new Properties(); while (itExtras.hasNext()) { Object entry = itExtras.next(); if (entry.toString().endsWith(".props")) { try { if (sJarIn != null) { ZipEntry zipEntry = (ZipEntry)entry; props.load(zip.getInputStream(zipEntry)); log("Properties read from jar entry: " + zipEntry.getName(), Project.MSG_VERBOSE); } else { FileResource fr = (FileResource)entry; props.load(fr.getInputStream()); log("Properties read from file: " + fr.getFile().toString(), Project.MSG_VERBOSE); } } catch (java.io.IOException e) { throw new BuildException(e); } } } // }}} sVersion = props.getProperty("plugin." + sClass + ".version", ""); parseProps(sClass, props); bFilled = true; } //}}} //{{{ parsePropse method /** Reads dependencies from properties. * See <code>PluginJAR.checkDependencies()</code> */ private void parseProps(String sPluginClass, Properties props) { int i, iPluginDep; i = 0; iPluginDep = 0; String sDepPropName = "plugin." + sPluginClass + ".depend."; String sDep; while((sDep = props.getProperty(sDepPropName + i)) != null) { String asDeps[] = sDep.split(" "); if (asDeps[0].equals("jedit")) { sJeditVersionFull = asDeps[1]; String v[] = sJeditVersionFull.split("\\."); sJeditVersionShort = v[0] + "." + v[1]; } if (asDeps[0].equals("optional")) { // ignore the optional keyword, treat as usual plugin dep asDeps = Arrays.copyOfRange(asDeps, 1, asDeps.length); } if (asDeps[0].equals("plugin")) { Dep dep = new Dep(); //print("" + iPluginDep + asDeps[1] + "-" + asDeps[2]); String sPref = "plugin.dep." + iPluginDep; dep.sClass = asDeps[1]; dep.sVersion = asDeps[2]; dep.sJar = GetPluginJarNameTask.getJarName(asDeps[1]); deps.add(dep); iPluginDep++; } i++; } } //}}} public void setJar(String s) //{{{ { sJarIn = s; checkAttr(); } //}}} public void addFsSrc(FileSet fs) //{{{ { fsSrc = fs; checkAttr(); } //}}} public void addFsExtras(FileSet fs) //{{{ { fsExtras = fs; checkAttr(); } //}}} private void checkAttr() //{{{ { p = getProject(); if (sJarIn != null && (fsSrc != null || fsExtras != null)) { throw new BuildException("jar and fsSrc/Extras " + "are mutually exclusive."); } } //}}} protected PluginInfoType getRef() { //{{{ return (PluginInfoType) getCheckedRef(PluginInfoType.class, "plugininfotype"); } //}}} public String toString() //{{{ { if (isReference()) { return getRef().toString(); } String s; if (!bFilled) { fill(); } s = "Plugin class name: " + sClass + ", jar name: " + sJar; s += ", version: " + sVersion + "\n"; s += "jedit version: " + sJeditVersionFull; s += ", dependencies count: " + deps.size() + "\n"; s += deps.toString(); return s; } //}}} public PluginInfoType clone() //{{{ { if (isReference()) { return getRef().clone(); } PluginInfoType piNew = null; try { piNew = (PluginInfoType)super.clone(); piNew.deps = this.deps.clone(); } catch (CloneNotSupportedException e) { throw new BuildException(e); } return piNew; } //}}} //{{{ setProjectProperties() method /** Stores plugin info in project properties. For details see {@link GetPluginInfoTask}. @param sPref Prefix added to the properties. May not be <code>null</code> */ public void setProjectProperties(String sPref) { if (isReference()) { getRef().setProjectProperties(sPref); return; } fill(); PropertySet ps = (PropertySet)p.createDataType("propertyset"); p.addReference(sPref + "plugin.props.set", ps); p.setProperty(sPref + "plugin.class.name", sClass); p.setProperty(sPref + "plugin.jar.name", sJar); p.setProperty(sPref + "plugin.jedit.version.full", sJeditVersionFull); p.setProperty(sPref + "plugin.jedit.version", sJeditVersionShort); p.setProperty(sPref + "plugin.dep.count", "" + deps.size()); ps.appendName(sPref + "plugin.class.name"); ps.appendName(sPref + "plugin.jar.name"); ps.appendName(sPref + "plugin.jedit.version.full"); ps.appendName(sPref + "plugin.jedit.version"); ps.appendName(sPref + "plugin.dep.count"); for (int i=0; i<deps.size(); i++) { Dep dep = deps.get(i); String sDepPref = sPref + "plugin.dep." + i; p.setProperty(sDepPref + ".class", dep.sClass); p.setProperty(sDepPref + ".version", dep.sVersion); p.setProperty(sDepPref + ".jar.name", dep.sJar); ps.appendRegex(sDepPref + "\\.*"); } } //}}} //{{{ joinDeps method /** Adds dependencies from <code>pi</code> to current dependencies. @return <code>true</code> if there were new dependencies. */ public boolean joinDeps(PluginInfoType pi2, StringBuilder sb) { boolean bNewDeps = false; for (int i2 = 0; i2 < pi2.getDepCount(); i2++) { int iState = 1; // 0 - same, 1 - new, 2 - update Dep dep2 = pi2.getDep(i2); for (Dep dep: this.filled().deps) { if (dep.getJarName().equals(dep2.getJarName())) { if (Misc.compareStrings(dep.getVersion(), dep2.getVersion(), true) < 0) { iState = 2; dep.sVersion = dep2.getVersion(); } else { iState = 0; } break; } } if (iState == 1) { this.filled().deps.add(dep2); } if (iState != 0) { sb.append(dep2.getJarName() + " " + dep2.getVersion() + " (" + (iState == 2 ? "update" : "new") + ")\n"); bNewDeps = true; } } return bNewDeps; } //}}} //{{{ Dep class /** Plugin dependency information */ public static class Dep { String sClass; String sVersion; String sJar; //{{{ get... methods public String getClassName() { return sClass; } public String getJarName() { return sJar; } public String getVersion() { return sVersion; } //}}} } //}}} //{{{ DepList class /** A list of dependencies being plugins */ public static class DepList implements Iterable<Dep>, Cloneable { private ArrayList<Dep> a = new ArrayList<Dep>(); // several methods imitating ArrayList {{{ public void add(Dep d) { a.add(d); } public Dep get(int i) { return a.get(i); } public int size() { return a.size(); } public Iterator<Dep> iterator() { return a.iterator(); } @SuppressWarnings (value="unchecked") public DepList clone() { DepList depsNew = new DepList(); depsNew.a = (ArrayList)this.a.clone(); return depsNew; } //}}} public String toString() { StringBuilder sb = new StringBuilder(); for (Dep dep: a) { sb.append("dependency: " + dep.sJar + " " + dep.sVersion + "\n"); } return sb.toString(); } } //}}} }