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 
main(String[] args)47   public static void main(String[] args) {
48     new DoclavaDiff(args).generateSite();
49   }
50 
DoclavaDiff(String[] args)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 
generateSite()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    */
generateHdf()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    */
knownPackages(List<FederatedSite> sites)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    */
knownClassesForPackage(String pkg, List<FederatedSite> sites)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    */
knownMethodsForClass(String qualifiedClassName, List<FederatedSite> sites)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    */
agreeOnPackage(String pkg, List<FederatedSite> sites)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    */
agreeOnClass(String qualifiedClassName, List<FederatedSite> sites)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    */
agreeOnMethod(String qualifiedClassName, MethodInfo method, List<FederatedSite> sites)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    */
packageUniqueToSite(String pkg, List<FederatedSite> sites)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    */
classUniqueToSite(String qualifiedClassName, List<FederatedSite> sites)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 
generateHtml(String template, Data data, File file)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