blob: d0c97d18843b7f8b640494b77779ae80eb40e07f [file] [log] [blame]
package org.eclipse.swt.tools.internal;
import java.io.*;
import java.util.*;
import org.eclipse.jdt.core.dom.*;
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-2015), 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<String> fBashed;
List<String> fUnchanged;
List<String> fSkipped;
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
// "carbon", // we are no longer maintaining carbon
"cocoa",
// "common",
// "common_j2me",
// "common_j2se",
"emulated", "emulated/bidi", // used by carbon, cocoa
"emulated/coolbar", // used by carbon, cocoa, gtk
"emulated/expand", // used by carbon, cocoa
"emulated/taskbar", // used by carbon, gtk
"emulated/tooltip", // used by cocoa (?!)
"glx", // used by gtk
"gtk",
"mozilla", // used by carbon, cocoa, gtk, win32
// "qt", // folder should be deleted
};
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<String> 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<String> list, String targetSubdir) {
int count = list.size();
System.out.println(label + " " + count
+ ((count == 1) ? " file" : " files") + " in " + targetSubdir
+ ((count > 0) ? ":" : "."));
if (count > 0) {
Iterator<String> 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);
}
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();
} 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.JLS8);
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<String, String> comments = new HashMap<>();
sourceUnit.accept(new ASTVisitor() {
String prefix = "";
@Override
public boolean visit(Block node) {
return false;
}
@Override
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;
}
@Override
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<SingleVariableDeclaration> iterator = node.parameters().iterator(); iterator.hasNext();) {
SingleVariableDeclaration param = 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;
}
@Override
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<Edit> edits = new ArrayList<>();
targetUnit.accept(new ASTVisitor() {
String prefix = "";
@Override
public boolean visit(Block node) {
return false;
}
@Override
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 = comments.get(key);
if (newComment != null) {
comments.remove(key);
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;
}
@Override
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<SingleVariableDeclaration> iterator = node.parameters().iterator(); iterator.hasNext();) {
SingleVariableDeclaration param = iterator.next();
key += param.getType().toString();
}
String newComment = comments.get(key);
if (newComment != null) {
comments.remove(key);
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;
}
@Override
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 = comments.get(key);
if (newComment != null) {
comments.remove(key);
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 = edits.get(i);
try {
targetDocument.replace(edit.start, edit.length, edit.text);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/* Rudimentary API consistency checker.
* This assumes that:
* a) the sourceSubdir (typically win32) API is correct
* b) all sourceSubdir API classes, methods and fields do have a comment
* c) names that are in the filter list are never API,
* or they are old API that is defined in the super on some platforms
*/
if (comments.size() > 0) {
String [] filter = new String [] {
"Color.win32_newDeviceint",
"Cursor.win32_newDeviceint",
"Device.hPalette",
"Font.win32_newDevicelong",
"FontData.data",
"FontData.win32_newLOGFONTfloat",
"FontMetrics.handle",
"FontMetrics.win32_newTEXTMETRIC",
"GC.win32_newlongGCData",
"GC.win32_newDrawableGCData",
"Image.win32_newDeviceintlong",
"Pattern.handle",
"Region.win32_newDeviceint",
"Control.handle",
"Display.getSystemFont",
"Display.msg",
"Menu.handle",
"Shell.win32_newDisplaylong",
"Accessible.internal_WM_GETOBJECTlonglong",
"TransferData.result",
"TransferData.stgmedium",
"TransferData.pIDataObject",
"TransferData.formatetc",
"Printer.handle",
"Printer.checkDevice",
"TableDragSourceEffect.dragFinishedDragSourceEvent",
"TableDragSourceEffect.dragStartDragSourceEvent",
"TableDropTargetEffect.dragOverDropTargetEvent",
"TableDropTargetEffect.dragEnterDropTargetEvent",
"TableDropTargetEffect.dragLeaveDropTargetEvent",
"Transfer.validateObject",
"TransferData.result",
"TransferData.stgmedium",
"TransferData.pIDataObject",
"TransferData.formatetc",
"TreeDragSourceEffect.dragFinishedDragSourceEvent",
"TreeDragSourceEffect.dragStartDragSourceEvent",
"TreeDropTargetEffect.dragLeaveDropTargetEvent",
"TreeDropTargetEffect.dragEnterDropTargetEvent",
"TreeDropTargetEffect.dragOverDropTargetEvent",
"Printer.createDeviceData",
"Printer.internal_dispose_GClongGCData",
"Printer.release",
"Printer.destroy",
"Image.handle",
"Display.getClientArea",
"TreeItem.handle",
};
for (Iterator<String> iterator = comments.keySet().iterator(); iterator.hasNext();) {
String name = iterator.next();
if (comments.get(name).length() > 0){
int i = 0;
for (i = 0; i < filter.length; i++) {
if (name.equals(filter[i])) break;
}
if (i >= filter.length) {
System.err.println("***No target for " + name);
}
}
}
}
String newContents = targetDocument.get();
if (!targetContents.equals(newContents)) {
if (makeDirectory(out.getParentFile())) {
writeFile(newContents.toCharArray(), out);
fBashed.add(target.toString());
} else {
System.out.println("*** Could not create " + out.getParent());
}
} else {
fUnchanged.add(target.toString());
}
}
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<String> getBashed() {
return fBashed;
}
List<String> getUnchanged() {
return fUnchanged;
}
List<String> getSkipped() {
return fSkipped;
}
}