diff --git a/spring-boot-samples/spring-boot-sample-simple/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-simple/src/main/resources/application.properties index 4dfe84cedc..b04cdc39b5 100644 --- a/spring-boot-samples/spring-boot-sample-simple/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-simple/src/main/resources/application.properties @@ -1 +1 @@ -name: Phil \ No newline at end of file +name: Phil diff --git a/spring-boot-samples/spring-boot-sample-simple/src/main/resources/banner.jpg b/spring-boot-samples/spring-boot-sample-simple/src/main/resources/banner.jpg new file mode 100644 index 0000000000..f196fed2c3 Binary files /dev/null and b/spring-boot-samples/spring-boot-sample-simple/src/main/resources/banner.jpg differ diff --git a/spring-boot-samples/spring-boot-sample-simple/src/main/resources/banner.txt b/spring-boot-samples/spring-boot-sample-simple/src/main/resources/banner.txt new file mode 100644 index 0000000000..2634c44793 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-simple/src/main/resources/banner.txt @@ -0,0 +1 @@ +${Ansi.GREEN} :: Sample application build with Spring Boot${spring-boot.formatted-version} ::${Ansi.DEFAULT} diff --git a/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java b/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java index ccd478404d..9c32a539ae 100644 --- a/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java +++ b/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java @@ -18,26 +18,24 @@ package org.springframework.boot; import java.awt.Color; import java.awt.Image; -import java.awt.color.ColorSpace; - import java.awt.image.BufferedImage; - +import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - import javax.imageio.ImageIO; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.boot.ansi.AnsiPropertySource; +import org.springframework.boot.ansi.AnsiBackground; +import org.springframework.boot.ansi.AnsiColor; +import org.springframework.boot.ansi.AnsiColors; +import org.springframework.boot.ansi.AnsiElement; +import org.springframework.boot.ansi.AnsiOutput; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertyResolver; -import org.springframework.core.env.PropertySourcesPropertyResolver; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -46,271 +44,137 @@ import org.springframework.util.Assert; * {@link Resource}. * * @author Craig Burke + * @author Phillip Webb + * @since 1.4.0 */ public class ImageBanner implements Banner { private static final Log log = LogFactory.getLog(ImageBanner.class); - private static final double RED_WEIGHT = 0.2126d; - private static final double GREEN_WEIGHT = 0.7152d; - private static final double BLUE_WEIGHT = 0.0722d; + private static final double[] RGB_WEIGHT = { 0.2126d, 0.7152d, 0.0722d }; - private static final int DEFAULT_MAX_WIDTH = 72; - private static final double DEFAULT_ASPECT_RATIO = 0.5d; - private static final boolean DEFAULT_DARK = false; + private static final char[] PIXEL = { ' ', '.', '*', ':', 'o', '&', '8', '#', '@' }; - private Resource image; - private Map colors = new HashMap(); + private static final int LUMINANCE_INCREMENT = 10; + + private static final int LUMINANCE_START = LUMINANCE_INCREMENT * PIXEL.length; + + private final Resource image; public ImageBanner(Resource image) { Assert.notNull(image, "Image must not be null"); Assert.isTrue(image.exists(), "Image must exist"); this.image = image; - colorsInit(); - } - - private void colorsInit() { - this.colors.put("BLACK", new Color(0, 0, 0)); - this.colors.put("RED", new Color(170, 0, 0)); - this.colors.put("GREEN", new Color(0, 170, 0)); - this.colors.put("YELLOW", new Color(170, 85, 0)); - this.colors.put("BLUE", new Color(0, 0, 170)); - this.colors.put("MAGENTA", new Color(170, 0, 170)); - this.colors.put("CYAN", new Color(0, 170, 170)); - this.colors.put("WHITE", new Color(170, 170, 170)); - - this.colors.put("BRIGHT_BLACK", new Color(85, 85, 85)); - this.colors.put("BRIGHT_RED", new Color(255, 85, 85)); - this.colors.put("BRIGHT_GREEN", new Color(85, 255, 85)); - this.colors.put("BRIGHT_YELLOW", new Color(255, 255, 85)); - this.colors.put("BRIGHT_BLUE", new Color(85, 85, 255)); - this.colors.put("BRIGHT_MAGENTA", new Color(255, 85, 255)); - this.colors.put("BRIGHT_CYAN", new Color(85, 255, 255)); - this.colors.put("BRIGHT_WHITE", new Color(255, 255, 255)); } @Override - public void printBanner(Environment environment, Class sourceClass, PrintStream out) { - String headlessProperty = System.getProperty("java.awt.headless"); + public void printBanner(Environment environment, Class sourceClass, + PrintStream out) { + String headless = System.getProperty("java.awt.headless"); try { System.setProperty("java.awt.headless", "true"); - BufferedImage sourceImage = ImageIO.read(this.image.getInputStream()); - - int maxWidth = environment.getProperty("banner.image.max-width", - Integer.class, DEFAULT_MAX_WIDTH); - Double aspectRatio = environment.getProperty("banner.image.aspect-ratio", - Double.class, DEFAULT_ASPECT_RATIO); - boolean invert = environment.getProperty("banner.image.dark", Boolean.class, - DEFAULT_DARK); - - BufferedImage resizedImage = resizeImage(sourceImage, maxWidth, aspectRatio); - String banner = imageToBanner(resizedImage, invert); - - PropertyResolver ansiResolver = getAnsiResolver(); - banner = ansiResolver.resolvePlaceholders(banner); - out.println(banner); + printBanner(environment, out); } catch (Exception ex) { log.warn("Image banner not printable: " + this.image + " (" + ex.getClass() + ": '" + ex.getMessage() + "')", ex); } finally { - System.setProperty("java.awt.headless", headlessProperty); + if (headless == null) { + System.clearProperty("java.awt.headless"); + } + else { + System.setProperty("java.awt.headless", headless); + } } } - private PropertyResolver getAnsiResolver() { - MutablePropertySources sources = new MutablePropertySources(); - sources.addFirst(new AnsiPropertySource("ansi", true)); - return new PropertySourcesPropertyResolver(sources); + private void printBanner(Environment environment, PrintStream out) + throws IOException { + PropertyResolver properties = new RelaxedPropertyResolver(environment, + "banner.image."); + int width = properties.getProperty("width", Integer.class, 76); + int heigth = properties.getProperty("height", Integer.class, 0); + int margin = properties.getProperty("margin", Integer.class, 2); + boolean invert = properties.getProperty("invert", Boolean.class, false); + BufferedImage image = readImage(width, heigth); + printBanner(image, margin, invert, out); } - private String imageToBanner(BufferedImage image, boolean dark) { - StringBuilder banner = new StringBuilder(); + private BufferedImage readImage(int width, int heigth) throws IOException { + InputStream inputStream = this.image.getInputStream(); + try { + BufferedImage image = ImageIO.read(inputStream); + return resizeImage(image, width, heigth); + } + finally { + inputStream.close(); + } + } + private BufferedImage resizeImage(BufferedImage image, int width, int height) { + if (width < 1) { + width = 1; + } + if (height <= 0) { + double aspectRatio = (double) width / image.getWidth() * 0.5; + height = (int) Math.ceil(image.getHeight() * aspectRatio); + } + BufferedImage resized = new BufferedImage(width, height, + BufferedImage.TYPE_INT_RGB); + Image scaled = image.getScaledInstance(width, height, Image.SCALE_DEFAULT); + resized.getGraphics().drawImage(scaled, 0, 0, null); + return resized; + } + + private void printBanner(BufferedImage image, int margin, boolean invert, + PrintStream out) { + AnsiElement background = (invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT); + out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); + out.print(AnsiOutput.encode(background)); + out.println(); + out.println(); + AnsiColor lastColor = AnsiColor.DEFAULT; for (int y = 0; y < image.getHeight(); y++) { - if (dark) { - banner.append("${AnsiBackground.BLACK}"); - } - else { - banner.append("${AnsiBackground.DEFAULT}"); + for (int i = 0; i < margin; i++) { + out.print(" "); } for (int x = 0; x < image.getWidth(); x++) { Color color = new Color(image.getRGB(x, y), false); - banner.append(getFormatString(color, dark)); + AnsiColor ansiColor = AnsiColors.getClosest(color); + if (ansiColor != lastColor) { + out.print(AnsiOutput.encode(ansiColor)); + lastColor = ansiColor; + } + out.print(getAsciiPixel(color, invert)); } - if (dark) { - banner.append("${AnsiBackground.DEFAULT}"); - } - banner.append("${AnsiColor.DEFAULT}\n"); + out.println(); } - - return banner.toString(); + out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); + out.print(AnsiOutput.encode(AnsiBackground.DEFAULT)); + out.println(); } - protected String getFormatString(Color color, boolean dark) { - String matchedColorName = null; - Double minColorDistance = null; - - for (Entry colorOption : this.colors.entrySet()) { - double distance = getColorDistance(color, colorOption.getValue()); - - if (minColorDistance == null || distance < minColorDistance) { - minColorDistance = distance; - matchedColorName = colorOption.getKey(); - } - } - - return "${AnsiColor." + matchedColorName + "}" + getAsciiCharacter(color, dark); - } - - private static int getLuminance(Color color, boolean inverse) { - double red = color.getRed(); - double green = color.getGreen(); - double blue = color.getBlue(); - - double luminance; - - if (inverse) { - luminance = (RED_WEIGHT * (255.0d - red)) + (GREEN_WEIGHT * (255.0d - green)) - + (BLUE_WEIGHT * (255.0d - blue)); - } - else { - luminance = (RED_WEIGHT * red) + (GREEN_WEIGHT * green) - + (BLUE_WEIGHT * blue); - } - - return (int) Math.ceil((luminance / 255.0d) * 100); - } - - private static char getAsciiCharacter(Color color, boolean dark) { + private char getAsciiPixel(Color color, boolean dark) { double luminance = getLuminance(color, dark); - - if (luminance >= 90) { - return ' '; - } - else if (luminance >= 80) { - return '.'; - } - else if (luminance >= 70) { - return '*'; - } - else if (luminance >= 60) { - return ':'; - } - else if (luminance >= 50) { - return 'o'; - } - else if (luminance >= 40) { - return '&'; - } - else if (luminance >= 30) { - return '8'; - } - else if (luminance >= 20) { - return '#'; - } - else { - return '@'; + for (int i = 0; i < PIXEL.length; i++) { + if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) { + return PIXEL[i]; + } } + return PIXEL[PIXEL.length - 1]; } - private static BufferedImage resizeImage(BufferedImage sourceImage, int maxWidth, - double aspectRatio) { - int width; - double resizeRatio; - if (sourceImage.getWidth() > maxWidth) { - resizeRatio = (double) maxWidth / (double) sourceImage.getWidth(); - width = maxWidth; - } - else { - resizeRatio = 1.0d; - width = sourceImage.getWidth(); - } - - int height = (int) (Math.ceil(resizeRatio * aspectRatio - * (double) sourceImage.getHeight())); - Image image = sourceImage.getScaledInstance(width, height, Image.SCALE_DEFAULT); - - BufferedImage resizedImage = new BufferedImage(image.getWidth(null), - image.getHeight(null), BufferedImage.TYPE_INT_RGB); - - resizedImage.getGraphics().drawImage(image, 0, 0, null); - return resizedImage; + private int getLuminance(Color color, boolean inverse) { + double luminance = 0.0; + luminance += getLuminance(color.getRed(), inverse, RGB_WEIGHT[0]); + luminance += getLuminance(color.getGreen(), inverse, RGB_WEIGHT[1]); + luminance += getLuminance(color.getBlue(), inverse, RGB_WEIGHT[2]); + return (int) Math.ceil((luminance / 0xFF) * 100); } - /** - * Computes the CIE94 distance between two colors. - * - * Contributed by michael-simons - * (original implementation https://github.com/michael-simons/dfx-mosaic/blob/public/src/main/java/de/dailyfratze/mosaic/images/CIE94ColorDistance.java) - * - * @param color1 the first color - * @param color2 the second color - * @return the distance between the colors - */ - private static double getColorDistance(final Color color1, final Color color2) { - // Convert to L*a*b* color space - float[] lab1 = toLab(color1); - float[] lab2 = toLab(color2); - - // Make it more readable - double L1 = lab1[0]; - double a1 = lab1[1]; - double b1 = lab1[2]; - double L2 = lab2[0]; - double a2 = lab2[1]; - double b2 = lab2[2]; - - // CIE94 coefficients for graphic arts - double kL = 1; - double K1 = 0.045; - double K2 = 0.015; - // Weighting factors - double sl = 1.0; - double kc = 1.0; - double kh = 1.0; - - // See http://en.wikipedia.org/wiki/Color_difference#CIE94 - double c1 = Math.sqrt(a1 * a1 + b1 * b1); - double deltaC = c1 - Math.sqrt(a2 * a2 + b2 * b2); - double deltaA = a1 - a2; - double deltaB = b1 - b2; - double deltaH = Math.sqrt(Math.max(0.0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC)); - - return Math.sqrt(Math.max(0.0, Math.pow((L1 - L2) / (kL * sl), 2) + Math.pow(deltaC / (kc * (1 + K1 * c1)), 2) + Math.pow(deltaH / (kh * (1 + K2 * c1)), 2.0))); + private double getLuminance(int component, boolean inverse, double weight) { + return (inverse ? 0xFF - component : component) * weight; } - /** - * Returns the CIE L*a*b* values of this color. - * - * Implements the forward transformation described in - * https://en.wikipedia.org/wiki/Lab_color_space - * - * @param color the color to convert - * @return the xyz color components - */ - static float[] toLab(Color color) { - float[] xyz = color.getColorComponents( - ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), null); - - return xyzToLab(xyz); - } - - static float[] xyzToLab(float[] colorvalue) { - double l = f(colorvalue[1]); - double L = 116.0 * l - 16.0; - double a = 500.0 * (f(colorvalue[0]) - l); - double b = 200.0 * (l - f(colorvalue[2])); - return new float[]{(float) L, (float) a, (float) b}; - } - - private static double f(double t) { - if (t > 216.0 / 24389.0) { - return Math.cbrt(t); - } - else { - return (1.0 / 3.0) * Math.pow(29.0 / 6.0, 2) * t + (4.0 / 29.0); - } - } } diff --git a/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java b/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java index 6eab88af17..c2d1d797b2 100644 --- a/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java +++ b/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java @@ -38,7 +38,7 @@ import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** - * Banner implementation that prints from a source {@link Resource}. + * Banner implementation that prints from a source text {@link Resource}. * * @author Phillip Webb * @author Vedran Pavic diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index afbb224c5b..3d665a5552 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -16,9 +16,6 @@ package org.springframework.boot; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.security.AccessControlException; import java.util.ArrayList; @@ -41,6 +38,7 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.boot.Banner.Mode; import org.springframework.boot.diagnostics.FailureAnalyzers; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; @@ -167,19 +165,17 @@ public class SpringApplication { /** * Default banner location. */ - public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt"; + public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION; /** * Banner location property key. */ - public static final String BANNER_LOCATION_PROPERTY = "banner.location"; + public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY; private static final String CONFIGURABLE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.context.ConfigurableWebEnvironment"; private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless"; - private static final Banner DEFAULT_BANNER = new SpringBootBanner(); - private static final Set SERVLET_ENVIRONMENT_SOURCE_NAMES; static { @@ -543,60 +539,16 @@ public class SpringApplication { * @see #setBannerMode */ protected void printBanner(Environment environment) { - Banner selectedBanner = selectBanner(environment); - if (this.bannerMode == Banner.Mode.LOG) { - try { - logger.info(createStringFromBanner(selectedBanner, environment)); - } - catch (UnsupportedEncodingException ex) { - logger.warn("Failed to create String for banner", ex); - } - } - else { - selectedBanner.printBanner(environment, this.mainApplicationClass, - System.out); - } - } - - private Banner selectBanner(Environment environment) { - String location = environment.getProperty(BANNER_LOCATION_PROPERTY, - BANNER_LOCATION_PROPERTY_VALUE); ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(getClassLoader()); - Resource resource = resourceLoader.getResource(location); - if (resource.exists()) { - return new ResourceBanner(resource); + SpringApplicationBannerPrinter banner = new SpringApplicationBannerPrinter(resourceLoader, + this.banner); + if (this.bannerMode == Mode.LOG) { + banner.print(environment, this.mainApplicationClass, logger); } - if (this.banner != null) { - return this.banner; + else { + banner.print(environment, this.mainApplicationClass, System.out); } - - Resource image = getBannerImage(environment, resourceLoader); - if (image.exists()) { - return new ImageBanner(image); - } - - return DEFAULT_BANNER; - } - - private Resource getBannerImage(Environment environment, ResourceLoader resourceLoader) { - String imageLocation = environment.getProperty("banner.image", "banner.gif"); - Resource image = resourceLoader.getResource(imageLocation); - if (!image.exists()) { - image = resourceLoader.getResource("banner.jpg"); - } - if (!image.exists()) { - image = resourceLoader.getResource("banner.png"); - } - return image; - } - - private String createStringFromBanner(Banner banner, Environment environment) - throws UnsupportedEncodingException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos)); - String charset = environment.getProperty("banner.charset", "UTF-8"); - return baos.toString(charset); } /** diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java new file mode 100644 index 0000000000..b3241b6968 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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 org.springframework.boot; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; + +import org.springframework.core.env.Environment; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; + +/** + * Class used by {@link SpringApplication} to print the application banner. + * + * @author Phillip Webb + */ +class SpringApplicationBannerPrinter { + + static final String BANNER_LOCATION_PROPERTY = "banner.location"; + + static final String BANNER_IMAGE_LOCATION_PROPERTY = "banner.image.location"; + + static final String DEFAULT_BANNER_LOCATION = "banner.txt"; + + static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" }; + + private static final Banner DEFAULT_BANNER = new SpringBootBanner(); + + private final ResourceLoader resourceLoader; + + private final Banner fallbackBanner; + + SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) { + this.resourceLoader = resourceLoader; + this.fallbackBanner = fallbackBanner; + } + + public void print(Environment environment, Class sourceClass, Log logger) { + Banner banner = getBanner(environment, this.fallbackBanner); + try { + logger.info(createStringFromBanner(banner, environment, sourceClass)); + } + catch (UnsupportedEncodingException ex) { + logger.warn("Failed to create String for banner", ex); + } + } + + public void print(Environment environment, Class sourceClass, PrintStream out) { + Banner banner = getBanner(environment, this.fallbackBanner); + banner.printBanner(environment, sourceClass, out); + } + + private Banner getBanner(Environment environment, Banner definedBanner) { + Banners banners = new Banners(); + banners.addIfNotNull(getImageBanner(environment)); + banners.addIfNotNull(getTextBanner(environment)); + if (banners.hasAtLeastOneBanner()) { + return banners; + } + if (this.fallbackBanner != null) { + return this.fallbackBanner; + } + return DEFAULT_BANNER; + } + + private Banner getTextBanner(Environment environment) { + String location = environment.getProperty(BANNER_LOCATION_PROPERTY, + DEFAULT_BANNER_LOCATION); + Resource resource = this.resourceLoader.getResource(location); + if (resource.exists()) { + return new ResourceBanner(resource); + } + return null; + } + + private Banner getImageBanner(Environment environment) { + String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY); + if (StringUtils.hasLength(location)) { + Resource resource = this.resourceLoader.getResource(location); + return (resource.exists() ? new ImageBanner(resource) : null); + } + for (String ext : IMAGE_EXTENSION) { + Resource resource = this.resourceLoader.getResource("banner." + ext); + if (resource.exists()) { + return new ImageBanner(resource); + } + } + return null; + } + + private String createStringFromBanner(Banner banner, Environment environment, + Class mainApplicationClass) throws UnsupportedEncodingException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + banner.printBanner(environment, mainApplicationClass, new PrintStream(baos)); + String charset = environment.getProperty("banner.charset", "UTF-8"); + return baos.toString(charset); + } + + /** + * {@link Banner} comprised of other {@link Banner Banners}. + */ + private static class Banners implements Banner { + + private final List banners = new ArrayList(); + + public void addIfNotNull(Banner banner) { + if (banner != null) { + this.banners.add(banner); + } + } + + public boolean hasAtLeastOneBanner() { + return !this.banners.isEmpty(); + } + + @Override + public void printBanner(Environment environment, Class sourceClass, + PrintStream out) { + for (Banner banner : this.banners) { + banner.printBanner(environment, sourceClass, out); + } + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiColors.java b/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiColors.java new file mode 100644 index 0000000000..7a562a5503 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiColors.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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 org.springframework.boot.ansi; + +import java.awt.Color; +import java.awt.color.ColorSpace; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.util.Assert; + +/** + * Utility for working with {@link AnsiColor} in the context of {@link Color AWT Colors}. + * + * @author Craig Burke + * @author Ruben Dijkstra + * @author Phillip Webb + * @author Michael Simons + * @since 1.4.0 + */ +public final class AnsiColors { + + private static final Map ANSI_COLOR_MAP; + + static { + Map colorMap = new LinkedHashMap(); + colorMap.put(AnsiColor.BLACK, new LabColor(0x000000)); + colorMap.put(AnsiColor.RED, new LabColor(0xAA0000)); + colorMap.put(AnsiColor.GREEN, new LabColor(0x00AA00)); + colorMap.put(AnsiColor.YELLOW, new LabColor(0xAA5500)); + colorMap.put(AnsiColor.BLUE, new LabColor(0x0000AA)); + colorMap.put(AnsiColor.MAGENTA, new LabColor(0xAA00AA)); + colorMap.put(AnsiColor.CYAN, new LabColor(0x00AAAA)); + colorMap.put(AnsiColor.WHITE, new LabColor(0xAAAAAA)); + colorMap.put(AnsiColor.BRIGHT_BLACK, new LabColor(0x555555)); + colorMap.put(AnsiColor.BRIGHT_RED, new LabColor(0xFF5555)); + colorMap.put(AnsiColor.BRIGHT_GREEN, new LabColor(0x55FF00)); + colorMap.put(AnsiColor.BRIGHT_YELLOW, new LabColor(0xFFFF55)); + colorMap.put(AnsiColor.BRIGHT_BLUE, new LabColor(0x5555FF)); + colorMap.put(AnsiColor.BRIGHT_MAGENTA, new LabColor(0xFF55FF)); + colorMap.put(AnsiColor.BRIGHT_CYAN, new LabColor(0x55FFFF)); + colorMap.put(AnsiColor.BRIGHT_WHITE, new LabColor(0xFFFFFF)); + ANSI_COLOR_MAP = Collections.unmodifiableMap(colorMap); + } + + private AnsiColors() { + } + + public static AnsiColor getClosest(Color color) { + return getClosest(new LabColor(color)); + } + + private static AnsiColor getClosest(LabColor color) { + AnsiColor result = null; + double resultDistance = Float.MAX_VALUE; + for (Entry entry : ANSI_COLOR_MAP.entrySet()) { + double distance = color.getDistance(entry.getValue()); + if (result == null || distance < resultDistance) { + resultDistance = distance; + result = entry.getKey(); + } + } + return result; + } + + /** + * Represents a color stored in LAB form. + */ + private static final class LabColor { + + private static final ColorSpace XYZ_COLOR_SPACE = ColorSpace + .getInstance(ColorSpace.CS_CIEXYZ); + + private final double l; + + private final double a; + + private final double b; + + LabColor(Integer rgb) { + this(rgb == null ? (Color) null : new Color(rgb)); + } + + LabColor(Color color) { + Assert.notNull(color, "Color must not be null"); + float[] lab = fromXyz(color.getColorComponents(XYZ_COLOR_SPACE, null)); + this.l = lab[0]; + this.a = lab[1]; + this.b = lab[2]; + } + + private float[] fromXyz(float[] xyz) { + return fromXyz(xyz[0], xyz[1], xyz[2]); + } + + private float[] fromXyz(float x, float y, float z) { + double l = (f(y) - 16.0) * 116.0; + double a = (f(x) - f(y)) * 500.0; + double b = (f(y) - f(z)) * 200.0; + return new float[] { (float) l, (float) a, (float) b }; + } + + private double f(double t) { + return (t > (216.0 / 24389.0) ? Math.cbrt(t) + : (1.0 / 3.0) * Math.pow(29.0 / 6.0, 2) * t + (4.0 / 29.0)); + } + + // See http://en.wikipedia.org/wiki/Color_difference#CIE94 + public double getDistance(LabColor other) { + double c1 = Math.sqrt(this.a * this.a + this.b * this.b); + double deltaC = c1 - Math.sqrt(other.a * other.a + other.b * other.b); + double deltaA = this.a - other.a; + double deltaB = this.b - other.b; + double deltaH = Math.sqrt( + Math.max(0.0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC)); + return Math.sqrt(Math.max(0.0, + Math.pow((this.l - other.l) / (1.0), 2) + + Math.pow(deltaC / (1 + 0.045 * c1), 2) + + Math.pow(deltaH / (1 + 0.015 * c1), 2.0))); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiColorsTests.java b/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiColorsTests.java new file mode 100644 index 0000000000..e5942959c6 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiColorsTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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 org.springframework.boot.ansi; + +import java.awt.Color; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AnsiColors}. + * + * @author Phillip Webb + */ +public class AnsiColorsTests { + + @Test + public void getClosestWhenExactMatchShouldReturnAnsiColor() throws Exception { + assertThat(getClosest(0x000000)).isEqualTo(AnsiColor.BLACK); + assertThat(getClosest(0xAA0000)).isEqualTo(AnsiColor.RED); + assertThat(getClosest(0x00AA00)).isEqualTo(AnsiColor.GREEN); + assertThat(getClosest(0xAA5500)).isEqualTo(AnsiColor.YELLOW); + assertThat(getClosest(0x0000AA)).isEqualTo(AnsiColor.BLUE); + assertThat(getClosest(0xAA00AA)).isEqualTo(AnsiColor.MAGENTA); + assertThat(getClosest(0x00AAAA)).isEqualTo(AnsiColor.CYAN); + assertThat(getClosest(0xAAAAAA)).isEqualTo(AnsiColor.WHITE); + assertThat(getClosest(0x555555)).isEqualTo(AnsiColor.BRIGHT_BLACK); + assertThat(getClosest(0xFF5555)).isEqualTo(AnsiColor.BRIGHT_RED); + assertThat(getClosest(0x55FF00)).isEqualTo(AnsiColor.BRIGHT_GREEN); + assertThat(getClosest(0xFFFF55)).isEqualTo(AnsiColor.BRIGHT_YELLOW); + assertThat(getClosest(0x5555FF)).isEqualTo(AnsiColor.BRIGHT_BLUE); + assertThat(getClosest(0xFF55FF)).isEqualTo(AnsiColor.BRIGHT_MAGENTA); + assertThat(getClosest(0x55FFFF)).isEqualTo(AnsiColor.BRIGHT_CYAN); + assertThat(getClosest(0xFFFFFF)).isEqualTo(AnsiColor.BRIGHT_WHITE); + } + + @Test + public void getClosestWhenCloseShouldReturnAnsiColor() throws Exception { + assertThat(getClosest(0x292424)).isEqualTo(AnsiColor.BLACK); + assertThat(getClosest(0x8C1919)).isEqualTo(AnsiColor.RED); + assertThat(getClosest(0x0BA10B)).isEqualTo(AnsiColor.GREEN); + assertThat(getClosest(0xB55F09)).isEqualTo(AnsiColor.YELLOW); + assertThat(getClosest(0x0B0BA1)).isEqualTo(AnsiColor.BLUE); + assertThat(getClosest(0xA312A3)).isEqualTo(AnsiColor.MAGENTA); + assertThat(getClosest(0x0BB5B5)).isEqualTo(AnsiColor.CYAN); + assertThat(getClosest(0xBAB6B6)).isEqualTo(AnsiColor.WHITE); + assertThat(getClosest(0x615A5A)).isEqualTo(AnsiColor.BRIGHT_BLACK); + assertThat(getClosest(0xF23333)).isEqualTo(AnsiColor.BRIGHT_RED); + assertThat(getClosest(0x55E80C)).isEqualTo(AnsiColor.BRIGHT_GREEN); + assertThat(getClosest(0xF5F54C)).isEqualTo(AnsiColor.BRIGHT_YELLOW); + assertThat(getClosest(0x5656F0)).isEqualTo(AnsiColor.BRIGHT_BLUE); + assertThat(getClosest(0xFA50FA)).isEqualTo(AnsiColor.BRIGHT_MAGENTA); + assertThat(getClosest(0x56F5F5)).isEqualTo(AnsiColor.BRIGHT_CYAN); + assertThat(getClosest(0xEDF5F5)).isEqualTo(AnsiColor.BRIGHT_WHITE); + } + + private AnsiColor getClosest(int rgb) { + return AnsiColors.getClosest(new Color(rgb)); + } + +} diff --git a/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 80a1d91811..8bae435d42 100644 --- a/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -13,9 +13,36 @@ { "name": "banner.location", "type": "org.springframework.core.io.Resource", - "description": "Banner file location.", + "description": "Banner text resource location.", "defaultValue": "classpath:banner.txt" }, + { + "name": "banner.image.location", + "type": "org.springframework.core.io.Resource", + "description": "Banner image file location.", + "defaultValue": "banner.gif" + }, + { + "name": "banner.image.width", + "type": "java.lang.Integer", + "description": "Banner image width (in chars)." + }, + { + "name": "banner.image.height", + "type": "java.lang.Integer", + "description": "Banner image height (in chars)." + }, + { + "name": "banner.image.margin", + "type": "java.lang.Integer", + "description": "Left hand image height (in chars)." + }, + { + "name": "banner.image.invert", + "type": "java.lang.Boolean", + "description": "Invert images for dark console themes.", + "defaultValue": false + }, { "name": "debug", "type": "java.lang.Boolean", diff --git a/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java b/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java index 4d5460ed0c..f8fca5830c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java @@ -18,219 +18,171 @@ package org.springframework.boot; import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import java.util.HashMap; -import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.boot.ansi.AnsiBackground; import org.springframework.boot.ansi.AnsiColor; -import org.springframework.boot.ansi.AnsiElement; import org.springframework.boot.ansi.AnsiOutput; +import org.springframework.boot.ansi.AnsiOutput.Enabled; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.context.support.TestPropertySourceUtils; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.startsWith; - -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ImageBanner}. * * @author Craig Burke + * @author Phillip Webb */ public class ImageBannerTests { - private static final String IMAGE_BANNER_BLACK_AND_WHITE = "banners/black-and-white.gif"; - private static final String IMAGE_BANNER_LARGE = "banners/large.gif"; - private static final String IMAGE_BANNER_ALL_COLORS = "banners/colors.gif"; - private static final String IMAGE_BANNER_GRADIENT = "banners/gradient.gif"; - - private static final String BACKGROUND_DEFAULT_ANSI = getAnsiOutput(AnsiBackground.DEFAULT); - private static final String BACKGROUND_DARK_ANSI = getAnsiOutput(AnsiBackground.BLACK); + private static final String NEW_LINE = System.getProperty("line.separator"); private static final char HIGH_LUMINANCE_CHARACTER = ' '; + private static final char LOW_LUMINANCE_CHARACTER = '@'; - private static Map properties; + private static final String INVERT_TRUE = "banner.image.invert=true"; @Before public void setup() { AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS); - properties = new HashMap(); + } + + @After + public void cleanup() { + AnsiOutput.setEnabled(Enabled.DETECT); } @Test - public void renderDefaultBackground() { - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - - assertThat(banner, startsWith(BACKGROUND_DEFAULT_ANSI)); + public void printBannerShouldResetForegroundAndBackground() { + String banner = printBanner("black-and-white.gif"); + String expected = AnsiOutput.encode(AnsiColor.DEFAULT) + + AnsiOutput.encode(AnsiBackground.DEFAULT); + assertThat(banner).startsWith(expected); } @Test - public void renderDarkBackground() { - setDark(true); - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - - assertThat(banner, startsWith(BACKGROUND_DARK_ANSI)); + public void printBannerWhenInvertedShouldResetForegroundAndBackground() { + String banner = printBanner("black-and-white.gif", INVERT_TRUE); + String expected = AnsiOutput.encode(AnsiColor.DEFAULT) + + AnsiOutput.encode(AnsiBackground.BLACK); + assertThat(banner).startsWith(expected); } @Test - public void renderWhiteCharactersWithColors() { - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - String expectedFirstLine = getAnsiOutput(AnsiColor.BRIGHT_WHITE) + public void printBannerShouldPrintWhiteAsBrightWhiteHighLuminance() { + String banner = printBanner("black-and-white.gif"); + String expected = AnsiOutput.encode(AnsiColor.BRIGHT_WHITE) + HIGH_LUMINANCE_CHARACTER; - - assertThat(banner, containsString(expectedFirstLine)); + assertThat(banner).contains(expected); } @Test - public void renderWhiteCharactersOnDarkBackground() { - setDark(true); - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - String expectedFirstLine = getAnsiOutput(AnsiColor.BRIGHT_WHITE) + public void printBannerWhenInvertedShouldPrintWhiteAsBrightWhiteLowLuminance() { + String banner = printBanner("black-and-white.gif", INVERT_TRUE); + String expected = AnsiOutput.encode(AnsiColor.BRIGHT_WHITE) + LOW_LUMINANCE_CHARACTER; - - assertThat(banner, containsString(expectedFirstLine)); + assertThat(banner).contains(expected); } @Test - public void renderBlackCharactersOnDefaultBackground() { - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - String blackCharacter = getAnsiOutput(AnsiColor.BLACK) + LOW_LUMINANCE_CHARACTER; - - assertThat(banner, containsString(blackCharacter)); + public void printBannerShouldPrintBlackAsBlackLowLuminance() { + String banner = printBanner("black-and-white.gif"); + String expected = AnsiOutput.encode(AnsiColor.BLACK) + LOW_LUMINANCE_CHARACTER; + assertThat(banner).contains(expected); } @Test - public void renderBlackCharactersOnDarkBackground() { - setDark(true); - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - String blackCharacter = getAnsiOutput(AnsiColor.BLACK) + HIGH_LUMINANCE_CHARACTER; - - assertThat(banner, containsString(blackCharacter)); + public void printBannerWhenInvertedShouldPrintBlackAsBlackHighLuminance() { + String banner = printBanner("black-and-white.gif", INVERT_TRUE); + String expected = AnsiOutput.encode(AnsiColor.BLACK) + HIGH_LUMINANCE_CHARACTER; + assertThat(banner).contains(expected); } @Test - public void renderBannerWithAllColors() { - String banner = printBanner(IMAGE_BANNER_ALL_COLORS); - - assertThat("Banner contains BLACK", banner, - containsString(getAnsiOutput(AnsiColor.BLACK))); - assertThat("Banner contains RED", banner, - containsString(getAnsiOutput(AnsiColor.RED))); - assertThat("Banner contains GREEN", banner, - containsString(getAnsiOutput(AnsiColor.GREEN))); - assertThat("Banner contains YELLOW", banner, - containsString(getAnsiOutput(AnsiColor.YELLOW))); - assertThat("Banner contains BLUE", banner, - containsString(getAnsiOutput(AnsiColor.BLUE))); - assertThat("Banner contains MAGENTA", banner, - containsString(getAnsiOutput(AnsiColor.MAGENTA))); - assertThat("Banner contains CYAN", banner, - containsString(getAnsiOutput(AnsiColor.CYAN))); - assertThat("Banner contains WHITE", banner, - containsString(getAnsiOutput(AnsiColor.WHITE))); - - assertThat("Banner contains BRIGHT_BLACK", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_BLACK))); - assertThat("Banner contains BRIGHT_RED", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_RED))); - assertThat("Banner contains BRIGHT_GREEN", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_GREEN))); - assertThat("Banner contains BRIGHT_YELLOW", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_YELLOW))); - assertThat("Banner contains BRIGHT_BLUE", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_BLUE))); - assertThat("Banner contains BRIGHT_MAGENTA", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_MAGENTA))); - assertThat("Banner contains BRIGHT_CYAN", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_CYAN))); - assertThat("Banner contains BRIGHT_WHITE", banner, - containsString(getAnsiOutput(AnsiColor.BRIGHT_WHITE))); + public void printBannerWhenShouldPrintAllColors() { + String banner = printBanner("colors.gif"); + for (AnsiColor color : AnsiColor.values()) { + if (color != AnsiColor.DEFAULT) { + assertThat(banner).contains(AnsiOutput.encode(color)); + } + } } @Test - public void renderSimpleGradient() { + public void printBannerShouldRenderGradient() throws Exception { AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER); - String banner = printBanner(IMAGE_BANNER_GRADIENT); - String expectedResult = "@#8&o:*. "; - - assertThat(banner, startsWith(expectedResult)); + String banner = printBanner("gradient.gif", "banner.image.width=10", + "banner.image.margin=0"); + System.out.println(banner); + assertThat(banner).contains("@#8&o:*. "); } @Test - public void renderBannerWithDefaultAspectRatio() { - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - int bannerHeight = getBannerHeight(banner); - - assertThat(bannerHeight, equalTo(2)); + public void printBannerShouldCalculateHeight() throws Exception { + String banner = printBanner("large.gif", "banner.image.width=20"); + assertThat(getBannerHeight(banner)).isEqualTo(10); } @Test - public void renderBannerWithCustomAspectRatio() { - setAspectRatio(1.0d); - String banner = printBanner(IMAGE_BANNER_BLACK_AND_WHITE); - int bannerHeight = getBannerHeight(banner); - - assertThat(bannerHeight, equalTo(4)); + public void printBannerWhenHasHeightPropertyShouldSetHeight() throws Exception { + String banner = printBanner("large.gif", "banner.image.width=20", + "banner.image.height=30"); + assertThat(getBannerHeight(banner)).isEqualTo(30); } @Test - public void renderLargeBanner() { - String banner = printBanner(IMAGE_BANNER_LARGE); - int bannerWidth = getBannerWidth(banner); - - assertThat(bannerWidth, equalTo(72)); + public void printBannerShouldCapWidthAndCalculateHeight() throws Exception { + AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER); + String banner = printBanner("large.gif", "banner.image.margin=0"); + assertThat(getBannerWidth(banner)).isEqualTo(76); + assertThat(getBannerHeight(banner)).isEqualTo(37); } @Test - public void renderLargeBannerWithACustomWidth() { - setMaxWidth(60); - String banner = printBanner(IMAGE_BANNER_LARGE); - int bannerWidth = getBannerWidth(banner); + public void printBannerShouldPrintMargin() throws Exception { + AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER); + String banner = printBanner("large.gif"); + String[] lines = banner.split(NEW_LINE); + for (int i = 2; i < lines.length - 1; i++) { + assertThat(lines[i]).startsWith(" @"); + } + } - assertThat(bannerWidth, equalTo(60)); + @Test + public void printBannerWhenHasMarginPropertShouldPrintSizedMargin() throws Exception { + AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER); + String banner = printBanner("large.gif", "banner.image.margin=4"); + String[] lines = banner.split(NEW_LINE); + for (int i = 2; i < lines.length - 1; i++) { + assertThat(lines[i]).startsWith(" @"); + } } private int getBannerHeight(String banner) { - return banner.split("\n").length; + return banner.split(NEW_LINE).length - 3; } private int getBannerWidth(String banner) { - String strippedBanner = banner.replaceAll("\u001B\\[.*?m", ""); - String firstLine = strippedBanner.split("\n")[0]; - return firstLine.length(); + int width = 0; + for (String line : banner.split(NEW_LINE)) { + width = Math.max(width, line.length()); + } + return width; } - private static String getAnsiOutput(AnsiElement ansi) { - return "\u001B[" + ansi.toString() + "m"; - } - - private void setDark(boolean dark) { - properties.put("banner.image.dark", dark); - } - - private void setMaxWidth(int maxWidth) { - properties.put("banner.image.max-width", maxWidth); - } - - private void setAspectRatio(double aspectRatio) { - properties.put("banner.image.aspect-ratio", aspectRatio); - } - - private String printBanner(String imagePath) { - Resource image = new ClassPathResource(imagePath); - ImageBanner banner = new ImageBanner(image); + private String printBanner(String path, String... properties) { + ImageBanner banner = new ImageBanner(new ClassPathResource(path, getClass())); ConfigurableEnvironment environment = new MockEnvironment(); - environment.getPropertySources().addLast( - new MapPropertySource("testConfig", properties)); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, + properties); ByteArrayOutputStream out = new ByteArrayOutputStream(); banner.printBanner(environment, getClass(), new PrintStream(out)); return out.toString(); diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 644a1e0e1e..47f8654bf2 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -67,6 +67,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -196,29 +197,26 @@ public class SpringApplicationTests { } @Test - public void textBannerTakesPrecedence() throws Exception { + public void imageBannerAndTextBanner() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class); - BannerResourceLoaderStub resourceLoader = new BannerResourceLoaderStub(); - resourceLoader.addResource("banner.gif", "banners/black-and-white.gif"); - resourceLoader.addResource("banner.txt", "banners/foobar.txt"); - + MockResourceLoader resourceLoader = new MockResourceLoader(); + resourceLoader.addResource("banner.gif", "black-and-white.gif"); + resourceLoader.addResource("banner.txt", "foobar.txt"); application.setWebEnvironment(false); application.setResourceLoader(resourceLoader); application.run(); - - assertThat(this.output.toString()).startsWith("Foo Bar"); + assertThat(this.output.toString()).contains("@@@@").contains("Foo Bar"); } @Test public void imageBannerLoads() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class); - BannerResourceLoaderStub resourceLoader = new BannerResourceLoaderStub(); - resourceLoader.addResource("banner.gif", "banners/black-and-white.gif"); + MockResourceLoader resourceLoader = new MockResourceLoader(); + resourceLoader.addResource("banner.gif", "black-and-white.gif"); application.setWebEnvironment(false); application.setResourceLoader(resourceLoader); application.run(); - - assertThat(this.output.toString()).startsWith("@"); + assertThat(this.output.toString()).contains("@@@@@@"); } @Test @@ -1120,28 +1118,23 @@ public class SpringApplicationTests { } - private static class BannerResourceLoaderStub extends DefaultResourceLoader { + private static class MockResourceLoader implements ResourceLoader { - private Map resources = new HashMap(); - Resource notFoundResource; + private final Map resources = new HashMap(); - BannerResourceLoaderStub() { - this.notFoundResource = super.getResource("classpath:foo/bar/foobar"); - assert !this.notFoundResource.exists(); - } - - public void addResource(String file, String realPath) { - this.resources.put(file, realPath); + public void addResource(String source, String path) { + this.resources.put(source, new ClassPathResource(path, getClass())); } @Override - public Resource getResource(String s) { - if (this.resources.containsKey(s)) { - return super.getResource("classpath:" + this.resources.get(s)); - } - else { - return this.notFoundResource; - } + public Resource getResource(String path) { + Resource resource = this.resources.get(path); + return (resource == null ? new ClassPathResource("doesnotexit") : resource); + } + + @Override + public ClassLoader getClassLoader() { + return getClass().getClassLoader(); } } diff --git a/spring-boot/src/test/resources/banners/black-and-white.gif b/spring-boot/src/test/resources/org/springframework/boot/black-and-white.gif similarity index 100% rename from spring-boot/src/test/resources/banners/black-and-white.gif rename to spring-boot/src/test/resources/org/springframework/boot/black-and-white.gif diff --git a/spring-boot/src/test/resources/banners/colors.gif b/spring-boot/src/test/resources/org/springframework/boot/colors.gif similarity index 100% rename from spring-boot/src/test/resources/banners/colors.gif rename to spring-boot/src/test/resources/org/springframework/boot/colors.gif diff --git a/spring-boot/src/test/resources/banners/foobar.txt b/spring-boot/src/test/resources/org/springframework/boot/foobar.txt similarity index 100% rename from spring-boot/src/test/resources/banners/foobar.txt rename to spring-boot/src/test/resources/org/springframework/boot/foobar.txt diff --git a/spring-boot/src/test/resources/banners/gradient.gif b/spring-boot/src/test/resources/org/springframework/boot/gradient.gif similarity index 100% rename from spring-boot/src/test/resources/banners/gradient.gif rename to spring-boot/src/test/resources/org/springframework/boot/gradient.gif diff --git a/spring-boot/src/test/resources/banners/large.gif b/spring-boot/src/test/resources/org/springframework/boot/large.gif similarity index 100% rename from spring-boot/src/test/resources/banners/large.gif rename to spring-boot/src/test/resources/org/springframework/boot/large.gif