1 /* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.doclava; 18 19 import com.google.clearsilver.jsilver.JSilver; 20 import com.google.clearsilver.jsilver.data.Data; 21 import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader; 22 import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader; 23 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; 24 import java.io.File; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.io.OutputStreamWriter; 28 import java.net.URL; 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.HashMap; 32 import java.util.LinkedHashSet; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Set; 36 37 /** 38 * This class is used to generate a web page highlighting the differences and 39 * similarities among various Java libraries. 40 * 41 */ 42 public final class DoclavaDiff { 43 private final String outputDir; 44 private final JSilver jSilver; 45 private final List<FederatedSite> sites = new ArrayList<FederatedSite>(); 46 47 public static void main(String[] args) { 48 new DoclavaDiff(args).generateSite(); 49 } 50 51 public DoclavaDiff(String[] args) { 52 // TODO: options parsing 53 try { 54 sites.add(new FederatedSite("Android", new URL("http://manatee/doclava/android"))); 55 sites.add(new FederatedSite("GWT", new URL("http://manatee/doclava/gwt"))); 56 //sites.add(new FederatedSite("Crore", new URL("http://manatee/doclava/crore"))); 57 outputDir = "build"; 58 } catch (Exception e) { 59 throw new AssertionError(e); 60 } 61 62 // TODO: accept external templates 63 List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>(); 64 resourceLoaders.add(new FileSystemResourceLoader("assets/templates")); 65 66 ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders); 67 jSilver = new JSilver(compositeResourceLoader); 68 } 69 70 public void generateSite() { 71 Data data = generateHdf(); 72 generateHtml("diff.cs", data, new File(outputDir + "/diff.html")); 73 } 74 75 /** 76 * Creates an HDF with this structure: 77 * <pre> 78 * sites.0.name = projectA 79 * sites.0.url = http://proja.domain.com/reference 80 * sites.1.name = projectB 81 * sites.1.url = http://projb.domain.com 82 * packages.0.name = java.lang 83 * packages.0.sites.0.hasPackage = 1 84 * packages.0.sites.0.link = http://proja.domain.com/reference/java/lang 85 * packages.0.sites.1.hasPackage = 0 86 * packages.0.classes.0.qualifiedName = java.lang.Object 87 * packages.0.classes.0.sites.0.hasClass = 1 88 * packages.0.classes.0.sites.0.link = http://proja.domain.com/reference/java/lang/Object 89 * packages.0.classes.0.sites.1.hasClass = 0 90 * packages.0.classes.0.methods.0.signature = wait() 91 * packages.0.classes.0.methods.0.sites.0.hasMethod = 1 92 * packages.0.classes.0.methods.0.sites.0.link = http://proja.domain.com/reference/java/lang/Object#wait 93 * packages.0.classes.0.methods.0.sites.1.hasMethod = 0 94 * </pre> 95 */ 96 private Data generateHdf() { 97 Data data = jSilver.createData(); 98 99 data.setValue("triangle.opened", "../assets/templates/assets/images/triangle-opened.png"); 100 data.setValue("triangle.closed", "../assets/templates/assets/images/triangle-closed.png"); 101 102 int i = 0; 103 for (FederatedSite site : sites) { 104 String base = "sites." + (i++); 105 data.setValue(base + ".name", site.name()); 106 data.setValue(base + ".url", site.baseUrl().toString()); 107 } 108 109 List<String> allPackages = knownPackages(sites); 110 111 int p = 0; 112 for (String pkg : allPackages) { 113 PackageInfo packageInfo = new PackageInfo(pkg); 114 String packageBase = "packages." + (p++); 115 data.setValue(packageBase + ".name", pkg); 116 117 int s = 0; 118 for (FederatedSite site : sites) { 119 String siteBase = packageBase + ".sites." + (s++); 120 if (site.apiInfo().getPackages().containsKey(pkg)) { 121 data.setValue(siteBase + ".hasPackage", "1"); 122 data.setValue(siteBase + ".link", site.linkFor(packageInfo.htmlPage())); 123 } else { 124 data.setValue(siteBase + ".hasPackage", "0"); 125 } 126 } 127 128 if (packageUniqueToSite(pkg, sites)) { 129 continue; 130 } 131 132 List<String> packageClasses = knownClassesForPackage(pkg, sites); 133 int c = 0; 134 for (String qualifiedClassName : packageClasses) { 135 String classBase = packageBase + ".classes." + (c++); 136 data.setValue(classBase + ".qualifiedName", qualifiedClassName); 137 138 s = 0; 139 for (FederatedSite site : sites) { 140 String siteBase = classBase + ".sites." + (s++); 141 ClassInfo classInfo = site.apiInfo().findClass(qualifiedClassName); 142 if (classInfo != null) { 143 data.setValue(siteBase + ".hasClass", "1"); 144 data.setValue(siteBase + ".link", site.linkFor(classInfo.htmlPage())); 145 } else { 146 data.setValue(siteBase + ".hasClass", "0"); 147 } 148 } 149 150 if (agreeOnClass(qualifiedClassName, sites)) { 151 continue; 152 } 153 154 if (classUniqueToSite(qualifiedClassName, sites)) { 155 continue; 156 } 157 158 int m = 0; 159 List<MethodInfo> methods = knownMethodsForClass(qualifiedClassName, sites); 160 for (MethodInfo method : methods) { 161 if (agreeOnMethod(qualifiedClassName, method, sites)) { 162 continue; 163 } 164 165 String methodBase = classBase + ".methods." + (m++); 166 data.setValue(methodBase + ".signature", method.prettySignature()); 167 int k = 0; 168 for (FederatedSite site : sites) { 169 String siteBase = methodBase + ".sites." + (k++); 170 if (site.apiInfo().findClass(qualifiedClassName) == null) { 171 data.setValue(siteBase + ".hasMethod", "0"); 172 continue; 173 } 174 Map<String,MethodInfo> siteMethods 175 = site.apiInfo().findClass(qualifiedClassName).allMethods(); 176 if (siteMethods.containsKey(method.getHashableName())) { 177 data.setValue(siteBase + ".hasMethod", "1"); 178 data.setValue(siteBase + ".link", site.linkFor(method.htmlPage())); 179 } else { 180 data.setValue(siteBase + ".hasMethod", "0"); 181 } 182 } 183 } 184 } 185 } 186 187 return data; 188 } 189 190 /** 191 * Returns a list of all known packages from all sites. 192 */ 193 private List<String> knownPackages(List<FederatedSite> sites) { 194 Set<String> allPackages = new LinkedHashSet<String>(); 195 for (FederatedSite site : sites) { 196 Map<String, PackageInfo> packages = site.apiInfo().getPackages(); 197 for (String pkg : packages.keySet()) { 198 allPackages.add(pkg); 199 } 200 } 201 202 List<String> packages = new ArrayList<String>(allPackages); 203 Collections.sort(packages); 204 return packages; 205 } 206 207 /** 208 * Returns all known classes from all sites for a given package. 209 */ 210 private List<String> knownClassesForPackage(String pkg, List<FederatedSite> sites) { 211 Set<String> allClasses = new LinkedHashSet<String>(); 212 for (FederatedSite site : sites) { 213 PackageInfo packageInfo = site.apiInfo().getPackages().get(pkg); 214 if (packageInfo == null) { 215 continue; 216 } 217 HashMap<String, ClassInfo> classes = packageInfo.allClasses(); 218 for (Map.Entry<String, ClassInfo> entry : classes.entrySet()) { 219 allClasses.add(entry.getValue().qualifiedName()); 220 } 221 } 222 223 List<String> classes = new ArrayList<String>(allClasses); 224 Collections.sort(classes); 225 return classes; 226 } 227 228 /** 229 * Returns all known methods from all sites for a given class. 230 */ 231 private List<MethodInfo> knownMethodsForClass(String qualifiedClassName, 232 List<FederatedSite> sites) { 233 234 Map<String, MethodInfo> allMethods = new HashMap<String, MethodInfo>(); 235 for (FederatedSite site : sites) { 236 ClassInfo classInfo = site.apiInfo().findClass(qualifiedClassName); 237 if (classInfo == null) { 238 continue; 239 } 240 241 for (Map.Entry<String, MethodInfo> entry: classInfo.allMethods().entrySet()) { 242 allMethods.put(entry.getKey(), entry.getValue()); 243 } 244 } 245 246 List<MethodInfo> methods = new ArrayList<MethodInfo>(); 247 methods.addAll(allMethods.values()); 248 return methods; 249 } 250 251 /** 252 * Returns true if the list of sites all completely agree on the given 253 * package. All sites must possess the package, all classes it contains, and 254 * all methods of each class. 255 */ 256 private boolean agreeOnPackage(String pkg, List<FederatedSite> sites) { 257 for (FederatedSite site : sites) { 258 if (site.apiInfo().getPackages().get(pkg) == null) { 259 return false; 260 } 261 } 262 263 List<String> classes = knownClassesForPackage(pkg, sites); 264 for (String clazz : classes) { 265 if (!agreeOnClass(clazz, sites)) { 266 return false; 267 } 268 } 269 return true; 270 } 271 272 /** 273 * Returns true if the list of sites all agree on the given class. Each site 274 * must have the class and agree on its methods. 275 */ 276 private boolean agreeOnClass(String qualifiedClassName, List<FederatedSite> sites) { 277 List<MethodInfo> methods = knownMethodsForClass(qualifiedClassName, sites); 278 for (MethodInfo method : methods) { 279 if (!agreeOnMethod(qualifiedClassName, method, sites)) { 280 return false; 281 } 282 } 283 return true; 284 } 285 286 /** 287 * Returns true if the list of sites all contain the given method. 288 */ 289 private boolean agreeOnMethod(String qualifiedClassName, MethodInfo method, 290 List<FederatedSite> sites) { 291 292 for (FederatedSite site : sites) { 293 ClassInfo siteClass = site.apiInfo().findClass(qualifiedClassName); 294 if (siteClass == null) { 295 return false; 296 } 297 298 if (!siteClass.supportsMethod(method)) { 299 return false; 300 } 301 } 302 return true; 303 } 304 305 /** 306 * Returns true if the given package is known to exactly one of the given sites. 307 */ 308 private boolean packageUniqueToSite(String pkg, List<FederatedSite> sites) { 309 int numSites = 0; 310 for (FederatedSite site : sites) { 311 if (site.apiInfo().getPackages().containsKey(pkg)) { 312 numSites++; 313 } 314 } 315 return numSites == 1; 316 } 317 318 /** 319 * Returns true if the given class is known to exactly one of the given sites. 320 */ 321 private boolean classUniqueToSite(String qualifiedClassName, List<FederatedSite> sites) { 322 int numSites = 0; 323 for (FederatedSite site : sites) { 324 if (site.apiInfo().findClass(qualifiedClassName) != null) { 325 numSites++; 326 } 327 } 328 return numSites == 1; 329 } 330 331 private void generateHtml(String template, Data data, File file) { 332 ClearPage.ensureDirectory(file); 333 334 OutputStreamWriter stream = null; 335 try { 336 stream = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); 337 String rendered = jSilver.render(template, data); 338 stream.write(rendered, 0, rendered.length()); 339 } catch (IOException e) { 340 System.out.println("error: " + e.getMessage() + "; when writing file: " + file.getAbsolutePath()); 341 } finally { 342 if (stream != null) { 343 try { 344 stream.close(); 345 } catch (IOException ignored) {} 346 } 347 } 348 } 349 } 350