eclipseプラグインの依存関係

JFaceなどeclipseプラグインを使ったアプリを作ろうとすると必要なjarの依存関係を探すのがかなり面倒だ。
PDEを使えば、勝手に解決してくれるが、プラグインを作るわけでもないのにそこまでするのも面倒だということで、eclipseプラグインの依存関係にあるjarを探してくるコードを書いてみた。なんとなくバージョンチェックも付けてみたが意味はない。

public class PluginJarDependencies {

	public static void main(String[] args) throws IOException {
		File target = new File("org.eclipse.jface.text_3.4.2.r342_v20081119-0800.jar");
		RequirementSearch search = new RequirementSearch(Arrays.asList(new Requirement(target)));
		File pluginDir = new File("C:/eclipse/plugins");
		search.execute(pluginDir);
		for (File found : search.founds) {
			System.out.println(found);
		}
		for (Requirement r : search.notFounds) {
			StringBuilder sb = new StringBuilder();
			sb.append("Not found: " + r.name);
			if (r.max == r.min) {
				sb.append(" " + r.max);
			}
			else {
				sb.append(r.minInclude ? "[" : "(");
				sb.append(r.min);
				sb.append(",");
				sb.append(r.max);
				sb.append(r.maxInclude ? "]" : ")");
			}
			System.out.println(sb);
		}
	}
	
	static class RequirementSearch {
		List<Requirement> requires;
		Set<File> founds = new TreeSet<File>();
		List<Requirement> notFounds;
		RequirementSearch(List<Requirement> requires) {
			this.requires = new ArrayList<Requirement>(requires);
			notFounds = new ArrayList<Requirement>(requires);
		}
		void execute(File pluginDir) {
			int oldSize;
			do {
				oldSize = requires.size();
				for (File child : pluginDir.listFiles()) {
					checkFile(child);
				}
			}
			while (oldSize != requires.size());
		}
		void checkFile(File file) {
			if (!file.getName().endsWith(".jar")) {
				return;
			}
			String name = file.getName().split("_")[0];
			boolean require = false;
			for (Requirement req : notFounds) {
				if (req.name.equals(name)) {
					Version fileVer = new Version(file.getName().split("_", 2)[1]);
					int minCmp = fileVer.compareTo(req.min);
					int maxCmp = fileVer.compareTo(req.max);
					if ((req.minInclude ? minCmp >= 0 : minCmp > 0)
							&& (req.maxInclude ? maxCmp <= 0 : maxCmp < 0)) {
						requires.remove(req);
						notFounds.remove(req);
						founds.add(file);
						require = true;
						break;
					}
				}
			}
			if (require) {
				try {
					JarFile jar = new JarFile(file);
					Manifest manifest = jar.getManifest();
					jar.close();
					Attributes attributes = manifest.getMainAttributes();
					String requiresStr = attributes.getValue("Require-Bundle");
					if (requiresStr != null) {
						addRequires(requiresStr);
					}
				}
				catch (IOException e) {}
			}
		}
		static final Pattern COMMA_PATTERN = Pattern.compile("(([^\",]+|\"[^\"]+\")+),?");
		void addRequires(String requiresStr) {
			Matcher matcher = COMMA_PATTERN.matcher(requiresStr);
			while (matcher.find()) {
				String[] match = matcher.group(1).split(";");
				Requirement req = new Requirement(match[0], match[1]);
				if (!requires.contains(req)) {
					requires.add(req);
					notFounds.add(req);
				}
			}
		}
		
	}
	
	static class Requirement {
		String name;
		Version min;
		boolean minInclude;
		Version max;;
		boolean maxInclude;
		public Requirement(File file) {
			String[] split = file.getName().split("_", 2);
			name = split[0];
			minInclude = maxInclude = true;
			min = max = new Version(split[1]);
		}
		Requirement(String name, String version) {
			this.name = name;
			String[] vers = version.substring("bundle-version=".length() + 1, version.length() - 1).split(",");
			if (vers.length == 2) {
				if (vers[0].startsWith("[")) {
					minInclude = true;
				}
				min = new Version(vers[0].substring(1));
				if (vers[1].endsWith("]")) {
					maxInclude = true;
				}
				max = new Version(vers[1].substring(0, vers[1].length() - 1));
			}
			else if (vers.length == 1) {
				minInclude = true;
				maxInclude = true;
				min = max = new Version(version);
			}
			else {
				throw new RuntimeException("bundle-version parse failed");
			}
		}
	}
	
	static class Version implements Comparable<Version> {
		int major;
		int minor1;
		int minor2;
		String revision;
		Version(String version) {
			String[] vers = version.split("\\.");
			major = Integer.parseInt(vers[0]);
			minor1 = Integer.parseInt(vers[1]);
			minor2 = Integer.parseInt(vers[2]);
			if (vers.length > 3) {
				revision = vers[3];
			}
		}
		public int compareTo(Version o) {
			int cmp = major - o.major;
			if (cmp != 0) {
				return cmp;
			}
			cmp = minor1 - o.minor1;
			if (cmp != 0) {
				return cmp;
			}
			return minor2 - o.minor2;
		}
		@Override
		public String toString() {
			return major + "." + minor1 + "." + minor2
					+ (revision != null ? "." + revision : "");
		}
	}
	
}