| package org.eclipse.swt.tools.internal; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.Map.*; |
| |
| 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 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 |
| "cocoa", |
| // "common", |
| // "common_j2me", |
| // "common_j2se", |
| "emulated", "emulated/bidi", // used by carbon, cocoa |
| "emulated/coolbar", // used by cocoa, gtk |
| "emulated/expand", // used by cocoa |
| "emulated/taskbar", // used by gtk |
| "emulated/tooltip", // used by cocoa (?!) |
| "glx", // used by gtk |
| "gtk" |
| }; |
| |
| System.out.println("==== Start Bashing ===="); |
| int totalBashed = 0; |
| for (String dir : targetSubdirs) { |
| for (String folder : folders) { |
| String targetSubdir = folder + "/" + dir; |
| File source = new File(workspaceDir + "/org.eclipse.swt/" |
| + folder + "/" + 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) { |
| for(String s : list) |
| System.out.println(label + ": " + s); |
| 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.JLS11); |
| 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 (Entry<String, String> entry: comments.entrySet()) { |
| String name = entry.getKey(); |
| if (entry.getValue().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; |
| } |
| } |