Bug 409353 - JavadocBasher should only change comments
diff --git a/bundles/org.eclipse.swt.tools/.classpath b/bundles/org.eclipse.swt.tools/.classpath
index 22ab26b..9c2b44d 100644
--- a/bundles/org.eclipse.swt.tools/.classpath
+++ b/bundles/org.eclipse.swt.tools/.classpath
@@ -8,6 +8,7 @@
 	<classpathentry kind="src" path="Mozilla Generation"/>
 	<classpathentry kind="src" path="NativeStats"/>
 	<classpathentry kind="src" path="Icon Exe"/>
+	<classpathentry kind="src" path="JavadocBasher"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/bundles/org.eclipse.swt.tools/JavadocBasher/org/eclipse/swt/tools/internal/JavadocBasher.java b/bundles/org.eclipse.swt.tools/JavadocBasher/org/eclipse/swt/tools/internal/JavadocBasher.java
new file mode 100644
index 0000000..e564ee1
--- /dev/null
+++ b/bundles/org.eclipse.swt.tools/JavadocBasher/org/eclipse/swt/tools/internal/JavadocBasher.java
@@ -0,0 +1,455 @@
+package org.eclipse.swt.tools.internal;

+

+import java.io.*;

+import java.util.*;

+

+import org.eclipse.jdt.core.dom.*;

+import org.eclipse.jdt.core.dom.rewrite.*;

+import org.eclipse.jface.text.*;

+

+/**

+ * Bashes the javadoc from one source tree into another. Only produces new

+ * source files for compilation units that have changed.

+ * 

+ * How to use: 1) make sure you have the latest org.eclipse.swt (master branch)

+ * in your workspace, and that you have no outstanding org.eclipse.swt changes

+ * 2) create a Bugzilla bug called

+ * "Do the annual javadoc/copyright bash for x.x" 3) make a version (tag) of the

+ * org.eclipse.swt project before you bash here is a sample tag name:

+ * BEFORE_JAVADOC_BASH_FOR_43RC3 use the Bugzilla bug for the tag comment 4)

+ * modify the code in main, below, so that 'workspaceDir' and 'outputDir' point

+ * to the (git) directory that contains org.eclipse.swt in your workspace,

+ * typically C:/git/eclipse.platform.swt/bundles (prior to 3.8/4.2, these

+ * pointed to the workspace directory) 5) make sure 'sourceSubdir' (usually

+ * win32), 'targetSubdirs' (all others), and 'folders' are correct (note: there

+ * are typically a few new targetSubdirs and folders every year... although

+ * nothing new for 4.3) 6) run JavadocBasher (for a more verbose output, set

+ * fVerbose to true) 7) refresh (F5) the org.eclipse.swt project inside eclipse

+ * 8) search for *** in console output to see results of API consistency

+ * checking 9) synchronize, carefully reviewing every change. Watch out for: -

+ * duplicated comments - // comments that have been removed (if they appear

+ * before a javadoc comment) 10) use the Bugzilla bug as the commit comment for

+ * javadoc and copyright bash commits 11) make a version of the org.eclipse.swt

+ * project after bashing (use tag name AFTER_...)

+ * 

+ * 12) Copyright bash (tag before and after): NOTE: JavadocBasher does not fix

+ * copyrights. Use the "Fix Copyrights" tool in org.eclipse.releng.tools for

+ * that (always fix copyrights after bash). Use Help->Install New Software... to

+ * install "Releng Tools" from the "Eclipse Project Updates" site (for release -

+ * 1). Select org.eclipse.swt project and choose "Fix Copyrights" from the

+ * context menu. See http://wiki.eclipse.org/Development_Resources/

+ * How_to_Use_Eclipse_Copyright_Tool for more info. NOTE: The copyright tool

+ * takes about 45 minutes to run (for SWT). NOTE 2: Check console for possible

+ * errors/warnings, refresh (F5), synchronize, and browse all changes. Use

+ * keyboard (Ctrl+.) for next diff instead of mouse (keyboard is faster because

+ * there are fewer focus changes). Only use git History view as needed - if it

+ * is open and linked with editor, it gets bogged down and lags behind. NOTE 3:

+ * SWT anomalies that confuse the tool: - Some ns*.h files in

+ * Mozilla/common/library do not contain the word "copyright" so the tool tries

+ * to add one - don't keep it (the text is fine as-is). - Other ns*.h files in

+ * Mozilla/common/library have a copyright line that should not be updated

+ * (Initial Developer) - don't keep the change suggested by the tool (the text

+ * is fine as-is). - The ns*.java and some other *.java files in

+ * internal/mozilla have 2 copyright lines and the tool tries to change the 1st

+ * - don't keep the 1st change (Netscape 1998-1999), but update the 2nd (IBM)

+ * manually.

+ * 

+ * NOTE: JavadocBasher now does a fairly good job of checking API consistency.

+ * We used to use org.eclipse.swt.diff for API consistency checking, but it was

+ * difficult to maintain.

+ */

+public class JavadocBasher {

+	static final boolean fVerbose = false; // set to true for verbose output

+	List fBashed;

+	List fUnchanged;

+	List fSkipped;

+	private ASTRewrite rewriter;

+

+	public JavadocBasher() {

+		fBashed = new ArrayList();

+		fUnchanged = new ArrayList();

+		fSkipped = new ArrayList();

+	}

+	

+	public static class Edit {

+		int start, length;

+		String text;

+

+		public Edit(int start, int length, String text) {

+			this.start = start;

+			this.length = length;

+			this.text = text;

+		}

+	}

+

+	public static void main(String[] args) {

+		String workspaceDir = ".."; // use forward slashes, no final slash

+		String outputDir = ".."; // can point to another directory for debugging

+		String[] folders = new String[] { // commented folders do not need to be

+				// bashed

+				"Eclipse SWT", "Eclipse SWT Accessibility",

+				"Eclipse SWT AWT",

+				"Eclipse SWT Browser",

+				// "Eclipse SWT Custom Widgets",

+				"Eclipse SWT Drag and Drop", "Eclipse SWT Effects",

+				"Eclipse SWT Mozilla",

+				// "Eclipse SWT OLE Win32",

+				"Eclipse SWT OpenGL",

+				// "Eclipse SWT PI",

+				"Eclipse SWT Printing", "Eclipse SWT Program",

+				"Eclipse SWT Theme", "Eclipse SWT WebKit", };

+		String sourceSubdir = "win32";

+		String[] targetSubdirs = new String[] { "cairo", // used by gtk, motif

+				// "carbon", // we are no longer maintaining carbon

+				"cde", // used by gtk, motif

+				"cocoa",

+				// "common",

+				// "common_j2me",

+				// "common_j2se",

+				"emulated", "emulated/bidi", // used by carbon, cocoa, dojo,

+												// flex, gtk, motif, photon, wpf

+				"emulated/coolbar", // used by carbon, cocoa, flex, gtk, motif,

+									// photon

+				// "emulated/datetime", // only used by motif, photon, wpf

+				"emulated/expand", // used by carbon, cocoa, flex, motif, photon

+				// "emulated/graphics", // only used by photon

+				"emulated/ime", // used by dojo, flex, motif, photon, wpf

+				// "emulated/tabfolder", // only used by motif

+				"emulated/taskbar", // used by carbon, gtk, motif, photon, wpf

+				// "emulated/textlayout", // only used by photon

+				"emulated/tooltip", // used by cocoa (?!), motif, photon, wpf

+				// "emulated/tray", // only used by carbon_j2me, motif, photon

+				// "emulated/treetable", // only used by motif, photon

+				// "forms", // only used by wpf

+				"gnome", // used by gtk, motif

+				"glx", // used by gtk, motif

+				"gtk",

+				// "motif", // we are no longer maintaining motif

+				// "motif_gtk", // only used by motif

+				"mozilla", // used by carbon, cocoa, gtk, motif, win32

+		// "photon", // we are no longer maintaining photon

+		// "qt", // folder should be deleted

+		// "wpf", // we are no longer maintaining wpf

+		// "wpf_win32", // only used by wpf

+		};

+

+		System.out.println("==== Start Bashing ====");

+		int totalBashed = 0;

+		for (int t = 0; t < targetSubdirs.length; t++) {

+			for (int f = 0; f < folders.length; f++) {

+				String targetSubdir = folders[f] + "/" + targetSubdirs[t];

+				File source = new File(workspaceDir + "/org.eclipse.swt/"

+						+ folders[f] + "/" + sourceSubdir);

+				File target = new File(workspaceDir + "/org.eclipse.swt/"

+						+ targetSubdir);

+				File out = new File(outputDir + "/org.eclipse.swt/"

+						+ targetSubdir);

+				JavadocBasher basher = new JavadocBasher();

+				System.out.println("\n==== Start Bashing " + targetSubdir);

+				basher.bashJavaSourceTree(source, target, out);

+				List bashedList = basher.getBashed();

+				basher.status("Bashed", bashedList, targetSubdir);

+				if (bashedList.size() > 0) {

+					totalBashed += bashedList.size();

+					if (fVerbose)

+						basher.status("Didn't change", basher.getUnchanged(),

+								targetSubdir);

+					basher.status("Skipped", basher.getSkipped(), targetSubdir);

+				}

+				System.out.println("==== Done Bashing " + targetSubdir);

+			}

+		}

+		System.out.println("\n==== Done Bashing (Bashed " + totalBashed

+				+ " files in total) - Be sure to Refresh (F5) project(s) ====");

+	}

+

+	void status(String label, List list, String targetSubdir) {

+		int count = list.size();

+		System.out.println(label + " " + count

+				+ ((count == 1) ? " file" : " files") + " in " + targetSubdir

+				+ ((count > 0) ? ":" : "."));

+		if (count > 0) {

+			Iterator i = list.iterator();

+			while (i.hasNext())

+				System.out.println(label + ": " + i.next());

+			System.out.println();

+		}

+	}

+

+	char[] readFile(File file) {

+		try {

+			Reader in = new FileReader(file);

+			CharArrayWriter storage = new CharArrayWriter();

+			char[] chars = new char[8192];

+			int read = in.read(chars);

+			while (read > 0) {

+				storage.write(chars, 0, read);

+				storage.flush();

+				read = in.read(chars);

+			}

+			in.close();

+			return storage.toCharArray();

+		} catch (IOException ioe) {

+			System.out.println("*** Could not read " + file);

+		}

+		return null;

+	}

+

+	void writeFile(char[] contents, File file) {

+		try {

+			Writer out = new FileWriter(file);

+			out.write(contents);

+			out.flush();

+			out.close();

+		} catch (IOException ioe) {

+			System.out.println("*** Could not write to " + file);

+			if (fVerbose) {

+				System.out.println("<dump filename=\"" + file + "\">");

+				System.out.println(contents);

+				System.out.println("</dump>");

+			}

+		}

+	}

+

+	void bashJavaSourceTree(File sourceDir, File targetDir, File outDir) {

+		if (fVerbose)

+			System.out.println("Reading source javadoc from " + sourceDir);

+		if (!sourceDir.exists()) {

+			System.out.println("Source: " + sourceDir + " was missing");

+			return;

+		}

+		if (!targetDir.exists()) {

+			System.out.println("Target: " + targetDir + " was missing");

+			return;

+		}

+

+		String[] list = sourceDir.list();

+		if (list != null) {

+			int count = list.length;

+			for (int i = 0; i < count; i++) {

+				String filename = list[i];

+				if (filename.equals("CVS") || filename.equals("internal")

+						|| filename.equals("library"))

+					continue;

+				File source = new File(sourceDir, filename);

+				File target = new File(targetDir, filename);

+				File out = new File(outDir, filename);

+				if (source.exists() && target.exists()) {

+					if (source.isDirectory()) {

+						if (target.isDirectory()) {

+							bashJavaSourceTree(source, target, out);

+						} else {

+							System.out.println("*** " + target

+									+ " should have been a directory.");

+						}

+					} else {

+						if (filename.toLowerCase().endsWith(".java")) {

+							bashFile(source, target, out);

+						} else {

+							fSkipped.add(source + " (not a java file)");

+						}

+					}

+				} else {

+					if (source.exists()) {

+						fSkipped.add(target + " (does not exist)");

+					} else {

+						fSkipped.add(source + " (does not exist)");

+					}

+				}

+			}

+		}

+	}

+

+

+	void bashFile(final File source, final File target, File out) {

+		char[] contents = readFile(source);

+		if (contents == null) return;

+		ASTParser parser = ASTParser.newParser(AST.JLS4);

+		final Document sourceDocument = new Document(new String(contents));

+		parser.setSource(contents);

+		CompilationUnit sourceUnit = (CompilationUnit)parser.createAST(null);

+

+		contents = readFile(target);

+		if (contents == null) return;

+		String targetContents = new String(contents);

+		final Document targetDocument = new Document(targetContents);

+		parser.setSource(contents);

+		CompilationUnit targetUnit = (CompilationUnit)parser.createAST(null);

+

+		final HashMap comments = new HashMap();

+		sourceUnit.accept(new ASTVisitor() {

+			String prefix = "";

+			public boolean visit(Block node) {

+				return false;

+			}

+			public boolean visit(VariableDeclarationFragment node) {

+				FieldDeclaration field = (FieldDeclaration)node.getParent();

+				int mods = field.getModifiers();

+				if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {

+					Javadoc javadoc = field.getJavadoc();

+					if (field.fragments().size() > 1 && javadoc != null) {

+						System.err.println("Field declaration with multiple variables is not supported. -> " + source + " " + node.getName().getFullyQualifiedName());

+					}

+					try {

+						String key = prefix + "." + node.getName().getFullyQualifiedName();

+						comments.put(key, javadoc != null ? sourceDocument.get(javadoc.getStartPosition(), getJavadocLength(sourceDocument, javadoc)) : "");

+					} catch (BadLocationException e) {}

+					return true;

+				}

+				return false;

+			}

+			public boolean visit(MethodDeclaration node) {

+				int mods = node.getModifiers();

+				if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {

+					Javadoc javadoc = node.getJavadoc();

+					try {

+						String key = prefix + "." + node.getName().getFullyQualifiedName();

+						for (Iterator iterator = node.parameters().iterator(); iterator.hasNext();) {

+							SingleVariableDeclaration param = (SingleVariableDeclaration) iterator.next();

+							key += param.getType().toString();

+						}

+						comments.put(key, javadoc != null ? sourceDocument.get(javadoc.getStartPosition(), getJavadocLength(sourceDocument, javadoc)) : "");

+					} catch (BadLocationException e) {}

+					return true;

+				}

+				return false;

+			}

+			public boolean visit(TypeDeclaration node) {

+				int mods = node.getModifiers();

+				if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {

+					Javadoc javadoc = node.getJavadoc();

+					try {

+						String key = prefix + "." + node.getName().getFullyQualifiedName();

+						comments.put(key, javadoc != null ? sourceDocument.get(javadoc.getStartPosition(), getJavadocLength(sourceDocument, javadoc)) : "");

+					} catch (BadLocationException e) {}

+					prefix = node.getName().getFullyQualifiedName();

+					return true;

+				}

+				return false;

+			}

+		});

+

+

+		final List edits = new ArrayList();

+		targetUnit.accept(new ASTVisitor() {

+			String prefix = "";

+			public boolean visit(Block node) {

+				return false;

+			}

+			public boolean visit(VariableDeclarationFragment node) {

+				FieldDeclaration field = (FieldDeclaration)node.getParent();

+				int mods = field.getModifiers();

+				if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {

+					Javadoc javadoc = field.getJavadoc();

+					if (field.fragments().size() > 1 && javadoc != null) {

+						System.err.println("Field declaration with multiple variables is not supported. -> " + target + " " + node.getName().getFullyQualifiedName());

+					}

+					String key = prefix + "." + node.getName().getFullyQualifiedName();

+					String newComment = (String)comments.get(key);

+					if (newComment != null) {

+						if (javadoc != null) {

+							edits.add(new Edit(javadoc.getStartPosition(), getJavadocLength(targetDocument, javadoc), newComment));

+						} else {

+							edits.add(new Edit(field.getStartPosition(), 0, newComment));

+						}

+					}

+					return true;

+				}

+				return false;

+			}

+			public boolean visit(MethodDeclaration node) {

+				int mods = node.getModifiers();

+				if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {

+					Javadoc javadoc = node.getJavadoc();

+					String key = prefix + "." + node.getName().getFullyQualifiedName();

+					for (Iterator iterator = node.parameters().iterator(); iterator.hasNext();) {

+						SingleVariableDeclaration param = (SingleVariableDeclaration) iterator.next();

+						key += param.getType().toString();

+					}

+					String newComment = (String)comments.get(key);

+					if (newComment != null) {

+						if (javadoc != null) {

+							edits.add(new Edit(javadoc.getStartPosition(), getJavadocLength(targetDocument, javadoc), newComment));

+						} else {

+							edits.add(new Edit(node.getStartPosition(), 0, newComment));

+						}

+					}

+					return true;

+				}

+				return false;

+			}

+			public boolean visit(TypeDeclaration node) {

+				int mods = node.getModifiers();

+				if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {

+					Javadoc javadoc = node.getJavadoc();

+					String key = prefix + "." + node.getName().getFullyQualifiedName();

+					String newComment = (String)comments.get(key);

+					if (newComment != null) {

+						if (javadoc != null) {

+							edits.add(new Edit(javadoc.getStartPosition(), getJavadocLength(targetDocument, javadoc), newComment));

+						} else {

+							edits.add(new Edit(node.getStartPosition(), 0, newComment));

+						}

+					}

+					prefix = node.getName().getFullyQualifiedName();

+					return true;

+				}

+				return false;

+			}

+		});

+

+		for (int i = edits.size() - 1; i >=0 ; i--) {

+			Edit edit = (Edit)edits.get(i);

+			try {

+				targetDocument.replace(edit.start, edit.length, edit.text);

+			} catch (BadLocationException e) {

+				e.printStackTrace();

+			}

+		}

+		

+		

+		String newContents = targetDocument.get();

+		if (!targetContents.equals(newContents)) {

+			if (makeDirectory(out.getParentFile())) {

+				writeFile(newContents.toCharArray(), out);

+				fBashed.add(target);

+			} else {

+				System.out.println("*** Could not create " + out.getParent());

+			}

+		} else {

+			fUnchanged.add(target);

+		}

+	}

+

+	int getJavadocLength(Document sourceDocument, Javadoc javadoc) {

+		return skipWhitespace(sourceDocument, javadoc.getStartPosition() + javadoc.getLength()) - javadoc.getStartPosition();

+	}

+	

+	int skipWhitespace(Document doc, int offset) {

+		try {

+			while (Character.isWhitespace(doc.getChar(offset))){

+				offset++;

+			}

+		} catch (BadLocationException e) {

+		}

+		return offset;

+	}

+

+	boolean makeDirectory(File directory) {

+		if (directory.exists())

+			return true;

+		return directory.mkdirs();

+	}

+

+	List getBashed() {

+		return fBashed;

+	}

+

+	List getUnchanged() {

+		return fUnchanged;

+	}

+

+	List getSkipped() {

+		return fSkipped;

+	}

+}

diff --git a/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF b/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF
index b93cca3..9f5d721 100644
--- a/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF
@@ -15,4 +15,6 @@
  org.eclipse.core.resources;bundle-version="3.4.0",
  org.eclipse.jdt.core;bundle-version="3.4.0",
  org.eclipse.ui;bundle-version="3.4.0",
- org.eclipse.ui.editors;bundle-version="3.4.0"
+ org.eclipse.ui.editors;bundle-version="3.4.0",
+ org.eclipse.jface.text;bundle-version="3.8.200"
+Import-Package: org.eclipse.text.edits