Home | History | Annotate | Download | only in doclava
      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