/* * Copyright 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.inject.rebind.util; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.inject.rebind.binding.Dependency; import com.google.inject.Key; import com.google.inject.TypeLiteral; import java.lang.annotation.Annotation; import java.util.List; /** * Pretty-printer that formats internal types for human consumption in error * messages. * * <p>{@link #format(String, Object...)} acts like {@link String#format}, except * that it detects and pretty-prints the following argument types: * * <ul> * <li>{@link Class}: formatted as "org.example.Class$SubClass"</li> * <li>{@link Key}: formatted as "@org.example.Annotation org.example.Class$SubClass"</li> * <li>{@link List<Dependency>}: formatted as a dependency path preceded by a newline. If * the path begins at {@link Dependency.GINJECTOR}, that key is hidden, and the context * of the outgoing dependency is given as the context of the first key in the displayed * path.</li> * </ul> * * All other arguments are passed unchanged to {@link String#format}. */ public final class PrettyPrinter { private PrettyPrinter() { } /** * Log a pretty-printed message if the given log level is active. The message * is only formatted if it will be logged. */ public static void log(TreeLogger logger, TreeLogger.Type type, String formatString, Object... args) { if (logger.isLoggable(type)) { logger.log(type, format(formatString, args)); } } /** * Generate a string based on a format template as {@link String#format} * would, using the pretty-printing rules specified in the class * documentation. */ public static String format(String formatString, Object... args) { // Rewrites the varargs so that objects that need special formatting are // replaced with formatted strings, then hands off to {@link String#format}. Object[] formattedArgs = new Object[args.length]; for (int i = 0; i < args.length; ++i) { formattedArgs[i] = formatObject(args[i]); } return String.format(formatString, (Object[]) formattedArgs); } /** * Pretty-print a single object. */ private static Object formatObject(Object object) { if (object instanceof Class) { return formatArg((Class<?>) object); } else if (object instanceof Key) { return formatArg((Key<?>) object); } else if (object instanceof List) { List<?> list = (List<?>) object; // Empirically check if this is a List<Dependency>. boolean allDependencies = true; for (Object entry : list) { if (!(entry instanceof Dependency)) { allDependencies = false; break; } } if (allDependencies) { return formatArg((List<Dependency>) list); } else { return object; } } else { return object; } } private static String formatArg(Class<?> type) { // Make sure classes are formatted in a manner that's consistent with type // literals. return TypeLiteral.get(type).toString(); } private static String formatArg(Key<?> key) { StringBuilder builder = new StringBuilder(); formatArgTo(key, builder); return builder.toString(); } /** * Formats a list of dependencies as a dependency path; see the class * comments. */ private static String formatArg(List<Dependency> path) { StringBuilder builder = new StringBuilder(); formatArgTo(path, builder); return builder.toString(); } private static void formatArgTo(Key<?> key, StringBuilder builder) { Annotation annotation = key.getAnnotation(); if (annotation != null) { builder.append(annotation); builder.append(" "); } else if (key.getAnnotationType() != null) { builder.append("@"); builder.append(formatArg(key.getAnnotationType())); builder.append(" "); } builder.append(key.getTypeLiteral()); } /** * Formats a list of dependencies as a dependency path; see the class * comments. */ private static void formatArgTo(List<Dependency> path, StringBuilder builder) { if (path.isEmpty()) { return; } builder.append("\n"); boolean first = true; Key<?> previousTarget = null; // For sanity-checking. for (Dependency dependency : path) { Key<?> source = dependency.getSource(); Key<?> target = dependency.getTarget(); // Sanity-check. if (previousTarget != null && !previousTarget.equals(source)) { throw new IllegalArgumentException("Dependency list is not a path."); } // There are two possible overall shapes of the list: // // If it starts with GINJECTOR, we get this: // // Key1 [context] // -> Key2 [context] // ... // // Otherwise (e.g., if we're dumping a cycle), we get this: // // Key1 // -> Key2 [context] // -> Key3 [context] // ... if (first) { if (source == Dependency.GINJECTOR) { formatArgTo(target, builder); builder.append(String.format(" [%s]%n", dependency.getContext())); } else { formatArgTo(source, builder); builder.append("\n -> "); formatArgTo(target, builder); builder.append(String.format(" [%s]%n", dependency.getContext())); } first = false; } else { builder.append(" -> "); formatArgTo(target, builder); builder.append(String.format(" [%s]%n", dependency.getContext())); } previousTarget = target; } } }