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
17package com.google.doclava;
18
19import com.google.clearsilver.jsilver.JSilver;
20import com.google.clearsilver.jsilver.data.Data;
21import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
22import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
23import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
24import java.io.File;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.io.OutputStreamWriter;
28import java.net.URL;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.LinkedHashSet;
33import java.util.List;
34import java.util.Map;
35import 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 */
42public 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