#!/bin/sh
#
# @Authors: Christopher Hylands
#
# @Version: $Id: chkjava,v 1.72 2007/12/06 18:24:58 cxh Exp $
#
# @Copyright (c) 1997-2007 The Regents of the University of California.
# All rights reserved.
#
# Permission is hereby granted, without written agreement and without
# license or royalty fees, to use, copy, modify, and distribute this
# software and its documentation for any purpose, provided that the
# above copyright notice and the following two paragraphs appear in all
# copies of this software.
#
# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
# ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
# THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
# PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
# CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
# ENHANCEMENTS, OR MODIFICATIONS.
#
#                                                 PT_COPYRIGHT_VERSION_2
#                                                 COPYRIGHTENDKEY

# Script to sanity check java files
# 1) Report protected and private variables that do not have a leading _
# 2) Report lines longer than 80 chars
# 3) Report public methods and variables after private or protected ones
# 4) Make sure that each file includes a copyright, @version and @author
# 5) Make sure that @author and @version are not @author: and @version:
# 5.1) Warn if @contributor is used
# 6) Check for spaces after commas
# 7) Check for FIXMEs
# 8) Check for lines with trailing spaces
# 9) Check the rating
# 10) Check for bogus spaces around declarations
# 11) Check for %W% %G%, which are from SCCS
# 12) Check for // System.out.println
# 13) Check for @exception tags that are not worded properly
# 14) Check for @exception
# 15) Make sure that Parameters names do not start with capital letters
# 16) Warn about catching CloneNotSupported
# 17) Warn about import ... *; for everything but ptolemy.kernel.util
# 18) Warn if the clone method copies port or parameters
# 19) Warn if there is no trailing newline
# 20) Warn if the class comment has * on each line.
# 21) Warn if printStackTrace() or fillInStackTrace() are used
# 22) Warn if setTypeAtLeast is present, but clone is not defined
# 23) Check that the methods are alphabetical

# Determine if we are on windows
windows=no
case "${OSTYPE-no}" in
# Cygwin Bash
    cygwin*)
        windows=yes;;
# Tcsh
    Windows*)
        windows=yes;;
esac

awk=awk
if [ "$windows" = "yes" ]; then
    awk=gawk
fi

for file in $@
do
        results=`head -1 $file | grep "Generated By:JavaCC: Do not edit this line"`
        if [ "x$results" != "x" ]; then
            echo "$file: Skipping because is is generated by JavaCC"
            continue
        fi

        results=`grep protected $file | grep -v 'protected methods' |
                egrep -v '[         ]*\*' |
                grep -v 'protected variables' |
                grep -v 'protected class' |
                grep -v 'write-protected' |
                grep -v ' _'`
        if [ "x$results" != "x" ]; then
                echo "$results" | $awk '{
                    # If this is a constructor, then do not complain
                    split($2,f,"(")
                    # Skip over comments that start with //
                    if (f[1] != filenm && $0 !~ /^[         ]*\/\//) {
                        if (printedheader == 0) {
                            print filenm".java: protected does not have a leading _"
                            printedheader = 1
                        }
                        print $0
                    }
                }' filenm=`basename $file .java`
        fi

        results=`grep private $file |
                egrep -v '[         ]*\*' |
                grep -v 'private methods' |
                grep -v 'private class' |
                grep -v 'private static final class' |
                grep -v 'private variables' | grep -v ' _'`
        if [ "x$results" != "x" ]; then
                echo "$results" | $awk '{
                    # If this is a constructor, then do not complain
                    split($2,f,"(")
                    # Skip over comments that start with //
                    # or start with a non whitespace character
                       if (f[1] != filenm && $0 !~ /^[         ]*\/\// && $0 !~/^[         ]*/) {
                        if (printedheader == 0) {
                            print filenm".java: private does not have a leading _"
                            printedheader = 1
                        }
                        print $0
                    }
                }' filenm=`basename $file .java`
        fi

        # Look for lines longer than 80 chars
        #$awk 'length($0) > 80 {if ($0 !~ /[Hh][Rr][Ee][Ff]/ && $1 !~ /@version/ ) {print FILENAME": Line more than 80 chars:"; print $0}}' $file

        # Look for public statements after private etc.
        $awk '$1 == "public" {sawpublic=1
                                if (sawprivate == 1 && sawinner != 1) {
                                   print FILENAME": public is after private"
                                   print $0
                                }
                                     if (sawprotected == 1 && sawinner != 1) {
                                   print FILENAME": public is after protected"
                                   print $0
                                }
                        }
        $1 == "protected" {
                                if (sawprivate == 1 && sawinner != 1) {
                                   print FILENAME": public is after private"
                                   print $0
                                }
                        }
        $1 == "private" {
                            if ( $0 ~ /private class/ || $0 ~/private static class/) {
                                # Inner Class
                                sawpublic=0
                                sawprotected=0
                                sawprivate=0
                            } else {
                                # If we have a private constructor, then
                                # allow it to be at the top of the file.
                                # ArrayMath.java has one.
                                split($2,constr,"(");
                                np=split(FILENAME,path,"/")
                                split(path[np],classname,".")
                                if (constr[1] !=  classname[1]) {
                                    sawprivate=1
                                }
                            }
                        }
        $0 ~ /^[         ]*\/\/*[         ]*[Ii]nner.*[Cc]lass/ {
                            # If we see an inner class comment, then
                            # we basically ignore any out of order methods
                            # after the comment.
                            sawinner=1
        }' $file

        # Check that certain keywords exist
        words="@version @author Copyright Regents"
        for word in $words
        do
          egrep -s $word $file > /dev/null
          retval=$?
              if [ $retval != 0 ]; then
            echo "$file does not contain $word"
          fi
        done

        # Look for @version: or @author:
        $awk '$0 ~ /@author:/ {
            print FILENAME": Remove trailing : from @author:"
            print $0
        }
        $0 ~ /@version:/ {
            print FILENAME": Remove trailing : from @version:"
            print $0
        }
        $0 ~/@contributor/ {
            print FILENAME": @contributor produces warnings under JDK1.4, instead, merge with @author so that the line looks like @author foo, bar, Contributor: bif"
            print $0
        }
        $0 ~/@returns/ {
            print FILENAME": Remove trailing s from @returns"
            print $0
        }' $file

        # Check that all commas have spaces
        # A line that contains ", is ok
        # A line that contains ,</i> is ok
        results=`sed 's/\r//' $file | egrep '[^"],[^v" ]' | grep -v ',[</i>]'`
        if [ "x$results" != "x" ]; then
            echo "$file: comma without out trailing space:"
            echo "$results"
        fi

        # Check for FIXME
        results=`egrep 'FIXME' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: contains a FIXME"
            echo "$results"
        fi

        # Check for lines with trailing spaces
        results=`egrep '[ 	]+$' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: contains trailing spaces or trailing tabs, run rmtrailingspace to fix this"
            echo "$results"
        fi

        # Check for lines with tabs
        results=`egrep '[	]' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: contains tabs, run rmtabs to fix this"
            echo "$results"
        fi

        # Check for problems with equals signs
        results=`egrep '[^-|*/ ><+&=\!]=' $file | \
            egrep -iv 'HREF=|NAME=|=WxH|"="|=\\\"' `
        if [ "x$results" != "x" ]; then
            echo "$file: contains an equal sign without a leading space"
            echo "$results"
        fi

        # Check for problems with equals signs
        # Note that we ignore =\", which is used in moml
        results=`egrep '=[^ =\\]' $file | \
            egrep -iv 'HREF=|NAME=|=WxH|"="' `
        if [ "x$results" != "x" ]; then
            echo "$file: contains an equal sign without a trailing space"
            echo "$results"
        fi


        # Check the rating
        results=`egrep '@(Pt.)*AcceptedRating' $file`
        if [ "x$results" != "x" ]; then
            rating=`echo $results | $awk '{print $2}' `
            if [ "$rating" != "Green" ]; then
                echo "$file: does not have an AcceptedRating of Green"
                echo "$results"
            fi
        else
            echo "$file: does not have an @AcceptedRating or @Pt.AcceptedRating doc tag"
        fi

        results=`egrep '(public|private|protected) .* .*\(' $file | egrep ' \(|\( ' | grep -v ') throws' `
        if [ "x$results" != "x" ]; then
            echo "$file: has extra spaces around '(' in decl (should be foo(int a)"
            echo "$results"
        fi

        results=`egrep '(public|private|protected) .* .*\(' $file | egrep '\){'`
        if [ "x$results" != "x" ]; then
            echo "$file: needs space between ')' and '{' in decl"
            echo "$results"
        fi

        # 11) Check for %W% %G%, which are from SCCS
        results=`egrep '%W%|%G%' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: has old SCCS keywords: %W% or %G%"
            echo "$results"
        fi

        # 12) Check for // System.out.println
        results=`egrep '//' $file | grep System.out.println`
        if [ "x$results" != "x" ]; then
            echo "$file: has commented out println statements"
            echo "$results"
        fi

        # 13) Check for @exception tags that are not worded properly
        # Added a . to this regex so that we find things like
        #  @exception java.util.NoSuchElementException If the enumeration is
        results=`egrep '@exception' $file | egrep -v '@exception [A-Za-z0-9.]* If' | grep -v 'Not thrown in this base class' | grep -v 'Always thrown' | grep -v "Thrown in derived classes if"`
        if [ "x$results" != "x" ]; then
            echo "$file: @exception tags should either be"
            echo "      '@exception MyException If such and such occurs'"
            echo " or   '@exception MyException Not thrown in this base class'"
            echo " or   '@exception MyException Always thrown'"
            echo " or   '@exception MyException Thrown in derived classes if'"
            echo "$results"
        fi

        # 13) Check for @exception
        results=`egrep '@throw' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: contains @throw[s].  Use @exception instead."
            echo "$results"
        fi

        # 15) Make sure that Parameters names do not start with capital letters
        results=`egrep 'public Parameter [A-Z]' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: contains a Parameter name that starts with a capital letter."
            echo "$results"
        fi

        # 16) Warn about catching CloneNotSupported
        results=`egrep 'catch .*CloneNotSupported' $file`
        if [ "x$results" != "x" ]; then
            echo "$file: catches CloneNotSupported, which is likely a mistake"
            echo "$results"
        fi

        # 17) Warn about import ..*;
        results=`egrep '^import .*\*;' $file | grep -v "import ptolemy.kernel.util.*;"`
        if [ "x$results" != "x" ]; then
            echo "$file: import ...*; should import each class individually"
            echo "$results"
        fi
        # 18) Warn if the clone method copies port or parameters
        results=`egrep 'Object clone\(W' $file`
        if [ "x$results" != "x" ]; then
            $awk '{  if ($0 ~ /Object clone/) {
                        clonedecl=NR
                    } else {
                        if ($0 ~ /getPort/ || $0 ~ /getAttribute/) {
                            if (NR - clonedecl < 20) {
                                print FILENAME ": clone?"
                                print "    This class has a clone method that calls either getPort or getAttribute."
                                print "    If this class extends TypedAtomicActor, then consider removing the getPort"
                                print "    and getAttribute lines in this clone method,"
                                print"     TypedAtomicActor does that for you."
                                exit
                            }
                        }
                    }
                 }' $file
        fi

        # 19) Warn if there is no trailing newline
        results=`tail -1 $file | od -bv | tail -2 |head -1 | grep '012$'`
        if [ "x$results" = "x" ]; then
                echo "$file: does not have a trailing newline"
        fi

        # 20) Warn if the class comment has * on each line.
        # Commented out because Eclipse 3.1 inserts * on each line
        #results=`egrep '^[ ]\*' $file | egrep -v ':[ ]\*/'`
        #if [ "x$results" != "x" ]; then
        #        echo "$file: may have leading * on each line in the class comment"
        #        echo "$results"
        #fi

        #21) Warn if printStackTrace() or fillInStackTrace() are used
        results=`egrep 'printStackTrace\(\)|fillInStackTrace\(\)' $file`
        if [ "x$results" != "x" ]; then
                echo "$file: calls printStackTrace() or fillInStackTrace(), use KernelException.stackTraceToString(ex) instead."
                echo "$results"
        fi

        # 22) Warn if setTypeAtLeast is present, but clone is not defined
        results=`egrep '\.setTypeAtLeast\(' $file`
        if [ "x$results" != "x" ]; then
            results2=`egrep 'Object clone\(W' $file`
            if [ "x$results2" = "x" ]; then
                echo "$file: calls setTypeAtLeast, but does not define clone()"
                echo "$results"
            fi
        fi
        # 23) Check that the methods are alphabetical
        # This is important because the javadoc output follows
        # the order of the methods in the file.
        accesses="public protected private"
        for access in $accesses
        do
          # Once we see the string 'inner class' we stop checking
          awk '{print $0; if ($0 ~ /inner class/) { exit}}' $file |
          egrep "^    $access" | grep -v = | grep '[a-zA-Z](' |
                sed 's/.* \([_A-Za-z0-9]*(\).*/\1/'  > /tmp/chkjava.a
          awk '{print $0; if ($0 ~ /inner class/) { exit}}' $file |
          egrep "^    $access" | grep -v = | grep '[a-zA-Z](' |
                sed 's/.* \([_A-Za-z0-9]*(\).*/\1/' | sort > /tmp/chkjava.b
          diff /tmp/chkjava.a /tmp/chkjava.b > /dev/null
          retval=$?
          if [ $retval != 0 ]; then
             echo "$file: $access methods are not in alphabetical order"
             echo "methods are:"
             cat /tmp/chkjava.a
             echo "methods should be:"
             diff /tmp/chkjava.a /tmp/chkjava.b
          fi
        done
done

