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; } }