View Javadoc

1   package net.sf.jlayercheck.util;
2   
3   import java.io.File;
4   import java.io.FileOutputStream;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.PrintWriter;
8   import java.net.URL;
9   import java.util.Map;
10  import java.util.Set;
11  import java.util.TreeMap;
12  import java.util.logging.Logger;
13  
14  import javax.imageio.ImageIO;
15  
16  import net.sf.jlayercheck.util.exceptions.CycleFoundException;
17  import net.sf.jlayercheck.util.exceptions.OrphanedSearchException;
18  import net.sf.jlayercheck.util.exceptions.OverlappingModulesDefinitionException;
19  import net.sf.jlayercheck.util.graph.GraphModuleDependencies;
20  import net.sf.jlayercheck.util.model.ClassDependency;
21  import de.java2html.Java2Html;
22  import de.java2html.options.JavaSourceConversionOptions;
23  
24  /**
25   * Creates HTML output of the retrieved dependeny information.
26   * 
27   * @author webmaster@earth3d.org
28   */
29  public class HTMLOutput {
30  	protected static Logger logger = Logger.getLogger("JLayerCheck");
31  	
32  	/**
33  	 * The directory to write the HTML output to.
34  	 */
35  	protected String outputDir;
36  	
37  	public HTMLOutput(String outputDir) {
38  		this.outputDir = outputDir;
39  	}
40  	
41      /**
42       * Writes the dependency information that was found into human readable
43       * HTML files containing the Java source code. The lines that are responsible
44       * for the illegal dependencies are marked.
45       * 
46       * @param dv the DependencyVisitor containing the dependency information
47       * @param xcp the configuration that determines the architecture
48       * @throws IOException
49       */
50  	public void write(DependencyVisitor dv, XMLConfiguration xcp) throws IOException {
51  		// create necessary output directories
52  		new File(outputDir).mkdirs();
53  		
54  		// open output stream
55  		FileOutputStream fos = new FileOutputStream(outputDir+File.separator+"unspecified.html");
56  		PrintWriter pw = new PrintWriter(fos);
57  		
58  		// load and parse configuration, class and java files
59  		Map<String, URL> javaSources = new TreeMap<String, URL>();
60  		javaSources.putAll(xcp.getClassSources().get(0).getSourceFiles());
61  
62  		Set<String> unspecifiedPackages;
63  		try {
64  			unspecifiedPackages = xcp.getUnspecifiedPackages(dv.getDependencies());
65  			Map<String, Map<String, ClassDependency>> unallowedDependencies = xcp.getUnallowedDependencies(dv.getDependencies()); 
66  
67  			// copy images to destination directory
68  			copyImage("/error.png", "images/error.png", outputDir);
69  			copyImage("/package.png", "images/package.png", outputDir);
70  			copyImage("/class.png", "images/class.png", outputDir);
71  			copyImage("/list.png", "images/list.png", outputDir);
72  			copyImage("/jlayercheck.css", "jlayercheck.css", outputDir);
73  
74  			// find violations
75  			pw.println("<html><head><title>Unspecified packages</title></head><body>");
76  			for(String classPackageName : unspecifiedPackages) {
77  				classPackageName = formatPackageName(classPackageName);
78  				pw.println("Warning: Package "+classPackageName+" has no module.<br/>");
79  			}
80  			pw.println("</body>");
81  			pw.close();
82  			fos.close();
83  
84  			Map<String, URL> sourceFiles = xcp.getAllClassSources();
85  
86  			fos = new FileOutputStream(outputDir+File.separator+"violations.html");
87  			pw = new PrintWriter(fos);
88  			
89  			pw.println("<html><head><title>Dependency violations</title>");
90  			pw.println("<style type=\"text/css\" media=\"all\">@import \"jlayercheck.css\";</style>");
91  			pw.println("</head><body>");
92  			
93  			// write module dependencies
94  			pw.println("<h1>Module dependencies:</h1>");
95  			try {
96  				GraphModuleDependencies gmd = new GraphModuleDependencies(xcp.getModuleDependencies());
97  				ImageIO.write(gmd.getImage(), "png", new File(outputDir+File.separator+"images"+File.separator+"hierarchy.png"));
98  				pw.println("<img src=\"images/hierarchy.png\" />");
99  			} catch (CycleFoundException e) {
100 				pw.println("<b>The module dependency graph contains cycles (which should be removed)!</b>");
101 			}
102 			
103 			// write dependency violations
104 			writeDependencyViolations(dv, xcp, pw, unallowedDependencies, sourceFiles);
105 
106 			// write orphaned classes information
107 			writeOrphanedClasses(dv, xcp, pw);
108 
109 			pw.println("</body>");
110 			pw.close();
111 			fos.close();
112 		} catch (OverlappingModulesDefinitionException e1) {
113 			pw.println("<html><head></head><body>Configuration error: "+e1.getMessage()+"</body>");
114 			pw.close();
115 			fos.close();
116 		}
117 	}
118 
119 	/**
120 	 * Writes a list of dependency violations to the given PrintWriter using html.
121 	 * It shows the classes that violate the architecture by accessing a module
122 	 * which they are not allowed to access.
123 	 * 
124 	 * @param dv
125 	 * @param xcp
126 	 * @param pw
127 	 * @param unallowedDependencies
128 	 * @param sourceFiles
129 	 * @throws IOException
130 	 * @throws OverlappingModulesDefinitionException
131 	 */
132 	protected void writeDependencyViolations(DependencyVisitor dv, XMLConfiguration xcp, PrintWriter pw, Map<String, Map<String, ClassDependency>> unallowedDependencies, Map<String, URL> sourceFiles) throws IOException, OverlappingModulesDefinitionException {
133 		pw.println("<h1>Dependency violations by packages:</h1>");
134 		for(String packagename : dv.getPackages().keySet()) {
135 			boolean wrotePackageHeader = false;
136 			for(String classname : dv.getPackages().get(packagename)) {
137 				String classmodule = xcp.getMatchingModule(classname);
138 
139 				if (unallowedDependencies.get(classname) != null) {
140 					if (!wrotePackageHeader) {
141 						wrotePackageHeader = true;
142 						pw.println("<br/>");
143 						pw.println("<h2><img src=\"images/package.png\" /> Package "+formatPackageName(packagename)+"</h2>");
144 					}
145 
146 					// Create link to sources
147 					String link = classname.replaceAll("/", "_")+".html";
148 					pw.println("<h3><img src=\"images/class.png\" /> Class <a href=\""+link+"\">"+formatPackageName(classname)+"</a> ("+classmodule+")</h3>");
149 					pw.println("<ul>");
150 					Map<Integer, String> markedLines = new TreeMap<Integer, String>();
151 					for(String dependency : unallowedDependencies.get(classname).keySet()) {
152 						String dependencyPackageName = StringUtils.getPackageName(dependency);
153 
154 						String dependencymodule = xcp.getPackageModules().get(dependencyPackageName);
155 
156 //							System.out.print("Class "+classname+" ("+classmodule+") must not use class "+dependency+" ("+dependencymodule+") in line ");
157 						pw.println("<li>"+formatPackageName(dependency)+" ("+dependencymodule+")</li>");
158 
159 						logger.finer("class="+classname+" dep="+dependency);
160 						for(int line : unallowedDependencies.get(classname).get(dependency).getLineNumbers()) {
161 							logger.finest(" "+line);
162 							markedLines.put(line, "must not depend on "+formatPackageName(dependency)+" ("+dependencymodule+")");
163 						}
164 						logger.finest("");
165 					}
166 					pw.println("</ul>");
167 
168 					// write sources
169 					URL url = sourceFiles.get(classname);
170 					if (url != null) {
171 						String content = readURL(url);
172 						JavaSourceConversionOptions options = JavaSourceConversionOptions.getDefault();
173 						options.setShowLineNumbers(true);
174 						content = Java2Html.convertToHtml(content, options);
175 
176 						writeSourceFile(outputDir+File.separator+link,
177 								content,
178 								markedLines);
179 					}
180 				}
181 			}
182 		}
183 	}
184 
185 	protected void writeOrphanedClasses(DependencyVisitor dv, XMLConfiguration xcp, PrintWriter pw) {
186 		Set<String> orphanedClasses;
187 		pw.println("<br/>");
188 		pw.println("<h1><img src=\"images/class.png\" /> Orphaned classes:</h1>");
189 		pw.println("<ul>");
190 		try {
191 			orphanedClasses = xcp.getOrphanedClasses(dv.getDependencies());
192 			for(String classname : orphanedClasses) {
193 				pw.println("<li>" + formatPackageName(classname) + "</li>");
194 			}
195 		} catch (OrphanedSearchException e) {
196 			e.printStackTrace();
197 
198 			pw.println("<b>An error ocurred: "+e.getMessage()+"</b>");
199 		}
200 		pw.println("</ul>");
201 	}
202     
203     /**
204      * Copies the names resource to the given destination directory.
205      * 
206      * @param resourceName the name of the resource to load
207      * @param filename the filename to save to
208      * @param outputDir the destination directory
209      * @throws IOException 
210      */
211 	protected void copyImage(String resourceName, String filename, String outputDir) throws IOException {
212         new File(outputDir, filename).getParentFile().mkdirs();
213         
214         InputStream is = getClass().getResourceAsStream(resourceName);
215         FileOutputStream fos = new FileOutputStream(new File(outputDir, filename));
216         while(is.available() > 0) {
217             byte content[] = new byte[is.available()];
218             is.read(content);
219             fos.write(content);
220         }
221         
222         is.close();
223         fos.close();
224     }
225 
226     /**
227      * Writes the given java source into the given file. The syntax is highlighted and the given
228      * marked lines are marked with small error symbols.
229      * 
230      * @param filename output filename
231      * @param content java source
232      * @param markedLines a Map containing the line number and the message for this line
233      * @throws IOException
234      */
235     protected void writeSourceFile(String filename, String content, Map<Integer, String> markedLines)  throws IOException {
236         FileOutputStream fos = new FileOutputStream(filename);
237         PrintWriter pw = new PrintWriter(fos);
238 //        pw.write(content);
239 //        pw.close();
240 //        fos.close();
241         
242 //        if (true) return;
243         content = content.substring(content.indexOf("<code>")+6);
244         content = content.substring(0, content.lastIndexOf("</code>"));
245         
246         int linenumber = 1;
247         pw.println("<html><head><title>"+filename+"</title>");
248         pw.println("<style type=\"text/css\" media=\"all\">@import \"jlayercheck.css\";</style>");
249         
250         copyImage("/yahoo-debug.js", "yahoo-debug.js", outputDir);
251         copyImage("/event-debug.js", "event-debug.js", outputDir);
252         copyImage("/dom-debug.js", "dom-debug.js", outputDir);
253         copyImage("/jlayercheck.js", "jlayercheck.js", outputDir);
254 
255         pw.println("<script src='yahoo-debug.js'></script>");
256         pw.println("<script src='event-debug.js'></script>");
257         pw.println("<script src='dom-debug.js'></script>");
258         pw.println("<script src='jlayercheck.js'></script>");
259         pw.println("<script>YAHOO.util.Event.addListener(window, 'load', jlayercheckInit);</script>");
260         
261         pw.println("</head><body>");
262         pw.println("<div align=\"left\" class=\"java\"><table border=\"0\" cellpadding=\"3\" cellspacing=\"0\" bgcolor=\"#ffffff\">");
263 //        pw.println("<table>");
264         while(content.length()>0) {
265             int lastpos = content.indexOf("<br />");
266             String token = "";
267             if (lastpos>=0) {
268                 token = content.substring(0, lastpos);
269                 content = content.substring(lastpos + 6);
270             } else {
271                 token = content;
272                 content = "";
273             }
274             pw.print("<tr><td>");
275             if (markedLines.containsKey(linenumber)) {
276             	pw.print("<div class='msgErr'>");
277                 pw.print("<img class='msgErrImg' src=\"images/error.png\" title=\""+markedLines.get(linenumber)+"\" />");
278                 pw.print("<div class='msgErrText' id='errText" + linenumber + "'>");
279                 pw.print(markedLines.get(linenumber));
280                 pw.print("</div>");
281                 pw.print("</div>");
282             }
283             pw.print("</td><td nowrap=\"nowrap\" valign=\"top\" align=\"left\">");
284             pw.println("<code>");
285             pw.write(token);
286             pw.println("</code>");
287             pw.print("</td></tr>");
288             linenumber++;
289         }
290         pw.println("</table></div>");
291         pw.println("</body>");
292         pw.close();
293         fos.close();
294     }
295 
296     private String readURL(URL url) throws IOException {
297         String result = "";
298         
299         InputStream is = url.openStream();
300         while(is.available() > 0) {
301             byte content[] = new byte[is.available()];
302             is.read(content);
303 
304             result = result + new String(content);
305         }
306         
307         return result;
308         
309     }
310 
311     /**
312 	 * Replaces "/" by "." to create a package name in the format the user expects
313 	 * to see.
314 	 * 
315 	 * @param classPackageName
316 	 * @return
317 	 */
318 	public static String formatPackageName(String classPackageName) {
319 		return classPackageName.replaceAll("/", ".");
320 	}
321 }