001 package hirondelle.web4jtools.metrics.base; 002 003 import hirondelle.web4j.action.ActionTemplateShowAndApply; 004 import hirondelle.web4j.action.ResponsePage; 005 import hirondelle.web4j.model.AppException; 006 import hirondelle.web4j.model.ModelCtorException; 007 import hirondelle.web4j.model.ModelFromRequest; 008 import hirondelle.web4j.request.RequestParameter; 009 import hirondelle.web4j.request.RequestParser; 010 import hirondelle.web4j.util.Stopwatch; 011 import hirondelle.web4j.util.Util; 012 013 import java.io.File; 014 import java.util.ArrayList; 015 import java.util.Arrays; 016 import java.util.Collections; 017 import java.util.Comparator; 018 import java.util.LinkedHashMap; 019 import java.util.List; 020 import java.util.Map; 021 import java.util.Set; 022 import java.util.logging.Logger; 023 024 /** 025 * Override base project information configured in <tt>web.xml</tt>, and show high level metrics to user. 026 * 027 * <P>Scans the local file system under the given base directory. 028 * Various statistics and listings are extracted. 029 */ 030 public final class BaseInfoAction extends ActionTemplateShowAndApply { 031 032 public BaseInfoAction(RequestParser aRequestParser) { 033 super(FORWARD, REDIRECT, aRequestParser); 034 } 035 036 public static final RequestParameter BASE_DIRECTORY = RequestParameter.withLengthCheck("BaseDirectory"); 037 public static final RequestParameter PROJECT_NAME = RequestParameter.withLengthCheck("ProjectName"); 038 public static final RequestParameter BASE_URI_FOR_FETCHING_IMAGES = RequestParameter.withLengthCheck("BaseURIForFetchingImages"); 039 040 /** Show a form reflecting current settings. */ 041 @Override protected void show() throws AppException { 042 if( listFileInfo() == null ) { 043 addMessage("Please 'Scan Source Code' to analyze your app's source code."); 044 } 045 addToRequest(ITEM_FOR_EDIT, fetchBaseInfo()); 046 } 047 048 /** Validate changes to current settings. */ 049 @Override protected void validateUserInput() throws AppException { 050 ModelFromRequest builder = new ModelFromRequest(getRequestParser()); 051 try { 052 fBaseInfo = builder.build(BaseInfo.class, PROJECT_NAME, BASE_DIRECTORY, BASE_URI_FOR_FETCHING_IMAGES); 053 } 054 catch (ModelCtorException ex) { 055 addError(ex); 056 } 057 } 058 059 /** 060 * Save the changes to current settings, and recursively scan the files under 061 * the base directory. 062 * 063 * <P>Creates an internal data structure to represent your application's source code. 064 */ 065 @Override protected void apply() throws AppException { 066 fLogger.fine("Saving current settings."); 067 BaseInfoDAO dao = new BaseInfoDAO(getRequestParser().getRequest()); 068 dao.save(fBaseInfo); 069 070 fLogger.fine("Scanning source files..."); 071 Stopwatch stopwatch = new Stopwatch(); 072 stopwatch.start(); 073 074 File baseDirectory = new File(fBaseInfo.getBaseDirectory().getRawString()); 075 scan(baseDirectory); 076 077 addToSession(FILE_INFO_MAP_KEY, fAllFiles); 078 addToSession(FILE_INFO_LIST_KEY, asSortedList(fAllFiles)); 079 080 addToSession("numFiles", fNumFiles); 081 addToSession("totalSize", fTotalSize); 082 addToSession("numSourceFiles", fNumSourceFiles); 083 addToSession("numImageFiles", fNumImageFiles); 084 addToSession("numMarkupFiles", fNumMarkupFiles); 085 addToSession("numJavaFiles", fNumJavaFiles); 086 087 addToSession("sourceFileExtensions", FileInfo.getSourceFileExtensions()); 088 addToSession("imageFileExtensions", FileInfo.getImageFileExtensions()); 089 addToSession("markupFileExtensions", FileInfo.getMarkupFileExtensions()); 090 addToSession("ignorableFiles", FileInfo.getIgnorableFiles()); 091 092 stopwatch.stop(); 093 fLogger.fine("Finished scanning source files. Time needed for scan : " + stopwatch + " msec.'"); 094 } 095 096 /** 097 * Key for <tt>Map</tt> stored in session scope. This data structure is created/replaced when 098 * the user presses the 'Scan Source Code' button. 099 * 100 * <P>This Map style retains directory information. 101 */ 102 public static final String FILE_INFO_MAP_KEY = "fileInfoMap"; 103 104 /** 105 * Key for <tt>List</tt> stored in session scope. This data structure is created/replaced when 106 * the user presses the 'Scan Source Code' button. 107 * 108 * <P>This List style removes directory information, and is useful for file listings. 109 * When the user changes the sorting, the re-sorted list should overwrite the object 110 * in session scope. That way, the latest sort used by the user will be retained. 111 */ 112 public static final String FILE_INFO_LIST_KEY = "fileInfoList"; 113 114 // PRIVATE // 115 private BaseInfo fBaseInfo; 116 private Map<File, List<FileInfo>> fAllFiles = new LinkedHashMap<File, List<FileInfo>>(); 117 private long fNumFiles; 118 private long fTotalSize; 119 private long fNumSourceFiles; 120 private long fNumJavaFiles; 121 private long fNumImageFiles; 122 private long fNumMarkupFiles; 123 124 private static final ResponsePage FORWARD = new ResponsePage("Source Code Base Info", "view.jsp", BaseInfoAction.class); 125 private static final ResponsePage REDIRECT = new ResponsePage("BaseInfoAction.do?Operation=Show"); 126 private static final Logger fLogger = Util.getLogger(BaseInfoAction.class); 127 128 private Map<File, List<FileInfo>> listFileInfo() { 129 Map<File, List<FileInfo>> result = (Map<File, List<FileInfo>>)getFromSession(FILE_INFO_MAP_KEY); 130 return result; 131 } 132 133 private BaseInfo fetchBaseInfo(){ 134 BaseInfoDAO dao = new BaseInfoDAO(getRequestParser().getRequest()); 135 return dao.fetch(); 136 } 137 138 /** 139 * Recursively scan the base dir. When each file is encountered, it is examined. 140 * A FileInfo thing is created, and it is added to allFiles. 141 * Along the way, the various counters are incremented, in order to avoid 142 * possible performance problems with large numbers of files. 143 */ 144 private void scan(File aDirectory) { 145 assert (aDirectory.isDirectory()); 146 List<FileInfo> filesOnly = new ArrayList<FileInfo>(); 147 List<File> allInDirectory = Arrays.asList(aDirectory.listFiles()); 148 for(File file : allInDirectory){ 149 if(! file.isDirectory() ) { 150 addFile(filesOnly, file); 151 } 152 } 153 fAllFiles.put(aDirectory, filesOnly); 154 for( File dir : allInDirectory ) { 155 if ( dir.isDirectory() ) { 156 scan(dir); //recursive call!! 157 } 158 } 159 } 160 161 private void addFile(List<FileInfo> aFilesOnly, File aFile) { 162 if ( FileInfo.isIgnorable(aFile)) { 163 //do nothing 164 } 165 else{ 166 FileInfo fileInfo = new FileInfo(aFile); 167 aFilesOnly.add(fileInfo); 168 fNumFiles = fNumFiles + 1; 169 fTotalSize = fTotalSize + aFile.length(); 170 if ( fileInfo.isSourceFile() ) { 171 ++fNumSourceFiles; 172 } 173 if ( fileInfo.isJavaSourceFile() ) { 174 ++fNumJavaFiles; 175 } 176 if ( fileInfo.isImageFile() ) { 177 ++fNumImageFiles; 178 } 179 if ( fileInfo.isMarkupFile() ) { 180 ++fNumMarkupFiles; 181 } 182 } 183 } 184 185 private List<FileInfo> asSortedList(Map<File, List<FileInfo>> aAllFiles){ 186 List<FileInfo> result = new ArrayList<FileInfo>(); 187 Set<File> dirs = aAllFiles.keySet(); 188 for ( File dir : dirs ) { 189 List<FileInfo> files = aAllFiles.get(dir); 190 result.addAll(files); 191 } 192 Collections.sort(result, byTotalSize()); 193 return result; 194 } 195 196 private Comparator<FileInfo> byTotalSize() { 197 return new Comparator<FileInfo>() { 198 public int compare(FileInfo aThis, FileInfo aThat) { 199 return aThis.getSize().compareTo(aThat.getSize()); 200 } 201 }; 202 } 203 }