This project has retired. For details please refer to its Attic page.
Source code
001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.tang.util;
020
021import org.apache.reef.tang.ExternalConstructor;
022import org.apache.reef.tang.JavaClassHierarchy;
023import org.apache.reef.tang.Tang;
024import org.apache.reef.tang.annotations.*;
025import org.apache.reef.tang.exceptions.ClassHierarchyException;
026import org.apache.reef.tang.exceptions.NameResolutionException;
027import org.apache.reef.tang.formats.ConfigurationModule;
028import org.apache.reef.tang.formats.ConfigurationModuleBuilder;
029import org.apache.reef.tang.types.*;
030import org.apache.reef.tang.util.walk.AbstractClassHierarchyNodeVisitor;
031import org.apache.reef.tang.util.walk.NodeVisitor;
032import org.apache.reef.tang.util.walk.Walk;
033import org.reflections.Reflections;
034import org.reflections.scanners.MethodAnnotationsScanner;
035import org.reflections.scanners.MethodParameterScanner;
036import org.reflections.scanners.SubTypesScanner;
037import org.reflections.scanners.TypeAnnotationsScanner;
038
039import javax.inject.Inject;
040import java.io.*;
041import java.lang.reflect.Field;
042import java.lang.reflect.Modifier;
043import java.net.MalformedURLException;
044import java.net.URL;
045import java.util.Map;
046import java.util.Map.Entry;
047import java.util.Set;
048import java.util.TreeSet;
049import java.util.logging.Logger;
050
051/**
052 * Tang Static Analytics Tool.
053 */
054public class Tint {
055  private static final String SETTERS = "setters";
056  private static final String USES = "uses";
057  private static final String FULLNAME = "fullName";
058  private final JavaClassHierarchy ch;
059  private final Map<Field, ConfigurationModule> modules = new MonotonicHashMap<>();
060  private final MonotonicMultiMap<String, String> setters = new MonotonicMultiMap<>();
061  // map from thing that was used to user of the thing.
062  private final MonotonicMultiMap<String, String> usages = new MonotonicMultiMap<>();
063  private final Set<ClassNode<?>> knownClasses = new MonotonicSet<>();
064  private final Set<String> divs = new MonotonicSet<>();
065
066  private static final Logger LOG = Logger.getLogger(Tint.class.getName());
067
068  {
069    divs.add("doc");
070    divs.add(USES);
071    divs.add(SETTERS);
072
073  }
074
075  public Tint() {
076    this(new URL[0]);
077  }
078
079  public Tint(final URL[] jars) {
080    this(jars, false);
081  }
082
083  @SuppressWarnings("unchecked")
084  public Tint(final URL[] jars, final boolean checkTang) {
085    final Object[] args = new Object[jars.length + 6];
086    for (int i = 0; i < jars.length; i++) {
087      args[i] = jars[i];
088    }
089    args[args.length - 1] = new TypeAnnotationsScanner();
090    args[args.length - 2] = new SubTypesScanner();
091    args[args.length - 3] = new MethodAnnotationsScanner();
092    args[args.length - 4] = new MethodParameterScanner();
093    args[args.length - 5] = "com.microsoft";
094    args[args.length - 6] = "org.apache";
095    final Reflections r = new Reflections(args);
096//    Set<Class<?>> classes = new MonotonicSet<>();
097    final Set<String> strings = new TreeSet<>();
098    final Set<String> moduleBuilders = new MonotonicSet<>();
099
100    // Workaround bug in Reflections by keeping things stringly typed, and using Tang to parse them.
101//  Set<Constructor<?>> injectConstructors = (Set<Constructor<?>>)(Set)r.getMethodsAnnotatedWith(Inject.class);
102//  for(Constructor<?> c : injectConstructors) {
103//    classes.add(c.getDeclaringClass());
104//  }
105    final Set<String> injectConstructors =
106        r.getStore().getConstructorsAnnotatedWith(ReflectionUtilities.getFullName(Inject.class));
107    for (final String s : injectConstructors) {
108      strings.add(s.replaceAll("\\.<.+$", ""));
109    }
110    final Set<String> parameterConstructors =
111        r.getStore().get(MethodParameterScanner.class, ReflectionUtilities.getFullName(Parameter.class));
112    for (final String s : parameterConstructors) {
113      strings.add(s.replaceAll("\\.<.+$", ""));
114    }
115//    Set<Class> r.getConstructorsWithAnyParamAnnotated(Parameter.class);
116//    for(Constructor<?> c : parameterConstructors) {
117//      classes.add(c.getDeclaringClass());
118//    }
119    final Set<String> defaultStrings =
120        r.getStore().get(TypeAnnotationsScanner.class, ReflectionUtilities.getFullName(DefaultImplementation.class));
121    strings.addAll(defaultStrings);
122    strings.addAll(r.getStore().get(TypeAnnotationsScanner.class,
123        ReflectionUtilities.getFullName(NamedParameter.class)));
124    strings.addAll(r.getStore().get(TypeAnnotationsScanner.class, ReflectionUtilities.getFullName(Unit.class)));
125//    classes.addAll(r.getTypesAnnotatedWith(DefaultImplementation.class));
126//    classes.addAll(r.getTypesAnnotatedWith(NamedParameter.class));
127//    classes.addAll(r.getTypesAnnotatedWith(Unit.class));
128
129    strings.addAll(r.getStore().get(SubTypesScanner.class, ReflectionUtilities.getFullName(Name.class)));
130
131    moduleBuilders.addAll(r.getStore().get(SubTypesScanner.class,
132        ReflectionUtilities.getFullName(ConfigurationModuleBuilder.class)));
133//    classes.addAll(r.getSubTypesOf(Name.class));
134
135    ch = Tang.Factory.getTang().getDefaultClassHierarchy(jars,
136        (Class<? extends ExternalConstructor<?>>[]) new Class[0]);
137//    for(String s : defaultStrings) {
138//      if(classFilter(checkTang, s)) {
139//        try {
140//          ch.getNode(s);
141//        } catch(ClassHierarchyException | NameResolutionException | ClassNotFoundException e) {
142//          System.err.println(e.getMessage());
143//        }
144//      }
145//    }
146
147    for (final String s : strings) {
148      if (classFilter(checkTang, s)) {
149        try {
150          ch.getNode(s);
151        } catch (ClassHierarchyException | NameResolutionException e) {
152          System.err.println(e.getMessage());
153        }
154      }
155    }
156    for (final String s : moduleBuilders) {
157      if (classFilter(checkTang, s)) {
158        try {
159          ch.getNode(s);
160        } catch (ClassHierarchyException | NameResolutionException e) {
161          e.printStackTrace();
162        }
163      }
164    }
165
166    final NodeVisitor<Node> v = new AbstractClassHierarchyNodeVisitor() {
167
168      @Override
169      public boolean visit(final NamedParameterNode<?> node) {
170        final String nodeS = node.getFullName();
171        for (final String s : node.getDefaultInstanceAsStrings()) {
172          if (!usages.contains(s, nodeS)) {
173            usages.put(s, nodeS);
174          }
175        }
176        return true;
177      }
178
179      @Override
180      public boolean visit(final PackageNode node) {
181        return true;
182      }
183
184      @Override
185      public boolean visit(final ClassNode<?> node) {
186        final String nodeS = node.getFullName();
187        for (final ConstructorDef<?> d : node.getInjectableConstructors()) {
188          for (final ConstructorArg a : d.getArgs()) {
189            if (a.getNamedParameterName() != null &&
190                !usages.contains(a.getNamedParameterName(), nodeS)) {
191              usages.put(a.getNamedParameterName(), nodeS);
192            }
193          }
194        }
195        if (!knownClasses.contains(node)) {
196          knownClasses.add(node);
197        }
198        return true;
199      }
200    };
201    int numClasses;
202    do {
203      numClasses = knownClasses.size();
204
205      Walk.preorder(v, null, ch.getNamespace());
206
207      for (final ClassNode<?> cn : knownClasses) {
208        try {
209          final String s = cn.getFullName();
210          if (classFilter(checkTang, s)) {
211            final Class<?> c = ch.classForName(s);
212            processDefaultAnnotation(c);
213            processConfigurationModules(c);
214          }
215        } catch (final ClassNotFoundException e) {
216          e.printStackTrace();
217        }
218      }
219
220      for (final Entry<Field, ConfigurationModule> entry: modules.entrySet()) {
221        final String fS = ReflectionUtilities.getFullName(entry.getKey());
222        final Set<NamedParameterNode<?>> nps = entry.getValue().getBoundNamedParameters();
223        for (final NamedParameterNode<?> np : nps) {
224          final String npS = np.getFullName();
225          if (!setters.contains(npS, fS)) {
226            setters.put(npS, fS);
227          }
228        }
229      }
230    } while (numClasses != knownClasses.size()); // Note naive fixed point evaluation here.  Semi-naive would be faster.
231
232  }
233
234  public static String stripCommonPrefixes(final String s) {
235    return
236        stripPrefixHelper2(
237            stripPrefixHelper2(s, "java.lang"),
238            "java.util");
239  }
240
241  public static String stripPrefixHelper2(final String s, final String prefix) {
242    final String[] pretok = prefix.split("\\.");
243    final String[] stok = s.split("\\.");
244    final StringBuffer sb = new StringBuffer();
245    int i;
246    for (i = 0; i < pretok.length && i < stok.length; i++) {
247      if (pretok[i].equals(stok[i])) {
248        sb.append(pretok[i].charAt(0) + ".");
249      } else {
250        break;
251      }
252    }
253    for (; i < stok.length - 1; i++) {
254      sb.append(stok[i] + ".");
255    }
256    sb.append(stok[stok.length - 1]);
257    return sb.toString();
258  }
259
260  /*  public static String stripPrefixHelper(String s, String prefix) {
261      if(!"".equals(prefix) && s.startsWith(prefix+".")) {
262        try {
263          String suffix = s.substring(prefix.length()+1);
264          if(suffix.matches("^[A-Z].+")){
265            return suffix;
266          } else {
267            String shorten = prefix.replaceAll("([^.])[^.]+", "$1");
268            return shorten + "." + suffix;
269          }
270        } catch(StringIndexOutOfBoundsException e) {
271          throw new RuntimeException("Couldn't strip " + prefix + " from " + s, e);
272        }
273      } else {
274        return s;
275      }
276    }*/
277  public static String stripPrefix(final String s, final String prefix) {
278    return stripPrefixHelper2(stripPrefixHelper2(stripPrefixHelper2(
279        stripCommonPrefixes(stripPrefixHelper2(s, prefix)),
280        "org.apache.reef"), "org.apache.reef"), "org.apache.reef.wake");
281  }
282
283  /**
284   * @param args
285   * @throws FileNotFoundException
286   * @throws MalformedURLException
287   */
288  public static void main(final String[] args)
289      throws FileNotFoundException, MalformedURLException, UnsupportedEncodingException {
290    int i = 0;
291    String doc = null;
292    String jar = null;
293    boolean tangTests = false;
294    while (i < args.length) {
295      if (args[i].equals("--doc")) {
296        i++;
297        doc = args[i];
298      } else if (args[i].equals("--jar")) {
299        i++;
300        jar = args[i];
301      } else if (args[i].equals("--tang-tests")) {
302        tangTests = true;
303      }
304
305      i++;
306    }
307
308    final Tint t;
309    if (jar != null) {
310      final File f = new File(jar);
311      if (!f.exists()) {
312        throw new FileNotFoundException(jar);
313      }
314      t = new Tint(new URL[]{f.toURI().toURL()}, tangTests);
315    } else {
316      t = new Tint(new URL[0], tangTests);
317    }
318
319    if (doc != null) {
320      try (final PrintStream out = new PrintStream(doc, "UTF-8")) {
321        out.println("<html><head><title>TangDoc</title>");
322
323        out.println("<style>");
324        out.println("body { font-family: 'Segoe UI', 'Helvetica'; font-size:12pt; font-weight: 200; " +
325            "margin: 1em; column-count: 2; }");
326        out.println(".package { font-size:18pt; font-weight: 500; column-span: all; }");
327//      out.println(".class { break-after: never; }");
328//      out.println(".doc { break-before: never; }");
329        out.println(".decl-margin { padding: 8pt; break-inside: avoid; }");
330        out.println(".module-margin { padding: 8pt; column-span: all; break-inside: avoid; }");
331        out.println(".decl { background-color: aliceblue; padding: 6pt;}");
332        out.println(".fullName { font-size: 11pt; font-weight: 400; }");
333        out.println(".simpleName { font-size: 11pt; font-weight: 400; }");
334        out.println(".constructorArg { padding-left: 16pt; }");
335        out.println("." + SETTERS + " { padding-top: 6pt; font-size: 10pt; }");
336        out.println("." + USES + " { padding-top: 6pt; font-size: 10pt; }");
337        out.println("pre { font-size: 10pt; }");
338        out.println("</style>");
339
340        out.println("</head><body>");
341
342        String currentPackage = "";
343        for (final Node n : t.getNamesUsedAndSet()) {
344          final String fullName = n.getFullName();
345          final String[] tok = fullName.split("\\.");
346          final StringBuffer sb = new StringBuffer(tok[0]);
347          for (int j = 1; j < tok.length; j++) {
348            if (tok[j].matches("^[A-Z].*") || j > 5) {
349              break;
350            } else {
351              sb.append("." + tok[j]);
352            }
353          }
354          final String pack = sb.toString();
355          if (!currentPackage.equals(pack)) {
356            currentPackage = pack;
357            out.println(t.endPackage());
358            out.println(t.startPackage(currentPackage));
359          }
360          if (n instanceof NamedParameterNode<?>) {
361            out.println(t.toHtmlString((NamedParameterNode<?>) n, currentPackage));
362          } else if (n instanceof ClassNode<?>) {
363            out.println(t.toHtmlString((ClassNode<?>) n, currentPackage));
364          } else {
365            throw new IllegalStateException("The node is neither ClassNode nor NamedParameterNode."
366                    + "The node variable n has this value: " + n.getFullName());
367          }
368        }
369        out.println("</div>");
370        out.println(t.endPackage());
371        out.println("<div class='package'>Module definitions</div>");
372        for (final Field f : t.modules.keySet()) {
373          final String moduleName = ReflectionUtilities.getFullName(f);
374//        String declaringClassName = ReflectionUtilities.getFullName(f.getDeclaringClass());
375          out.println("<div class='module-margin' id='" + moduleName + "'><div class='decl'><span class='fullName'>" +
376              moduleName + "</span>");
377          out.println("<pre>");
378          final String conf = t.modules.get(f).toPrettyString();
379          final String[] tok = conf.split("\n");
380          for (final String line : tok) {
381            out.println(stripPrefix(line, "no.such.prefix")); //t.modules.get(f).toPrettyString());
382          }
383//        List<Entry<String,String>> lines = t.modules.get(f).toStringPairs();
384//        for(Entry<String,String> line : lines) {
385//          String k = t.stripPrefix(line.getKey(), declaringClassName);
386//          String v = t.stripPrefix(line.getValue(), declaringClassName);
387//          out.println(k+"="+v);
388//        }
389          out.println("</pre>");
390          out.println("</div></div>");
391        }
392
393        out.println("<div class='package'>Interfaces and injectable classes</div>");
394        for (final ClassNode<?> c : t.knownClasses) {
395          if (t.classFilter(tangTests, c.getFullName())) {
396            Class<?> clz = null;
397            try {
398              clz = t.ch.classForName(c.getFullName());
399            } catch (final ClassNotFoundException e) {
400              // TODO[JIRA REEF-864] Clarify handling in this case
401              e.printStackTrace();
402            }
403            final String typ = clz == null ? "undefined" : clz.isInterface() ? "interface" : "class";
404            out.println("<div class='module-margin' id='" + c.getFullName() + "'><div class='decl'>" +
405                "<span class='fullName'>" + typ + " " + c.getFullName() + "</span>");
406            for (final ConstructorDef<?> d : c.getInjectableConstructors()) {
407              out.println("<div class='uses'>" + c.getFullName() + "(");
408              for (final ConstructorArg a : d.getArgs()) {
409                if (a.getNamedParameterName() != null) {
410                  out.print("<div class='constructorArg'><a href='#" + a.getType() + "'>" +
411                      stripPrefix(a.getType(), "xxx") + "</a> <a href='#" + a.getNamedParameterName() + "'>" +
412                      a.getNamedParameterName() + "</a></div>");
413                } else {
414                  out.print("<div class='constructorArg'><a href='#" + a.getType() + "'>" +
415                      stripPrefix(a.getType(), "xxx") + "</a></div>");
416                }
417              }
418              out.println(")</div>");
419            }
420            out.println("</div></div>");
421          }
422/*
423      out.println("<h1>Default usage of classes and constants</h1>");
424      for(String s : t.usages.keySet()) {
425        out.println("<h2>" + s + "</h2>");
426        for(Node n : t.usages.getValuesForKey(s)) {
427          out.println("<p>" + n.getFullName() + "</p>");
428        }
429      } */
430        }
431        out.println("</body></html>");
432      }
433    }
434  }
435
436  private boolean classFilter(final boolean checkTang, final String s) {
437    return checkTang || !s.startsWith("org.apache.reef.tang");
438  }
439
440  private void processDefaultAnnotation(final Class<?> cmb) {
441    final DefaultImplementation di = cmb.getAnnotation(DefaultImplementation.class);
442    // XXX hack: move to helper method + unify with rest of Tang!
443    if (di != null) {
444      final String diName = di.value() == Void.class ? di.name() : ReflectionUtilities.getFullName(di.value());
445      final ClassNode<?> cn = (ClassNode<?>) ch.getNode(cmb);
446      final String cnS = cn.getFullName();
447      if (!usages.contains(diName, cnS)) {
448        usages.put(diName, cnS);
449        if (!knownClasses.contains(cn)) {
450          knownClasses.add(cn);
451        }
452      }
453    }
454  }
455
456  private void processConfigurationModules(final Class<?> cmb) {
457    for (final Field f : cmb.getFields()) {
458      if (ReflectionUtilities.isCoercable(ConfigurationModule.class, f.getType())) {
459        final int mod = f.getModifiers();
460        boolean ok = true;
461        if (Modifier.isPrivate(mod)) {
462          System.err.println("Found private ConfigurationModule " + f);
463          ok = false;
464        }
465        if (!Modifier.isFinal(mod)) {
466          System.err.println("Found non-final ConfigurationModule " + f);
467          ok = false;
468        }
469        if (!Modifier.isStatic(f.getModifiers())) {
470          System.err.println("Found non-static ConfigurationModule " + f);
471          ok = false;
472        }
473        if (ok) {
474//          System.err.println("OK: " + f);
475          try {
476            f.setAccessible(true);
477            final String fS = ReflectionUtilities.getFullName(f);
478            if (!modules.containsKey(f)) {
479              modules.put(f, (ConfigurationModule) (f.get(null)));
480              try {
481                modules.get(f).assertStaticClean();
482              } catch (final ClassHierarchyException e) {
483                System.err.println(fS + ": " + e.getMessage());
484              }
485              for (final Entry<String, String> e : modules.get(f).toStringPairs()) {
486                //System.err.println("e: " + e.getKey() + "=" + e.getValue());
487                try {
488                  final Node n = ch.getNode(e.getKey());
489                  if (!setters.contains(e.getKey(), fS)) {
490                    setters.put(e.getKey(), fS);
491                  }
492                  if (n instanceof ClassNode) {
493                    final ClassNode<?> cn = (ClassNode<?>) n;
494                    if (!knownClasses.contains(cn)) {
495                      knownClasses.add(cn);
496                    }
497                  }
498                } catch (final NameResolutionException ex) {
499                  LOG.warning("The class " + e.getValue() + " not found in the class hierarchy."
500                          + " The exception message: " + ex.getMessage());
501                }
502                try {
503                  final String s = e.getValue();
504                  final Node n = ch.getNode(s);
505                  if (!usages.contains(ReflectionUtilities.getFullName(f), s)) {
506                    //  System.err.println("Added usage: " + ReflectionUtilities.getFullName(f) + "=" + s);
507                    usages.put(s, ReflectionUtilities.getFullName(f));
508                  }
509                  if (n instanceof ClassNode) {
510                    final ClassNode<?> cn = (ClassNode<?>) n;
511                    if (!knownClasses.contains(cn)) {
512                      System.err.println("Added " + cn + " to known classes");
513                      knownClasses.add(cn);
514                    }
515                  }
516                } catch (final NameResolutionException ex) {
517                  LOG.warning("The class " + e.getValue() + " not found in the class hierarchy."
518                               + " The exception message: " + ex.getMessage());
519                }
520              }
521            }
522          } catch (final ExceptionInInitializerError e) {
523            System.err.println("Field " + ReflectionUtilities.getFullName(f) + ": " + e.getCause().getMessage());
524          } catch (final IllegalAccessException e) {
525            throw new RuntimeException(e);
526          }
527        }
528      }
529    }
530  }
531
532  public Set<NamedParameterNode<?>> getNames() {
533    final Set<NamedParameterNode<?>> names = new MonotonicSet<>();
534    final NodeVisitor<Node> v = new AbstractClassHierarchyNodeVisitor() {
535
536      @Override
537      public boolean visit(final NamedParameterNode<?> node) {
538        names.add(node);
539        return true;
540      }
541
542      @Override
543      public boolean visit(final PackageNode node) {
544        return true;
545      }
546
547      @Override
548      public boolean visit(final ClassNode<?> node) {
549        return true;
550      }
551    };
552    Walk.preorder(v, null, ch.getNamespace());
553    return names;
554  }
555
556  public Set<Node> getNamesUsedAndSet() {
557    final Set<Node> names = new MonotonicSet<>();
558    final Set<String> userKeys = usages.keySet();
559    final Set<String> usedKeys = usages.values();
560    final Set<String> setterKeys = setters.keySet();
561    final NodeVisitor<Node> v = new AbstractClassHierarchyNodeVisitor() {
562
563      @Override
564      public boolean visit(final NamedParameterNode<?> node) {
565        names.add(node);
566        return true;
567      }
568
569      @Override
570      public boolean visit(final PackageNode node) {
571        return true;
572      }
573
574      @Override
575      public boolean visit(final ClassNode<?> node) {
576        final String nodeS = node.getFullName();
577        if (userKeys.contains(nodeS)) {
578          names.add(node);
579        }
580        if (setterKeys.contains(nodeS)) {
581          names.add(node);
582        }
583        if (usedKeys.contains(nodeS) && !names.contains(node)) {
584          names.add(node);
585        }
586
587        return true;
588      }
589    };
590    Walk.preorder(v, null, ch.getNamespace());
591    return names;
592  }
593
594  public Set<String> getUsesOf(final Node name) {
595
596    return usages.getValuesForKey(name.getFullName());
597  }
598
599  public Set<String> getSettersOf(final Node name) {
600    return setters.getValuesForKey(name.getFullName());
601  }
602
603  public String toString(final NamedParameterNode<?> n) {
604    final StringBuilder sb = new StringBuilder("Name: " + n.getSimpleArgName() + " " + n.getFullName());
605    final String[] instances = n.getDefaultInstanceAsStrings();
606    if (instances.length != 0) {
607      sb.append(" = ");
608      sb.append(join(",", instances));
609    }
610    if (!n.getDocumentation().equals("")) {
611      sb.append(" //" + n.getDocumentation());
612    }
613    return sb.toString();
614  }
615
616  private String join(final String sep, final String[] s) {
617    if (s.length > 0) {
618      final StringBuffer sb = new StringBuffer(s[0]);
619      for (int i = 1; i < s.length; i++) {
620        sb.append(sep);
621        sb.append(s[i]);
622      }
623      return sb.toString();
624    } else {
625      return null;
626    }
627  }
628
629  public String cell(final String s, final String clazz) {
630    String str = s;
631    if (clazz.equals(USES) && str.length() > 0) {
632      str = "<em>Used by:</em><br>" + str;
633    }
634    if (clazz.equals(SETTERS) && str.length() > 0) {
635      str = "<em>Set by:</em><br>" + str;
636    }
637    if (clazz.equals(FULLNAME)) {
638      str = "&nbsp;" + str;
639    }
640    if (divs.contains(clazz)) {
641      return "<div class=\"" + clazz + "\">" + str + "</div>";
642    } else {
643      return "<span class=\"" + clazz + "\">" + str + "</span>";
644    }
645  }
646
647  public String cell(final StringBuffer sb, final String clazz) {
648    return cell(sb.toString(), clazz);
649  }
650
651  public String row(final StringBuffer sb) {
652    return sb.toString();
653  }
654
655  public String toHtmlString(final NamedParameterNode<?> n, final String pack) {
656    final String fullName = stripPrefix(n.getFullName(), pack);
657    final StringBuffer sb = new StringBuffer();
658
659    sb.append("<div id='" + n.getFullName() + "' class='decl-margin'>");
660    sb.append("<div class='decl'>");
661
662    sb.append(cell(n.getSimpleArgName(), "simpleName") + cell(fullName, FULLNAME));
663    final String instance;
664    final String[] instances = n.getDefaultInstanceAsStrings();
665    if (instances.length != 0) {
666      final StringBuffer sb2 = new StringBuffer(" = " + stripPrefix(instances[0], pack));
667      for (int i = 1; i < instances.length; i++) {
668        sb2.append("," + stripPrefix(instances[i], pack));
669      }
670      instance = sb2.toString();
671    } else {
672      instance = "";
673    }
674    sb.append(cell(instance, "instance"));
675    final StringBuffer doc = new StringBuffer();
676    if (!n.getDocumentation().equals("")) {
677      doc.append(n.getDocumentation());
678    }
679    sb.append(cell(doc, "doc"));
680    final StringBuffer uses = new StringBuffer();
681    for (final String u : getUsesOf(n)) {
682      uses.append("<a href='#" + u + "'>" + stripPrefix(u, pack) + "</a> ");
683    }
684    sb.append(cell(uses, USES));
685    final StringBuffer settersStr = new StringBuffer();
686    for (final String f : getSettersOf(n)) {
687      settersStr.append("<a href='#" + f + "'>" + stripPrefix(f, pack) + "</a> ");
688    }
689    sb.append(cell(settersStr, SETTERS));
690    sb.append("</div>");
691    sb.append("</div>");
692    return row(sb);
693  }
694
695  public String toHtmlString(final ClassNode<?> n, final String pack) {
696    final String fullName = stripPrefix(n.getFullName(), pack);
697
698    final String type;
699    try {
700      if (ch.classForName(n.getFullName()).isInterface()) {
701        type = "interface";
702      } else {
703        type = "class";
704      }
705    } catch (final ClassNotFoundException e) {
706      throw new RuntimeException(e);
707    }
708    final StringBuffer sb = new StringBuffer();
709
710    sb.append("<div class='decl-margin' id='" + n.getFullName() + "'>");
711    sb.append("<div class='decl'>");
712    sb.append(cell(type, "simpleName") + cell(fullName, FULLNAME));
713    final String instance;
714    if (n.getDefaultImplementation() != null) {
715      instance = " = " + stripPrefix(n.getDefaultImplementation(), pack);
716    } else {
717      instance = "";
718    }
719    sb.append(cell(instance, "simpleName"));
720    sb.append(cell("", "fullName")); // TODO[REEF-1118]: Support documentation string
721    final StringBuffer uses = new StringBuffer();
722    for (final String u : getUsesOf(n)) {
723      uses.append("<a href='#" + u + "'>" + stripPrefix(u, pack) + "</a> ");
724    }
725    sb.append(cell(uses, USES));
726    final StringBuffer settersStr = new StringBuffer();
727    for (final String f : getSettersOf(n)) {
728      settersStr.append("<a href='#" + f + "'>" + stripPrefix(f, pack) + "</a> ");
729    }
730    sb.append(cell(settersStr, SETTERS));
731    sb.append("</div>");
732    sb.append("</div>");
733    return row(sb);
734  }
735
736  public String startPackage(final String pack) {
737    return "<div class=\"package\">" + pack + "</div>";
738//    return "<tr><td colspan='6'><br><b>" + pack + "</b></td></tr>";
739  }
740
741  public String endPackage() {
742    return "";
743  }
744}