/* * [StatusKind.java] * * Summary: enum of various status for an Xenu probe. * * Copyright: (c) 2012-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 2012-06-03 initial version * 1.1 2012-11-13 flip to using array lookup of status code rather than HashMap lookup of status message */ package com.mindprod.brokenlinks; import com.mindprod.common18.EIO; import com.mindprod.common18.ST; import com.mindprod.fastcat.FastCat; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.regex.Pattern; import static java.lang.System.*; /** * enum of various status for an Xenu probe. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2012-06-03 initial version * @since 2012-06-03 */ public enum StatusKind { BAD( 'b', "bad" ), GOOD( 'g', "good" ), IGNORE( 'i', "ignore" ), TEMP_REDIRECT( 't', "temporary redirect" ), PERM_REDIRECT( 'p', "permanent redirect" ), UNKNOWN( 'u', "?" ); private static final boolean DEBUGGING = false; /** * highest response code we cover. * If change, must change in Prepare, StatusKind, responsecodes.csv, recompile, run Prepare and rebuild. */ private static final int HIGHEST_CODE = 12200; /** * lowest response code we cover. * If change, must change in Prepare, StatusKind, responsecodes.csv, recompile, run Prepare and rebuild. */ private static final int LOWEST_CODE = -20; /** * marks version of this class. Must match Prepare.serialVersionUID and responsecodes.ser */ private static final long serialVersionUID = 1; private static final Pattern SPLIT_ON_PUNCT = Pattern.compile( "\\s*[,:]\\s*" ); /** * array of names Java uses for codes, interned */ private static String[] javaNames; /** * index code to get StatusKind category for code. */ private static StatusKind[] kindForCodes; /** * array of English descriptions for codes, interned */ private static String[] statusWordings; static { fetchResponseCodes(); } /** * description of this enum for display */ private final String displayName; /** * associated letter code for this enum */ private final char letterCode; /** * constructor * * @param letterCode letter b=bad g=good i=ignore p=permanent redirect t=temp redirect u=unknown * @param displayName name to use for this enum in display e.g. "good", "bad" */ StatusKind( final char letterCode, final String displayName ) { this.letterCode = letterCode; this.displayName = displayName; } /** * split toAdd into pieces and add them to a if not Empty * * @param toAdd */ private static void collect( ArrayList a, final String toAdd ) { if ( ST.isEmpty( toAdd ) ) { return; } final String[] pieces = SPLIT_ON_PUNCT.split( toAdd ); for ( String piece : pieces ) { if ( ST.isEmpty( piece ) ) { continue; } a.add( piece.trim() ); } } /** * Remove duplicates if match equalIgnoreCase * a ArrayList to deDup */ private static void deDup( final ArrayList a ) { // outer loop works down int n = a.size(); for ( int i = n - 1; i >= 1; i-- ) // inner partial loop works up { inner: for ( int j = 0; j < i; j++ ) { if ( a.get( j ).equalsIgnoreCase( a.get( i ) ) ) { a.remove( i ); break inner; } } } // deduped result is left in a } /** * read the resource containing prepared data about responsecodes to initialise lookup tables by status code. */ private static void fetchResponseCodes() { if ( DEBUGGING ) { out.println( "loading response code info..." ); } try { // O P E N // get resources "/com/mindprod/brokenlinks/responsecodes.ser" final ObjectInputStream ois = EIO.getObjectInputStream( StatusKind.class.getResourceAsStream( "responsecodes.ser" ), 4 * 1024, true ); // R E A D long wasResourceVersion = ( Long ) ois.readObject(); if ( wasResourceVersion != serialVersionUID ) { throw new IllegalArgumentException( "Program Bug: Wrong version of serialVersionUID in responsecodes" + ".ser resource. Was:" + wasResourceVersion + " wanted:" + serialVersionUID ); } if ( LOWEST_CODE != ( Integer ) ois.readObject() ) { throw new IllegalArgumentException( "Program Bug: Wrong version of low in responsecodes.ser resource" + "." ); } if ( HIGHEST_CODE != ( Integer ) ois.readObject() ) { throw new IllegalArgumentException( "Program Bug: Wrong version of high in responsecodes.ser resource" + "." ); } // only needed until we can build kindForCodes char[] kindForCodeLetters = ( char[] ) ois.readObject(); javaNames = ( String[] ) ois.readObject(); statusWordings = ( String[] ) ois.readObject(); // C L O S E ois.close(); // convert from letter form to enum form kindForCodes = new StatusKind[ kindForCodeLetters.length ]; for ( int j = 0; j < kindForCodeLetters.length; j++ ) { kindForCodes[ j ] = StatusKind.valueOfAlias( kindForCodeLetters[ j ] ); } // // dump tables we just got from the resource // for ( int i = LOWEST_CODE; i <= HIGHEST_CODE; i++ ) // { // final int j = i - LOWEST_CODE; // out.println( i + " " + kindForCodes[ j ] + " " + javaNames[ j ] + " " + statusWordings[ // j ] ); // } } catch ( Exception e ) { err.println( "<><>Program Bug<><> Missing responsecodes.ser resource " + e + "\n" ); System.exit( 5 ); } } /** * synthesise a status message corresponding to status code. Not Does not precisely match what server sent in * probe. * * @param status http response code * * @return status message e.g. 200 : ok */ static String terseStatusMessage( final int status ) { if ( LOWEST_CODE <= status && status <= HIGHEST_CODE ) { int j = status - LOWEST_CODE; final FastCat sb = new FastCat( 5 ); sb.append( "( " ); sb.append( status ); sb.append( " : " ); final String statusWording = statusWordings[ j ]; assert statusWording != null : "missing status wording"; sb.append( statusWording ); sb.append( " )" ); return sb.toString(); } else { return "( " + status + " : ? )"; } } /** * convert alias letter code for enum to the enum, e.g. as GOOD BAD etc. * * @param letterCode letter b=bad g=good i=ignore p=permanent redirect t=temp redirect u=unknown * * @return corresponding StatusKind enum. */ private static StatusKind valueOfAlias( final char letterCode ) { for ( StatusKind kind : StatusKind.values() ) { if ( kind.letterCode == letterCode ) { return kind; } } throw new IllegalArgumentException( "Invalid StatusKind " + letterCode + " argument to valueOfAlias" ); } /** * synthesise a status message corresponding to status code. Not Does not precisely match what server sent in * probe. * * @param status http response code * @param actualStatusMessage optional actual responseMessage from server. Might contain a : * * @return status message e.g. good : HTTP_OK : 200 : ok */ static String verboseStatusMessage( final int status, final String actualStatusMessage ) { if ( LOWEST_CODE <= status && status <= HIGHEST_CODE ) { int j = status - LOWEST_CODE; final FastCat sb = new FastCat( 10 ); final ArrayList a = new ArrayList<>( 10 ); collect( a, kindForCodes[ j ].displayName ); collect( a, javaNames[ j ] ); collect( a, statusWordings[ j ] ); collect( a, Integer.toString( status ) ); collect( a, actualStatusMessage ); deDup( a ); for ( String item : a ) { sb.append( item ); } return "( " + sb.toSeparatedList( " : " ) + " )"; } else { return "( " + status + " : ? )"; } } /** * categorise the status as GOOD BAD etc. * * @param status int HTTP status from probe or XENU. * * @return corresponding StatusKind enum or UNKNOWN if no match */ public static StatusKind categoriseStatus( final int status ) { if ( LOWEST_CODE <= status && status <= HIGHEST_CODE ) { int j = status - LOWEST_CODE; return kindForCodes[ j ]; } else { return UNKNOWN; } } /** * test driver * * @param args not used */ public static void main( String[] args ) { if ( DEBUGGING ) { for ( int i = LOWEST_CODE - 1; i <= HIGHEST_CODE + 1; i++ ) { out.println( verboseStatusMessage( i, null ) ); } } } }