/* * [TidyLeadComments.java] * * Summary: creates standard comments to go ahead of package line. * * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2009-04-15 initial version * 1.1 2009-04-30 generate html paragraph breaks in the class comment. * avoid inserting additional blank lines. * check that entities ok in Javadoc. * 1.2 2009-05-30 sort (@)see lines just after version */ package com.mindprod.repair; import com.mindprod.commandline.CommandLine; import com.mindprod.common18.Build; import com.mindprod.common18.EIO; import com.mindprod.common18.ST; import com.mindprod.entities.DeEntifyStrings; import com.mindprod.entities.EntifyStrings; import com.mindprod.fastcat.FastCat; import com.mindprod.filter.AllButSVNDirectoriesFilter; import com.mindprod.filter.ExtensionListFilter; import com.mindprod.hunkio.HunkIO; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.lang.System.*; /* TODO: collect stray @tags and put them at them bottom insert
lines on short lines in comments. tidy c-style comments too warn about tags and entities in package comments. tidy other javaDoc reorder @tags check entities in other class comment. check entities in ordinary Javadoc comments. Have JDisplay render JavaDoc more like HTML. see if some entities are not necessary if control encoding of Java source. Space out the Version history with blank lines between versions, like use.txt */ /** * creates standard comments to go ahead of package line. *

* Will trim lines of lead stars and blanks before reformatting, thus not good for columnar comments. * Strips out [p/], [p], [/p] on a line by itself, leaving a blank line. *

* I have been working away tidying my Java source code. The tools I have include: *

* TidyLeadComments: this program here, a very complex program that extracts data from the current mess of comments, * resolves conflicts from multiple sources * and reformats a lead package and class comment in rigidly standard format. * * @author Roedy Green, Canadian Mind Products * @version 1.2 2009-05-30 sort (@)see lines just after version. * Unfortunately, this code is peculiar to the sorts of mess I have coded. * Funduc Script bloutcomments to deal with [p/] , excess blank (*) lines. * InsertVersion to insert same (@)version in a group of files. * StripRearranger to remove Rearranger-generated tools. * IntelliJ Rearranges/Reformat -- aligns and tides Javadoc. * However, I have no tool yet to ensure: * 1. Javadoc has one blank line on front and none behind. * 2. /(*) comment align, fill in missing stars and make them all align. * Do you know of tools for these last two tasks that don't insist on totally reformatting my code? * see http://mindprod.com/jgloss/javadoc.html * @since 2009-04-15 */ public class TidyLeadComments { // declarations public static final String USAGE = "\nUsage: TidyLeadComments [proof/tidy] [-s] [list of dirs/files]"; /** * true if want additional debugging output */ private static final boolean DEBUGGING = false; /** * true generates extra blank lines for a more spacious output. Unfortunately IntelliJ reformat will remove them * soon. */ private static final boolean SPACIOUS = false; /** * true means use hoky code to repair damaged comments. Not for general application. * Normally false!! */ private static final boolean USE_KLUDGE = false; /** * Regex fragment to look for star slash that ends a comment */ private static final String A_COMMENT_END = "\\*/"; /** * Regex fragment to find YYYY-MM-DD */ private static final String A_DASH = "[/\\-]"; /* * Regex fragment to find YYYY-MM-DD */ private static final String A_DD = "[0-3]\\d"; /** * Regex fragment to look for slash star start that starts a Javadoc comment */ private static final String A_JAVADOC_START = "/\\*\\*"; /* * Regex fragment to find YYYY-MM-DD */ private static final String A_MM = "\\d{2}"; /** * regex fragment to look for some spaces */ private static final String A_SPACES = "\\s++"; /** * regex fragment that matches an arbitrary string of chars, possibly empty */ private static final String A_STUFF = ".*?"; /** * regex to find a version number eg. 1.5 for program or JDK versions */ private static final String A_VERSION = "\\d++\\.\\d"; /** * Regex fragment to find YYYY-MM-DD */ private static final String A_YYYY = "[12][90]\\d{2}"; /** * Regex fragment to find YYYY-MM-DD */ private static final String A_YYYY_MM__DD = A_YYYY + A_DASH + A_MM + A_DASH + A_DD; /** * Regex fragment to find YYYY-MM-DD, YYYY-MM or just plain YYYY */ private static final String A_YYYY_MM__DD_PARTIAL = A_YYYY + "(?:" + A_DASH + A_MM + "(?:" + A_DASH + A_DD + ")?)?"; /** * blank line in comment with(*) on left */ private static final String BLANK_LINE = " *\n"; /** * name of IDE used to develop the code */ private static final String IDE_USED = "JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/"; /** * distribution licence governing this source code */ private static final String LICENCE_TYPE = "This software may be copied and used freely for any purpose but " + "military."; /** * url where licence is explained */ private static final String LICENCE_URL = "http://mindprod.com/contact/nonmil.html"; /** * indent summary in log */ private static final String SUMMARY_INDENT = ST.spaces( 70 ); /** * current calendar year */ private static final String THIS_COPYRIGHT_YEAR = Integer.toString( Build.THIS_COPYRIGHT_YEAR ); /** * website that owns the Copyright */ private static final String WEBSITE = "http://mindprod.com"; /** * list of official authors. Must have different first names. First line is default. * Recognised by first name. If authors are added with same first name, will have to modify logic. * As you want them tidied, upper and lower case. * This is just the author; copyright has more tacked on head and tail. */ private static final String[] AUTHORS = { "Roedy Green, Canadian Mind Products", "Nic Fulton, nic.fulton@reuters.com, Reuters Ltd, http://www.reuters.com. Roedy Green, " + "Canadian Mind Products (polishing)", "Hiroki Akimoto, akimoto@pfu.co.jp", "Eric van Bezooijen, eric@logrus.berkeley.edu", "Fahd Shariff, Roedy Green, Canadian Mind Products (polishing)" }; /** * complete lines, not phrases, to remove from the comments, in lower case. Initialised later. */ private static final HashSet NOISE_LINES; /** * used to find the author. */ private static final Pattern AT_AUTHOR_FINDER; /** * used to find the copyright year, or the @since year. */ private static final Pattern AT_SINCE_FINDER; /** * take the version line apart e.g. (@)version 1.0 2009-04-15, must have @ * finds thing like: * (@)version 1.0 - 1998-08-18 - redo with FilenameFilter logic * (@)version 1.0 1998-08-18 * but not * (@)version 1.0 * or * random stuff */ private static final Pattern AT_VERSION_FINDER; /** * used to find the copyright year, or the @since year. Does not extract @SINCE or version 1.0. If no copyright * we look elsewhere for it. */ private static final Pattern COPYRIGHT_FINDER; /** * used to find the first slash star comment in the source code, only count if occurs just before package */ private static final Pattern PACKAGE_COMMENT_FINDER; /** * used to split the comment into lines */ private static final Pattern SPLITTER = Pattern.compile( "\\r\\n|\\n" ); /** * take the version line apart. Needs aux check to make sure line does not start with @ * finds thing like: * version 1.0 - 1998-08-18 - redo with FilenameFilter logic * 1.0 1998-08-18 * but not * 1.0 * or * random stuff */ private static final Pattern VERSION_FINDER; /** * at-version item about the current version, null if none */ private static VersionItem atVersion; /** * author of the program */ private static String author; /** * what is left over in class comment after we extract fields */ private static String[] classDregs; /** * name of class we are tidying */ private static String classname; /** * class comment split into lines */ private static String[] clines; /** * first copyright year e.g. 2000 of program we are tidying. */ private static String copyright; /** * fully qualified filename of the java source we are currently tidying */ private static String fullName; /** * JDK required e.g. "JDK 1.8+" */ private static String jdkRequired; /** * proofing log of new comments, one .new file per Java source file. */ private static PrintWriter newPrw; /** * entire contents of slash star star Javadoc comment */ @SuppressWarnings( { "FieldCanBeLocal" } ) private static String oldClassComment; /** * entire contents of slash star package comment */ @SuppressWarnings( { "FieldCanBeLocal" } ) private static String oldPackageComment; /** * proofing log of old comments, one .old file per Java source file. */ private static PrintWriter oldPrw; /** * what is left over in package comment after we extract fields */ private static String[] packageDregs; /** * package comment split into lines */ private static String[] plines; /** * count of how many problems we detected in current source program */ private static int problems = 0; /** * true if we are proofreading, false if actually changing the source code. */ private static boolean proof; /** * array of any at-see lines */ private static String[] sees; /** * date that program was first written e.g. yyyy-mm-dd or yyyy */ private static String since; /** * one line summary of what program does */ private static String summary; /** * count of how many files had problems */ private static int totalFilesWithProblems = 0; /** * count of how many files hand warnings */ private static int totalFilesWithWarnings = 0; /** * count of how many problems we detected in in all source we look at */ private static int totalProblems = 0; /** * count of how many warnings we detected in in all source we look at */ private static int totalWarnings = 0; /** * version items about each version, [0] if none. */ private static VersionItem[] versionHistory; /** * count of how many problems we detected in current source program */ private static int warnings = 0; private static int startClassComment; private static int endClassComment; // /declarations static { VERSION_FINDER = Pattern.compile( "(?:[Vv]ersion )?" + "[ \\-]*" + "(" + A_VERSION + ")" + "[ ,\\-:]+" + "(" + A_YYYY_MM__DD + ")" + "[ ,\\-\\.:]*" + "(" + A_STUFF + ")" ); AT_AUTHOR_FINDER = Pattern.compile( "@[Aa]uthor" + A_SPACES + "(" + A_STUFF + ")" ); AT_SINCE_FINDER = Pattern.compile( "@[Ss]ince" + A_SPACES + "(" + A_YYYY_MM__DD_PARTIAL + ")" ); AT_VERSION_FINDER = Pattern.compile( "(?:@[Vv]ersion)" + "[ \\-]+" + "(" + A_VERSION + ")" + "[ ,\\-:]+" + "(" + A_YYYY_MM__DD + ")" + "[ ,\\-\\.:]*(.*)" /* case-sensitive */ ); COPYRIGHT_FINDER = Pattern.compile( "\\([Cc]\\)" + "[ \\-,]*" + "(" + A_YYYY + ")" ); PACKAGE_COMMENT_FINDER = Pattern.compile( "/\\*" + "(" + "[^\\*]" + A_STUFF + ")" + A_COMMENT_END, Pattern.DOTALL ); NOISE_LINES = new HashSet<>( Arrays.asList( "#101 - 2536 wark street", // leave lower case "but military", "but military.", "canada v8t 4g8", "canadian mind products", "freeware that may be copied and used freely for any purpose", WEBSITE, "mailto:roedyg@mindprod.com", "may be copied and used freely for any purpose but military.", "may be freely distributed for any purpose but military.", "roedy g at mindprod dotcom", "roedy green", "tel: (250) 361-9093", "v8t 4g8 tel: (250) 361-9093", "v8t 4g8", "victoria, bc canada v8t 4g8", "victoria, bc canada", "victoria, bc", "created with: canadian mind products comparatorcutter." ) ); } // methods /** * extract the was is left over after everything else taken. No double blank lines. * * @param lines lines of the old comment */ private static void checkDregs( final String[] lines ) { for ( String line : lines ) { line = line.trim(); // we don't trim the original if ( line.length() > 0 ) { if ( line.contains( "@author" ) ) { oops( "unable to extract (or possibly duplicate) @author from [" + line + "]" ); } if ( line.contains( "@since" ) ) { oops( "unable to extract (or possibly duplicate) @since yyyy-[-mm-dd] from [" + line + "]" ); } if ( line.contains( "@version" ) ) { oops( "unable to extract (or possibly duplicate) @version 9.9 yyyy-mm-dd from [" + line + "]" ); } } } } // method /** * ensure HTML entities are properly coded. * * @param lines Lines to tidy , partly entity encoded, partly plain. */ private static void checkEntityCoding( String[] lines ) { for ( String line : lines ) { final String noTagsSomeEntities = DeEntifyStrings.stripHTMLTags( line ); final String noTagsNoEntities = DeEntifyStrings.deEntifyHTML( noTagsSomeEntities, ( char ) 160 ); final String noTagsRestoredEntities = EntifyStrings.entifyHTML( noTagsNoEntities ); if ( !noTagsRestoredEntities.equals( noTagsSomeEntities ) ) { warning( "entities not coded properly" ); out.println( " original:" + line ); out.println( " poss fix: " + noTagsRestoredEntities ); final String convertedTags = EntifyStrings.entifyHTML( line ); if ( !convertedTags.equals( noTagsRestoredEntities ) ) { out.println( " poss fix: " + convertedTags ); } } } } // method /** * close the log files */ private static void closeLogs() { oldPrw.close(); newPrw.close(); } // method /** * If data is missing, try to deduce it from other fields. */ private static void deduceMissingData() { // if versionHistory missing, try to deduce it from (@)version if ( versionHistory.length == 0 && atVersion != null ) { versionHistory = new VersionItem[] { atVersion }; } // if at-version missing, try to deduce it from versionHistory if ( atVersion == null && versionHistory.length >= 1 ) { atVersion = versionHistory[ versionHistory.length - 1 ]; } // if copyright missing , try to deduce it from other data. if ( copyright.length() == 0 && since.length() >= 4 ) { copyright = since.substring( 0, 4 ); } if ( copyright.length() == 0 && versionHistory.length >= 1 ) { copyright = versionHistory[ 0 ].getYYYYMMDD().substring( 0, 4 ); } if ( copyright.length() == 0 ) { oops( "missing Copyright presumed " + THIS_COPYRIGHT_YEAR ); copyright = THIS_COPYRIGHT_YEAR; } // if since missing, try to deduce it from other data. if ( since.length() == 0 && versionHistory.length >= 1 && versionHistory[ 0 ].getVersionTimesTen() == 10 ) { since = versionHistory[ 0 ].getYYYYMMDD(); } if ( since.length() == 0 && copyright.length() == 4 ) { since = copyright; } if ( atVersion == null && since.length() == 10 ) { atVersion = new VersionItem( "1.0", since ); atVersion.addVersionDescContinuation( "initial version" ); } } // method /** * debugging tool to dump out an array of text lines * * @param label label of what this is. * @param lines array of lines to dump */ private static void dumpLines( String label, String[] lines ) { out.println(); out.println( "<><><> " + label + " <><><>" ); for ( String line : lines ) { out.println( " [" + line + "]" ); } } // method /** * emit summary info on the file we are processing */ private static void emitFullSummary() { if ( !proof && problems == 0 && warnings == 0 ) { output( fullName, true, false, false ); emitMiniSummary(); } } // method /** * emit abbreviates summary info on the file we are processing */ private static void emitMiniSummary() { output( SUMMARY_INDENT + "summary: [" + summary + "]", true, false, false ); } // method /** * find at-version, multiple history entries from either the package or class comment * * @return VersionItems it found */ private static VersionItem extractAtVersion() { // version history is supposed to be in package comment, but take it from either package or class comment. VersionItem atVersion = extractAtVersion( clines ); if ( atVersion == null ) { atVersion = extractAtVersion( plines ); } // don't insist that an (@)version history exist. return atVersion; } // method /** * extract the (@)Version lines not to be confused with the Version History lines. * * @param lines lines of the old comment * * @return VersionItems it found */ private static VersionItem extractAtVersion( final String[] lines ) { boolean take = false; VersionItem vi = null; ArrayList keep = new ArrayList<>( lines.length ); for ( int i = 0; i < lines.length; i++ ) { String line = lines[ i ]; // find version history, and keep all non-blank lines thereafter. if ( line.length() != 0 && !line.equals( "

" ) ) { // canonical form is: // (@)version _1.5 2006-03-05 - reformat with IntelliJ, add Javadoc // tidy up the line // might look like: // (@)Version 1.5 2006-03-05 reformat with IntelliJ, add Javadoc // 1.5 2006-03-05 - reformat with IntelliJ, add Javadoc // 1.5 2006-03-05reformat with IntelliJ, add Javadoc // 1.5 2006-03-05. reformat with IntelliJ, add Javadoc // 1.5 2006-03-05, reformat with IntelliJ, add Javadoc // continuation. final Matcher m = AT_VERSION_FINDER.matcher( line ); if ( m.matches() ) { final String ver = m.group( 1 ); final String yyyymmdd = m.group( 2 ); final String changeDescription = tidyVersionChangeDescription( m.group( 3 ) ); vi = new VersionItem( ver, yyyymmdd ); vi.addVersionDescContinuation( changeDescription ); take = true; lines[ i ] = ""; keep.add( vi ); } else { if ( take && line.length() > 0 ) { if ( line.charAt( 0 ) == '@' ) { // hit end of version info break; } if ( vi == null ) { oops( "malformed @version" ); return null; } lines[ i ] = ""; vi.addVersionDescContinuation( tidyVersionChangeDescription( line ) ); } } } } // end for VersionItem[] keepa = keep.toArray( new VersionItem[ keep.size() ] ); Arrays.sort( keepa ); verifyHistoryOrder( keepa ); if ( keepa.length > 1 ) { oops( "multiple versions described in @version" ); return keepa[ keepa.length - 1 ]; } else if ( keepa.length == 1 ) { return keepa[ 0 ]; } else { return null; } } // method /** * extract the author from either source. * * @return author in canonical form without (@)author */ private static String extractAuthor() { String packageAuthor = extractAuthor( plines ); String classAuthor = extractAuthor( clines ); if ( packageAuthor.length() > 0 ) { if ( classAuthor.length() > 0 ) { if ( packageAuthor.equals( classAuthor ) ) { return packageAuthor; } else { oops( "Author inconsistent in package: [" + packageAuthor + "] and class: [" + classAuthor + "]" ); return packageAuthor; } } else { return packageAuthor; } } else if ( classAuthor.length() > 0 ) { return classAuthor; } else { // no author, use default. return AUTHORS[ 0 ]; } } // /method /** * extract the author * * @param lines lines of the old comment, trimmed * * @return author in canonical form without @author */ private static String extractAuthor( final String[] lines ) { for ( int i = 0; i < lines.length; i++ ) { final Matcher m = AT_AUTHOR_FINDER.matcher( lines[ i ] ); if ( m.find() ) { // wipe out that line so we will not bother with it ever again lines[ i ] = ""; return tidyAuthor( m.group( 1 ) ); } } return ""; } // /method /** * extract the original copyright year, from (c), not from at-since or Version 1.0. * * @return copyright earliest copyright year e.g. "2008" */ private static String extractCopyright() { String packageCopyright = extractCopyright( plines ); String classCopyright = extractCopyright( clines ); // check for package duplicates final String packageCopyright2 = extractCopyright( plines ); if ( packageCopyright2.length() > 0 && !packageCopyright2.equals( packageCopyright ) ) { oops( "copyright inconsistent: [" + packageCopyright + "] and [" + packageCopyright2 + "]" ); } // check for class duplicates final String classCopyright2 = extractCopyright( clines ); if ( classCopyright2.length() > 0 && !classCopyright2.equals( classCopyright ) ) { oops( "copyright inconsistent: [" + classCopyright + "] and [" + classCopyright2 + "]" ); } if ( packageCopyright.length() > 0 ) { if ( classCopyright.length() > 0 ) { if ( packageCopyright.equals( classCopyright ) ) { return packageCopyright; } else { oops( "copyright inconsistent: [" + packageCopyright + "] and [" + classCopyright + "]" ); return packageCopyright; } } else { return packageCopyright; } } else if ( classCopyright.length() > 0 ) { return classCopyright; } else { return ""; } } // /method /** * extract the original copyright year, from (c), not from at-since or Version 1.0. * * @param lines lines of the old comment * * @return copyright earliest copyright year e.g. "2008" */ private static String extractCopyright( final String[] lines ) { for ( int i = 0; i < lines.length; i++ ) { final Matcher m = COPYRIGHT_FINDER.matcher( lines[ i ] ); if ( m.find() ) { // wipe out that line so we will not bother with it ever again lines[ i ] = ""; return m.group( 1 ); } } return ""; } // /method /** * extract the any at-see lines line * * @return at-see lines for plines and clines. */ private static String[] extractSees() { ArrayList keep = new ArrayList<>( 11 ); for ( int i = 0; i < plines.length; i++ ) { String pline = plines[ i ]; // look for remaining at-see lines if ( pline.length() != 0 && pline.toLowerCase().startsWith( "@see" ) ) { if ( !keep.contains( pline ) ) { // wipe out so we will never process it again. plines[ i ] = ""; keep.add( pline ); } } } for ( int i = 0; i < clines.length; i++ ) { String cline = clines[ i ]; // look for remaining at-see lines if ( cline.length() != 0 && cline.toLowerCase().startsWith( "@see" ) ) { if ( !keep.contains( cline ) ) { // wipe out so we will never process it again. clines[ i ] = ""; keep.add( cline ); } } } String[] sees = keep.toArray( new String[ keep.size() ] ); Arrays.sort( sees, new SeeComparator() ); return sees; } /** * extract the at-since from either source * * @return since at-since e.g. 2009 on 2009-04-17 */ private static String extractSince() { String packageSince = extractSince( plines ); String classSince = extractSince( clines ); if ( packageSince.length() > 0 ) { if ( classSince.length() > 0 ) { if ( packageSince.equals( classSince ) ) { return classSince; } else { oops( "Since inconsistent in package: [" + packageSince + "] and class: [" + classSince + "]" ); return packageSince; } } else { return packageSince; } } else if ( classSince.length() > 0 ) { return classSince; } else { // no Since. return ""; } } /** * extract the at-since * * @param lines lines of the old comment, trimmed * * @return since at-since e.g. 2009 on 2009-04-17 */ private static String extractSince( final String[] lines ) { for ( int i = 0; i < lines.length; i++ ) { final Matcher m = AT_SINCE_FINDER.matcher( lines[ i ] ); if ( m.find() ) { // wipe out that line so we will not bother with it ever again lines[ i ] = ""; return m.group( 1 ); } } return ""; } /** * extract the summary line from either source * * @return summary line */ private static String extractSummary() { String packageSummary = extractSummary( plines ); String classSummary = extractSummary( clines ); if ( packageSummary.length() > 0 ) { if ( classSummary.length() > 0 ) { if ( packageSummary.equals( classSummary ) ) { return packageSummary; } else { oops( "Summary inconsistent\n package: [" + packageSummary + "]\n class: [" + classSummary + "]" ); return packageSummary; } } else { return packageSummary; } } else if ( classSummary.length() > 0 ) { return classSummary; } else { return ""; } } /** * extract the summary line * * @param lines lines of the old comment, trimmed * * @return summary line */ private static String extractSummary( final String[] lines ) { for ( int i = 0; i < lines.length; i++ ) { String line = lines[ i ]; // look first for existing summary line if ( line.length() != 0 && line.toLowerCase().startsWith( "summary:" ) ) { // wipe out so we will never process it again. lines[ i ] = ""; return tidySummary( line.substring( "summary:".length() ) ); } } // find first line, not the @(#) or copyright or @version etc. for ( int i = 0; i < lines.length; i++ ) { String line = lines[ i ]; // find first non-blank line. final String lc = line.toLowerCase(); if ( line.length() != 0 && !line.startsWith( "@" ) && !lc.startsWith( "copyright" ) && !lc.startsWith( "version" ) ) { if ( line.length() < 15 ) { return ""; } else { lines[ i ] = ""; return tidySummary( line ); } } } return ""; } /** * find version history, multiple history entries from either the package or class comment * * @return VersionItems it found */ private static VersionItem[] extractVersionHistory() { // version history is supposed to be in package comment, but take it from either package or class comment. VersionItem[] versionHistory = extractVersionHistory( plines ); if ( versionHistory.length == 0 ) { versionHistory = extractVersionHistory( clines ); } // don't insist that a version history exist. return versionHistory; } /** * extract the VersionHistory lines not to be confused with the at-version lines. * * @param lines lines of the old comment * * @return VersionItems it found */ private static VersionItem[] extractVersionHistory( final String[] lines ) { boolean take = false; VersionItem vi = null; ArrayList keep = new ArrayList<>( lines.length ); for ( int i = 0; i < lines.length; i++ ) { // find version history, and keep all non-blank lines thereafter. String line = lines[ i ]; if ( line.length() > 0 ) { if ( line.toLowerCase().startsWith( "version history" ) ) { take = true; lines[ i ] = ""; } else { // canonical form is: // _1.5 2006-03-05 - reformat with IntelliJ, add Javadoc // tidy up the line // might look like: // version 1.5 2006-03-05 reformat with IntelliJ, add Javadoc // 1.5 2006-03-05 - reformat with IntelliJ, add Javadoc // 1.5 2006-03-05reformat with IntelliJ, add Javadoc // 1.5 2006-03-05. reformat with IntelliJ, add Javadoc // 1.5 2006-03-05, reformat with IntelliJ, add Javadoc // continuation. final Matcher m = VERSION_FINDER.matcher( line ); // avoid a false hit on (@)version assert line.length() > 0 : "oops: unexpected empty line extracting version history."; if ( line.charAt( 0 ) != '@' && m.matches() ) { final String ver = m.group( 1 ); final String yyyymmdd = m.group( 2 ); String firstVersionDesc = tidyVersionChangeDescription( m.group( 3 ) ); // possibly empty firstVersionDesc. vi = new VersionItem( ver, yyyymmdd ); vi.addVersionDescContinuation( firstVersionDesc ); take = true; lines[ i ] = ""; keep.add( vi ); } else { if ( take && line.length() > 0 ) { if ( line.charAt( 0 ) == '@' ) { // hit end of version info break; } if ( vi == null ) { oops( "malformed version history near [" + line + "]" ); return new VersionItem[ 0 ]; } // continuation line vi.addVersionDescContinuation( tidyVersionChangeDescription( line ) ); lines[ i ] = ""; } } } } } // end for VersionItem[] keepa = keep.toArray( new VersionItem[ keep.size() ] ); Arrays.sort( keepa ); verifyHistoryOrder( keepa ); return keepa; } /** * find the first Javadoc class slash star star comment in a string representing the entire source file * just in front of class xxxx * * @param big string contents of entire source file * * @return first contents of comment excluding delimiters. */ private static String findClassComment( final String classname, final String big ) { startClassComment = -1; Pattern pattern = Pattern.compile( "\\s+(class|enum|interface)\\s+" + classname + "[\\<\\s]" ); final Matcher m = pattern.matcher( big ); if ( m.find() ) { int startClass = m.start(); /* scan backwards for end of javadoc comment */ endClassComment = big.lastIndexOf( "*/", startClass ); if ( endClassComment < 0 ) { return ""; } /* scan backwards for start of javadoc comment */ startClassComment = big.lastIndexOf( "/**", endClassComment ); if ( startClassComment < 0 ) { return ""; } /* we want just the contents, not the delimiters */ final String result = big.substring( startClassComment + 3, endClassComment ); // we found start of a comment before this comment. if ( result.contains( "/*" ) ) { startClassComment = -1; return ""; } else { // adjust marker so it includes the delimiters endClassComment += 2; return result; } } else { return ""; } } /** * find the first package slash star comment in a string representing the entire source file * * @param big string contents of entire source file * * @return first slash star comment is the file excluding the slash star and star slash delimiters. */ private static String findPackageComment( final String big ) { final Matcher m = PACKAGE_COMMENT_FINDER.matcher( big ); if ( m.find() ) { final int startSlashStar = m.start( 1 ); final int startSlashStarStar = big.indexOf( "/**" ); if ( 0 <= startSlashStarStar && startSlashStarStar < startSlashStar ) { // the slash star we found came after the first JavaDoc comment. It can't be the package comment. return ""; } else { return m.group( 1 ); } } else { return ""; } } /** * FormatPadSites a comment to go ahead of the package statement. * * @return tidy new class comment. */ private static String formatClassComment() { final FastCat sb = new FastCat( 167 + classDregs.length * 3 + sees.length * 3 ); sb.append( "/**\n" ); if ( summary.length() > 0 ) { // no blank line sb.append( " * " ); sb.append( summary ); sb.append( ".\n" ); } if ( classDregs.length > 0 ) { sb.append( " *

\n" ); for ( final String line : classDregs ) { sb.append( " * " ); sb.append( line ); sb.append( '\n' ); } } if ( author.length() > 0 ) { sb.append( BLANK_LINE ); sb.append( " * " ); sb.append( "@author " ); sb.append( author ); sb.append( '\n' ); } if ( atVersion != null ) { if ( SPACIOUS ) { sb.append( BLANK_LINE ); } sb.append( atVersion.formatForClass() ); } if ( sees.length > 0 ) { if ( SPACIOUS ) { sb.append( BLANK_LINE ); } for ( final String line : sees ) { sb.append( " * " ); sb.append( line ); sb.append( '\n' ); } } if ( since.length() > 0 ) { if ( SPACIOUS ) { sb.append( BLANK_LINE ); } sb.append( " * " ); sb.append( "@since " ); sb.append( since ); sb.append( '\n' ); } // no blank line sb.append( " */\n" ); return sb.toString(); } /** * FormatPadSites a comment to go ahead of the package statement. * * @return slash star comment to insert prior to package including delimiters. */ private static String formatPackageComment() { final FastCat sb = new FastCat( 37 + versionHistory.length ); sb.append( "/*\n" ); sb.append( " * [" );// used to be @(#) sb.append( classname ); sb.append( ".java]\n" ); sb.append( " *\n" ); sb.append( " * Summary: " ); sb.append( summary ); sb.append( ".\n" ); sb.append( " *\n" ); sb.append( " * Copyright: (c) " ); sb.append( copyright ); if ( !copyright.equals( THIS_COPYRIGHT_YEAR ) ) { sb.append( "-" ); sb.append( THIS_COPYRIGHT_YEAR ); } // We presume the author is the copyright holder and all programs are associated with the same website. // This would not be true in general. sb.append( " " ); sb.append( author ); sb.append( ", " ); sb.append( WEBSITE ); sb.append( '\n' ); sb.append( " *\n" ); sb.append( " * Licence: " ); sb.append( LICENCE_TYPE ); sb.append( "\n" ); sb.append( " * " ); sb.append( LICENCE_URL ); sb.append( "\n" ); sb.append( " *\n" ); sb.append( " * Requires: " ); sb.append( jdkRequired ); sb.append( '\n' ); sb.append( " *\n" ); sb.append( " * Created with: " ); sb.append( IDE_USED ); sb.append( "\n" ); sb.append( " *\n" ); sb.append( " * Version History:\n" ); for ( final VersionItem versionItem : versionHistory ) { sb.append( versionItem.formatForPackage() ); } sb.append( " */\n" ); return sb.toString(); } /** * get JDK required to compile/execute this class * * @param file name of Java source file. Must be in E:\intellij tree * * @return e.g. "JDK 1.8+" */ private static String getJDKRequired( final File file ) { final String canonical = EIO.getCanOrAbsPath( file ); // e.g. E:\intellij\j<1>m\src\com\mindprod\sound\Sound.java // or E:\intellij\jspm\src\com\mindprod\macro\Degrees.java if ( canonical.startsWith( "E:\\intellij\\jp\\" ) ) { return "JDK 1.6+"; } else { // canonical has form E:\intellij\mp\j1\src\com\mindprod\bio\Bio.java char jdkDigit = canonical.charAt( "E:/intellij/mp/j".length() ); if ( !( '1' <= jdkDigit && jdkDigit <= '8' ) ) { oops( "Can't deduce Java version required from filename " + canonical ); } return "JDK 1." + jdkDigit + "+"; } } /** * insert paragraph markers. * empty lines are converted to html paragraph breaks. Must be pre-trimmed. * * @param lines of comments * * @return lines with blank lines marked with

*/ private static String[] insertParagraphMarkers( String[] lines ) { for ( int i = 0; i < lines.length; i++ ) { if ( lines[ i ].length() == 0 ) { // No point in indenting further to make the generated *.java easier to read. // since IntelliJ reformat will just put it back like this. lines[ i ] = "

"; } } return lines; } /** * log the changes we proposed to make to new.txt and old.txt * * @param classname unqualified name of the class * @param oldPackageComment existing package comment, unmodified * @param oldClassComment existing class comment, unmodified. * @param newPackageComment text of the tidied package comment * @param newClassComment texd of the tidied class comment */ private static void logChanges( final String classname, final String oldPackageComment, final String oldClassComment, final String newPackageComment, final String newClassComment ) { newPrw.println( "-[- new package " + classname + " -]-" ); newPrw.println( newPackageComment ); newPrw.println( "-[- new class " + classname + " -]-" ); newPrw.println( newClassComment ); oldPrw.println( "-[- old package " + classname + " -]-" ); oldPrw.println( "/*" + oldPackageComment + "*/" ); oldPrw.println( "-[- old class " + classname + " -]-" ); oldPrw.println( "/**" + oldClassComment + "*/" ); } /** * output an error message to console, old ond new logs * * @param message text of message. */ private static void oops( final String message ) { // when tidying, we suppress the heading unless there is an error. // when proofing, we have already emitted the heading. output( "", true, true, true ); emitFullSummary(); problems++; output( " ^^^ " + message, true, true, true ); output( "", true, true, true ); } /** * open log files where we show old ond new comments * one file old and one for new for each java source in current dir * * @param classname name of class we are tidying * * @throws FileNotFoundException if can't open log files */ private static void openLogs( final String classname ) throws FileNotFoundException { // O P E N L O G S oldPrw = EIO.getPrintWriter( new File( classname + ".old" ), 8 * 1024, EIO.UTF8 ); newPrw = EIO.getPrintWriter( new File( classname + ".new" ), 8 * 1024, EIO.UTF8 ); } /** * output a message to console, old ond new logs * * @param message text of message. */ private static void output( final String message ) { output( message, true, true, true ); } /** * output a message to console, old ond new logs * * @param message text of message. * @param console true if should log message to console * @param oldLog true if should log message to old log * @param newLog true if should log message to new log */ private static void output( final String message, boolean console, boolean oldLog, boolean newLog ) { if ( console ) { out.println( message ); } if ( oldLog ) { oldPrw.println( message ); } if ( newLog ) { newPrw.println( message ); } } /** * remove noise lines from a set of comment lines, including

* * @param lines body of the comment */ private static void removeNoise( final String[] lines ) { for ( int i = 0; i < lines.length; i++ ) { final String lc = lines[ i ].toLowerCase(); if ( ( lc.startsWith( "created" ) || lc.startsWith( "produced" ) ) && lc.contains( "intellij" ) || lc.startsWith( "@(#)" ) || lc.startsWith( "[" ) || lc.equals( "

" ) || lc.equals( "

" ) || lc.equals( "

" ) || lc.equals( "@" ) || lc.startsWith( "licence:" ) || lc.startsWith( "requires: jdk" ) || lc.contains( LICENCE_URL ) || NOISE_LINES.contains( lc ) ) { // treat line as if it were empty. Will likely be totally pruned out later. lines[ i ] = ""; } // otherwise, leave the line as is. } } /** * remove the the star slash package comment so we can later replace it with a new one. * * @param big string contents of entire source file * * @return new contents of file. */ private static String removePackageComment( final String big ) { final Matcher m = PACKAGE_COMMENT_FINDER.matcher( big ); if ( m.find() ) { return big.substring( 0, m.start() ) + big.substring( m.end() ); } else { return big; } } /** * replace the class comment with a new one. * * @param big string contents of entire source file * @param newClassComment replacement for the Javadoc class comment, with delimiters. * * @return new contents for source file. */ private static String replaceClassComment( final String big, final String newClassComment ) { // we left notes where old class comment began and ended, including delimiters if ( startClassComment >= 0 ) { return big.substring( 0, startClassComment ) + newClassComment + big.substring( endClassComment ); } else { // if we could not find it, leave as is return big; } } /** * Remove * each line. Lines are trimmed. Empty lines left in place. * * @param multilineComment the text of the giant comment without lead slash star or star slash * * @return comment broken into lines without *s, trimmed of both leads stars and spaces. */ private static String[] split( final String multilineComment ) { final String[] lines = SPLITTER.split( multilineComment ); for ( int i = 0; i < lines.length; i++ ) { lines[ i ] = lines[ i ].trim(); if ( lines[ i ].startsWith( "*" ) ) { // trim any lead spaces or *s. lines[ i ] = ST.trimLeading( lines[ i ], " *" ); } } return lines; } /** * tidy author's name to standard form. * * @param author author's same without lead @author * * @return author's name in standard form. */ private static String tidyAuthor( String author ) { String lc = author.trim().toLowerCase(); // if no name at all, assume default author. if ( lc.length() == 0 ) { return AUTHORS[ 0 ]; } for ( String candidate : AUTHORS ) { // check for a match on the author's first name with first name in candidate. if ( lc.startsWith( ST.firstWord( candidate ).toLowerCase() ) ) { return candidate; } } oops( "unrecognised author " + author ); return author.trim(); } /** * tidy/proof the comments in one file * * @param file Java source file to tidy * * @throws IOException if can't write logs or update the source */ private static void tidyOneFile( final File file ) throws IOException { classname = file.getName(); jdkRequired = getJDKRequired( file ); // chop off .java classname = classname.substring( 0, classname.length() - 5 ); openLogs( classname ); final String big = HunkIO.readEntireFile( file ); fullName = EIO.getCanOrAbsPath( file ); summary = ""; if ( proof ) { output( fullName, true, false, false ); } output( "-[- " + fullName + " -]-", false, true, true ); // don't process JAXB-generated code final String lead = big.substring( 0, Math.min( big.length(), 500 ) ); if ( lead.contains( "package com.mindprod.aws.jax" ) || lead.contains( "Binding(JAXB)" ) ) { return; } oldPackageComment = findPackageComment( big ); plines = split( oldPackageComment ); removeNoise( plines ); oldClassComment = findClassComment( classname, big ); if ( oldClassComment.length() == 0 ) { out.println( " ^^^ missing class comment or possible filename/class mismatch for " + file.getAbsolutePath() ); problems++; } clines = split( oldClassComment ); removeNoise( clines ); summary = extractSummary(); if ( proof ) { emitMiniSummary(); } copyright = extractCopyright(); author = extractAuthor(); sees = extractSees(); since = extractSince(); // extract summary, copyright and since first so they won't be confused as part of version history. versionHistory = extractVersionHistory(); atVersion = extractAtVersion(); // kludge fix damaged (@)version description comments. if ( USE_KLUDGE && atVersion != null ) { atVersion.kludge( versionHistory ); } packageDregs = ST.pruneExcessBlankLines( plines, 1 ); checkDregs( packageDregs ); if ( DEBUGGING ) { dumpLines( "class dregs before prune", clines ); } classDregs = ST.pruneExcessBlankLines( clines, 1 ); /* normally 1. 0 double spaces. 2 undouble spaces, 9999 remove all blank lines */ if ( DEBUGGING ) { dumpLines( "class dregs after prune", classDregs ); } checkDregs( classDregs ); checkEntityCoding( classDregs ); classDregs = insertParagraphMarkers( classDregs ); deduceMissingData(); warnOfDroppedData(); warnOfMissingData(); final String newPackageComment = formatPackageComment(); final String newClassComment = formatClassComment(); logChanges( classname, oldPackageComment, oldClassComment, newPackageComment, newClassComment ); if ( !proof && problems == 0 ) { updateJavaSource( file, big, newPackageComment, newClassComment ); } if ( problems > 0 ) { totalFilesWithProblems++; } totalProblems += problems; problems = 0; if ( warnings > 0 ) { totalFilesWithWarnings++; } totalWarnings += warnings; warnings = 0; closeLogs(); } /** * remove trailing dots from the summary string, and condense spaces. * * @param summary string to de-dot. * * @return string without trailing dot, also trimmed. */ private static String tidySummary( final String summary ) { return ST.condense( ST.trimTrailing( summary, '.' ) ); } /** * Tidy a changeDescription. Remove lead space, dots, stars, dashes, colons, commas * * @param changeDescription untidied version change description line. * * @return tidied change description. */ private static String tidyVersionChangeDescription( String changeDescription ) { String temp = ST.trimLeading( changeDescription, " -.*:," ); temp = ST.trimTrailing( temp, " *" ); return ST.condense( temp ); } /** * make the changes to the Java source comments * * @param file Java source file * @param big text contents of that file * @param newPackageComment text of tidied package comment. * @param newClassComment text of the tidied class comment * * @throws IOException if problem writing file. */ private static void updateJavaSource( final File file, final String big, final String newPackageComment, final String newClassComment ) throws IOException { // trim replacements so we won't add an additional new line at either end. final String newBig = com.mindprod.common18.Localise.localise( newPackageComment.trim() ) + removePackageComment( replaceClassComment( big, com.mindprod.common18.Localise.localise( newClassComment.trim() ) ) ); HunkIO.writeEntireFile( file, newBig ); } /** * ensure history is in proper order * * @param versionItems sorted by version number. */ private static void verifyHistoryOrder( VersionItem[] versionItems ) { int prevVersion = 0; String prevVersionString = ""; String prevYYYYMMDD = ""; for ( VersionItem vi : versionItems ) { final int thisVersion = vi.getVersionTimesTen(); final String thisVersionString = vi.getVersionString(); if ( thisVersion == prevVersion ) { oops( "duplicate version " + vi.getVersionString() ); } else if ( thisVersion != prevVersion + 1 && prevVersion != 0 ) { oops( "skipped version from " + prevVersionString + " to " + thisVersionString ); } prevVersion = thisVersion; prevVersionString = thisVersionString; final String thisYYYYMMDD = vi.getYYYYMMDD(); if ( thisYYYYMMDD.compareTo( prevYYYYMMDD ) < 0 ) { oops( "version dates out of order near " + thisVersionString + " " + thisYYYYMMDD ); } prevYYYYMMDD = thisYYYYMMDD; } } /** * warn of data in old comments that will not be copied over in any form to the new comment. */ private static void warnOfDroppedData() { if ( packageDregs.length > 0 ) { oops( "dregs in package dropped" ); for ( String packageDreg : packageDregs ) { output( packageDreg ); } } } /** * issue warning message about any missing data */ private static void warnOfMissingData() { if ( summary.length() == 0 ) { oops( "Missing Summary" ); } if ( copyright.length() == 0 ) { oops( "Missing Copyright" ); } if ( author.length() == 0 ) { oops( "Missing Author" ); } if ( versionHistory.length == 0 ) { oops( "Missing version history" ); } if ( atVersion == null ) { oops( "Missing @version" ); } if ( since.length() == 0 ) { oops( "Missing @since history" ); } } /** * output an error message to console, old ond new logs * * @param message text of message. */ private static void warning( final String message ) { // when tidying, we suppress the heading unless there is an error. // when proofing, we have already emitted the heading. emitFullSummary(); warnings++; output( " ___ " + message, true, true, true ); } /** * prepare java source files by replacing the first header comment with a standard. * When proofing, logs to old.txt and new.txt in the current directory. * * @param args word "proof" or "tidy" followed by a list of files and directories to process. * e.g. E:\intellij\mp\j7\src\com\mindprod\repair * -s mean means process subdirs of subsequently mentioned dirs, comes first. * usually tidy -s E:\intellij\mp * * @throws IOException if cannot read or write source files. */ public static void main( final String[] args ) throws IOException { out.println( "logs *.new *.old will appear in current dir" ); out.println( "Errors will be marked with ^^^" ); final String flavour = args[ 0 ]; switch ( flavour ) { case "proof": proof = true; break; case "tidy": proof = false; break; default: throw new IllegalArgumentException( USAGE ); } args[ 0 ] = null; final CommandLine files = new CommandLine( args, new AllButSVNDirectoriesFilter(), new ExtensionListFilter( "java" ) ); for ( File file : files ) { tidyOneFile( file ); } // end for out.println( totalFilesWithProblems + " files with problems" ); out.println( totalFilesWithWarnings + " files with warnings" ); out.println( totalProblems + " problems detected" ); out.println( totalWarnings + " warnings detected" ); } /** * compare two @see strings by package, class, method. *

* Defines an alternate sort order for String. */ private static class SeeComparator implements Comparator { /** * compare two @see strings by package, class, method. * Defines an alternate sort order for String with JDK 1.5+ generics. * Compare two String Objects. * Compares package case sensitively then classname case sensitively then method case sensitively. * Informally, returns (a-b), or +ve if a comes after b. * * @param a first String to compare * @param b second String to compare * * @return +ve if a>b, 0 if a==b, -ve if a<b */ public final int compare( String a, String b ) { String aPackage = null; String bPackage = null; String aClassname = null; String bClassname = null; String aMethod; String bMethod; a = ST.chopLeadingString( a, "@see " ); final int aPlace = a.indexOf( '#' ); if ( aPlace >= 0 ) { aMethod = a.substring( aPlace + 1 ); a = a.substring( 0, aPlace ); } else { aMethod = ""; } // lead lower-case or . chars are part of the package for ( int i = 0; i < a.length(); i++ ) { final char c = a.charAt( i ); if ( !ST.isLegal( c, "abcdefghijklmnopqrstuvwyz0123456789_." ) ) { if ( i == 0 ) { aPackage = "zzzzzzzzzz"; aClassname = a; } else { aPackage = a.substring( 0, i ); aClassname = a.substring( i ); } break; } } if ( aPackage == null ) { aPackage = a; aClassname = "zzzzzzzzzz"; } b = ST.chopLeadingString( b, "@see " ); final int bPlace = a.indexOf( '#' ); if ( bPlace >= 0 ) { bMethod = b.substring( bPlace + 1 ); b = b.substring( 0, bPlace ); } else { bMethod = ""; } // lead lower-case or . chars are part of the package for ( int i = 0; i < b.length(); i++ ) { final char c = b.charAt( i ); if ( !ST.isLegal( c, "abcdefghijklmnopqrstuvwyz0123456789_." ) ) { if ( i == 0 ) { bPackage = "zzzzzzzzzz"; bClassname = b; } else { bPackage = b.substring( 0, i ); bClassname = b.substring( i ); } break; } } if ( bPackage == null ) { bPackage = b; bClassname = "zzzzzzzzzz"; } int diff = aPackage.compareTo( bPackage ); if ( diff != 0 ) { return diff; } int diff1 = aClassname.compareTo( bClassname ); if ( diff1 != 0 ) { return diff1; } return aMethod.compareTo( bMethod ); } } /** * nest static class to describe a version. */ private static class VersionItem implements Comparable { // declarations /** * indent for class (@)version continuation, from *_ */ private static final String AT_VERSION_CONTINUATION_INDENT = ST.spaces( 9 ); /** * indent for package version history continuation, from *_ */ private static final String PACKAGE_VERSION_CONTINUATION_INDENT = ST.spaces( 16 ); /** * strings describing various changes made as part of that version. */ private final ArrayList descsForOneVersion; /** * version number as a string e.g. "10.1" */ private final String versionString; /** * iso date i form 2009-04-17 */ private final String yyyymmdd; /** * version 10.1 would be stored as 101 */ private final int versionTimesTen; // /declarations /** * constructor * * @param versionString as a string with a dot e.g. "10.1" * @param yyyymmdd as an iso date e.g. "2009-04-17" */ VersionItem( String versionString, String yyyymmdd ) { this.versionString = versionString; // get rid of the .and get as int. versionTimesTen = Integer.parseInt( versionString.substring( 0, versionString.length() - 2 ) + versionString.substring( versionString.length() - 1 ) ); this.yyyymmdd = yyyymmdd; descsForOneVersion = new ArrayList<>( 11 ); } // methods /** * add description on initial line or subsequent lines. * * @param versionDescContinuation text of the description of the version */ void addVersionDescContinuation( String versionDescContinuation ) { versionDescContinuation = versionDescContinuation.trim(); if ( versionDescContinuation.length() != 0 ) { descsForOneVersion.add( versionDescContinuation ); } } // /method /** * format the Version Item to look like this for inclusion in the package format. *

* (@)version _2.4 2007-05-26 - add spinner, drop in BC and Saskatchewan sales tax. * BC sales dropped to 7% from 7.5%, * Saskatchewan from 6% to 5% * * @return text of this Version item formatted in an (@)version class comment. */ String formatForClass() { final FastCat sb = new FastCat( 5 + descsForOneVersion.size() * 4 ); sb.append( " * @version " ); sb.append( versionString ); sb.append( ' ' ); sb.append( yyyymmdd ); if ( descsForOneVersion.size() == 0 ) { sb.append( "\n" ); } else { sb.append( " " ); boolean first = true; for ( String changeDescription : descsForOneVersion ) { if ( first ) { first = false; } else { sb.append( " * " ); sb.append( AT_VERSION_CONTINUATION_INDENT ); } sb.append( changeDescription ); sb.append( "\n" ); } } return sb.toString(); } // /method /** * format the Version Item to look like this for inclusion in the package format. * _2.4 2007-05-26 - add spinner, drop in BC and Saskatchewan sales tax. * BC sales dropped to 7% from 7.5%, * Saskatchewan from 6% to 5% * * @return text of this Version item formatted in an version package comment */ String formatForPackage() { final FastCat sb = new FastCat( 5 + descsForOneVersion.size() * 4 ); sb.append( " * " ); sb.append( ST.leftPad( versionString, 4, false ) ); sb.append( ' ' ); sb.append( yyyymmdd ); if ( descsForOneVersion.size() == 0 ) { sb.append( "\n" ); } else { sb.append( " " ); boolean first = true; for ( String changeDescription : descsForOneVersion ) { if ( first ) { sb.append( changeDescription ); sb.append( "\n" ); first = false; } else { sb.append( " * " ); sb.append( PACKAGE_VERSION_CONTINUATION_INDENT ); sb.append( changeDescription ); sb.append( "\n" ); } } } return sb.toString(); } // /method String getVersionString() { return versionString; } // /method int getVersionTimesTen() { return versionTimesTen; } // /method String getYYYYMMDD() { return yyyymmdd; } // /method /** * patch (@) version with lead char lost, on continuation lines. * * @param templates Version History. */ void kludge( VersionItem[] templates ) { for ( final VersionItem template : templates ) { if ( template.versionTimesTen == this.versionTimesTen && template.yyyymmdd.equals( this.yyyymmdd ) && template.descsForOneVersion.size() >= 2 && this.descsForOneVersion.size() >= 2 ) { final int n = Math.min( this.descsForOneVersion.size(), template.descsForOneVersion.size() ); for ( int i = 1; i < n; i++ ) { String templateDesc = template.descsForOneVersion.get( i ); String thisDesc = this.descsForOneVersion.get( i ); if ( templateDesc.length() == thisDesc.length() + 1 && thisDesc.equals( templateDesc.substring( 1 ) ) ) { this.descsForOneVersion.set( i, templateDesc ); out.println( "restoring @version description: " + templateDesc ); } } } } } /** * sort by version number. * Defines default the sort order for VersionItem Objects. * Compare this VersionItem with another VersionItem. * Compares versionTimesTen. * Informally, returns (this-other) or +ve if this is more positive than other. * * @param other other VersionItem to compare with this one * * @return +ve if this>other, 0 if this==other, -ve if this<other */ public final int compareTo( @NotNull VersionItem other ) { return this.versionTimesTen - other.versionTimesTen; } // /method } // /method // /methods } // end TidyLeadComments