Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding validation #19

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Copyright (C) Marcus Hirt, 2024
<modelVersion>4.0.0</modelVersion>
<groupId>se.hirt</groupId>
<artifactId>slogan-generator</artifactId>
<version>0.1.15-SNAPSHOT</version>
<version>0.1.16-SNAPSHOT</version>

<properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version>
Expand Down
68 changes: 64 additions & 4 deletions src/main/java/se/hirt/slogan/ImageConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@
import java.awt.*;

public class ImageConfig {
private String background = "random";
private ImageGenerator.Background background = null;
private String textColor = "#FFFFFF";
private boolean dropShadow = true;
private int dropShadowDistance;
private int fontSize = 12;
private String fontName = "Arial";
private int fontStyle = Font.BOLD;
private float opacity = 1.0f;
private String validationError = null;

public String getBackground() {
public ImageGenerator.Background getBackground() {
return background;
}

Expand Down Expand Up @@ -75,11 +76,36 @@ public float getOpacity() {
return opacity;
}

public boolean isValid() {
return validationError == null;
}

public String getValidationError() {
return validationError;
}

public static class Builder {
private ImageConfig config = new ImageConfig();

public Builder() {
}

public Builder(ImageConfig config) {
copyFrom(config);
}

public Builder background(String background) {
config.background = background;
if (background == null || background.isBlank()) {
background = "ocean";
}
try {
config.background = "random".equalsIgnoreCase(background) ? ImageGenerator.Background.getRandom()
: ImageGenerator.Background.valueOf(background.toUpperCase());
} catch (IllegalArgumentException e) {
}
if (config.background == null) {
config.validationError = background + " is not a valid background";
}
return this;
}

Expand All @@ -88,16 +114,36 @@ public Builder textColor(String textColor) {
if (!textColor.startsWith("#")) {
textColor = "#" + textColor;
}
textColor = textColor.trim();
validateColorFormat(textColor);
config.textColor = textColor;
return this;
}

private boolean validateColorFormat(String textColor) {
if (textColor.length() != 7) {
config.validationError = "textcolor error! Format is hex e.g. #9F8E7D. Was: " + textColor;
return false;
}
for (int i = 1; i < textColor.length(); i++) {
if (!Character.isLetterOrDigit(textColor.charAt(i))) {
config.validationError = "textcolor error! Format is hex e.g. #9F8E7D. Was: " + textColor;
return false;
}
}
return true;
}

public Builder dropShadow(boolean dropShadow) {
config.dropShadow = dropShadow;
return this;
}

public Builder fontSize(int fontSize) {
if (fontSize < 2 || fontSize > 50) {
config.validationError = "Font size " + fontSize + " is unreasonable!";
return this;
}
config.fontSize = fontSize;
return this;
}
Expand Down Expand Up @@ -130,13 +176,27 @@ public Builder opacity(float opacity) {
}

public Builder dropShadowDistance(int dropShadowDistance) {
if (dropShadowDistance > 50 || dropShadowDistance < -50) {
config.validationError = "Dropshadow distance " + dropShadowDistance + " is unreasonable!";
return this;
}
config.dropShadowDistance = dropShadowDistance;
return this;
}

public Builder copyFrom(ImageConfig fromConfig) {
config.background = fromConfig.getBackground();
opacity(fromConfig.getOpacity());
dropShadow(fromConfig.isDropShadow());
dropShadowDistance(fromConfig.getDropShadowDistance());
textColor(fromConfig.getTextColor());
fontName(fromConfig.getFontName());
config.fontStyle = fromConfig.getFontStyle();
return this;
}

public ImageConfig build() {
return config;
}

}
}
19 changes: 10 additions & 9 deletions src/main/java/se/hirt/slogan/ImageGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,19 @@ public static Background getRandom() {
}

public byte[] generateImage(String slogan, ImageConfig config) throws IOException {
Background background;
try {
background = "random".equalsIgnoreCase(config.getBackground()) ? Background.getRandom()
: Background.valueOf(config.getBackground().toUpperCase());
} catch (IllegalArgumentException e) {
slogan = "-->" + config.getBackground() + " is not a valid background <--";
background = Background.SUNSET;
boolean configIsValid = true;
if (!config.isValid() || slogan.isBlank()) {
if (slogan.isBlank()) {
slogan = "--> Must set either item or slogan parameter! <--";
} else {
slogan = "-->" + config.getValidationError() + "<--";
}
config = new ImageConfig.Builder().background("lakenight").fontSize(10).textColor("#FFFFFF").build();
}

try (InputStream is = getClass().getResourceAsStream("/backgrounds/" + background.getFileName() + ".png")) {
try (InputStream is = getClass().getResourceAsStream("/backgrounds/" + config.getBackground().getFileName() + ".png")) {
if (is == null) {
throw new IOException("Background image not found: " + background);
throw new IOException("Background image not found: " + config.getBackground().getFileName());
}
byte[] backgroundBytes = is.readAllBytes();
BufferedImage image = Imaging.getBufferedImage(backgroundBytes);
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/se/hirt/slogan/SloganGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ public SloganGenerator(List<String> patterns) {
}

public String generateSlogan(String item) {
if (item == null || item.isBlank()) {
return "";
}
String pattern = patterns.get(ThreadLocalRandom.current().nextInt(patterns.size()));
return item != null && !item.isEmpty() ? pattern.replace("{item}", item) : pattern;
return pattern.replace("{item}", item);
}

private static List<String> readDefaultPatterns() {
static List<String> readDefaultPatterns() {
try (InputStream inputStream = SloganGenerator.class.getResourceAsStream("/slogan-patterns.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
return reader.lines().collect(Collectors.toList());
return reader.lines().filter(line -> !line.trim().startsWith("#") && !line.isBlank()).collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException("Failed to load slogan patterns", e);
}
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/se/hirt/slogan/SloganResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,24 @@ public String getTextSlogan(@QueryParam("item") String item) {
@Path("/image")
@Produces("image/png")
public Response getImageSlogan(
@Parameter(description = "The item to generate a slogan for. If 'slogan' is provided, this parameter is ignored.", example = "Java")
@Parameter(description = "The item to generate a slogan for. If 'slogan' is provided, this parameter is ignored. Must be less than 20 characters.", example = "Java")
@QueryParam("item") String item,
@Parameter(description = "A static slogan to use instead of generating one. If provided, 'item' is ignored.", example = "Java: Write once, run anywhere!")
@Parameter(description = "A static slogan to use instead of generating one. If provided, 'item' is ignored. Must be less than 70 characters.", example = "Java: Write once, run anywhere!")
@QueryParam("slogan") String slogan,
@Parameter(description = "The background image to use", example = "random")
@QueryParam("background") @DefaultValue("random") String background,
@Parameter(description = "The color of the text in hexadecimal format", example = "#FFFFFF")
@QueryParam("textColor") @DefaultValue("#FFFFFF") String textColor,
@QueryParam("textcolor") @DefaultValue("#FFFFFF") String textColor,
@Parameter(description = "Whether to add a drop shadow to the text", example = "true")
@QueryParam("dropshadow") @DefaultValue("true") String dropShadow,
@Parameter(description = "The distance of the drop shadow in pixels", example = "2")
@QueryParam("dropshadowdistance") @DefaultValue("2") int dropShadowDistance,
@Parameter(description = "The font size in points", example = "12")
@QueryParam("fontSize") @DefaultValue("12") int fontSize,
@QueryParam("fontsize") @DefaultValue("12") int fontSize,
@Parameter(description = "The name of the font to use", example = "Arial")
@QueryParam("fontName") @DefaultValue("Arial") String fontName,
@QueryParam("fontname") @DefaultValue("Arial") String fontName,
@Parameter(description = "The style of the font (can be 'plain', 'bold', 'italic', or 'bold italic')", example = "bold italic")
@QueryParam("fontStyle") @DefaultValue("bold italic") String fontStyle,
@QueryParam("fontstyle") @DefaultValue("bold italic") String fontStyle,
@Parameter(description = "The opacity of the text (0.0 to 1.0)", example = "1.0")
@QueryParam("opacity") @DefaultValue("1.0") float opacity) throws IOException {

Expand Down
28 changes: 28 additions & 0 deletions src/main/resources/slogan-patterns.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
# Copyright (C) 2024 Marcus Hirt
# www.hirt.se
# This software is free:
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Copyright (C) Marcus Hirt, 2024

{item} - Unleash the power
Experience the magic of {item}
{item}: Your secret weapon
Expand Down
17 changes: 15 additions & 2 deletions src/test/java/se/hirt/slogan/ImageGeneratorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ public class ImageGeneratorTest {

@Test
public void testGenerateImage() throws IOException {
ImageConfig config = new ImageConfig.Builder().background("ocean").textColor("#FFFFFF").dropShadow(true)
.build();
ImageConfig config = new ImageConfig.Builder().background("ocean").textColor("#FFFFFF").dropShadow(true).build();
byte[] imageBytes = imageGenerator.generateImage("JDK Mission Control", config);
assertNotNull(imageBytes);
assertTrue(imageBytes.length > 0);
Expand All @@ -62,4 +61,18 @@ public void testGenerateImage() throws IOException {
assertEquals(460, image.getWidth());
assertEquals(50, image.getHeight());
}

@Test
public void testBadColorConfig() {
ImageConfig config = new ImageConfig.Builder().build();
assertTrue(config.isValid(), "Empty config should be all defaults and fine!");
config = new ImageConfig.Builder().background("ocean").textColor("#FFFFF").dropShadow(true).build();
assertFalse(config.isValid(), "Has invalid color (should be 6 characters after #)");
config = new ImageConfig.Builder().background("ocean").textColor("FFFFFF").dropShadow(true).build();
assertTrue(config.isValid(), "We allow people to forget the pound sign");
config = new ImageConfig.Builder().fontSize(-20).build();
assertFalse(config.isValid(), "We don't allow negative font sizes");
config = new ImageConfig.Builder().dropShadowDistance(400).build();
assertFalse(config.isValid(), "Unreasonable drop shadw distance!");
}
}
21 changes: 21 additions & 0 deletions src/test/java/se/hirt/slogan/SloganGeneratorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand All @@ -51,4 +53,23 @@ public void testGenerateSlogan() {
System.out.println("The slogan was: " + slogan);
assertTrue(slogan.contains("JDK Mission Control"));
}

@Test
public void testAcceptablePatternLengths() {
int acceptableLength = 51 + "{item}".length();
List<String> list = SloganGenerator.readDefaultPatterns();
for (String pattern : list) {
assertTrue(pattern.length() <= acceptableLength, "The pattern " + pattern + " is too long and should be trimmed or removed!");
}
}

@Test
public void testPatternsHaveItem() {
List<String> list = SloganGenerator.readDefaultPatterns();
int i = 0;
for (String pattern : list) {
assertTrue(pattern.contains("{item}"), "Pattern " + i + " (" + pattern + ") didn't contain {item}!");
i++;
}
}
}