summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2015-04-24 15:54:23 +0300
committerVaadin Code Review <review@vaadin.com>2015-05-06 10:02:54 +0000
commit49ded4acd2682cc7dc888fb5d57b8afe47dc30d5 (patch)
tree1c92fe96e8064a7f57cd91206eec7a9be4b71315
parent0fcac7054158ec6dbd8e8707854ee2700be72439 (diff)
downloadvaadin-framework-49ded4acd2682cc7dc888fb5d57b8afe47dc30d5.tar.gz
vaadin-framework-49ded4acd2682cc7dc888fb5d57b8afe47dc30d5.zip
Fix for declarative FontIcon support (#17275)
Change-Id: I5d61ed7003811f95bba4ded71937bb08742936c5
-rw-r--r--server/src/com/vaadin/server/FontAwesome.java25
-rw-r--r--server/src/com/vaadin/server/GenericFontIcon.java144
-rw-r--r--server/src/com/vaadin/server/ResourceReference.java2
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignFormatter.java9
-rw-r--r--server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java185
-rw-r--r--server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java78
-rw-r--r--shared/src/com/vaadin/shared/ApplicationConstants.java8
-rw-r--r--shared/src/com/vaadin/shared/VaadinUriResolver.java2
8 files changed, 420 insertions, 33 deletions
diff --git a/server/src/com/vaadin/server/FontAwesome.java b/server/src/com/vaadin/server/FontAwesome.java
index f226e0c320..4c60b90233 100644
--- a/server/src/com/vaadin/server/FontAwesome.java
+++ b/server/src/com/vaadin/server/FontAwesome.java
@@ -538,7 +538,7 @@ public enum FontAwesome implements FontIcon {
YOUTUBE_PLAY(0XF16A), //
YOUTUBE_SQUARE(0XF166);
- private static final String fontFamily = "FontAwesome";
+ public static final String FONT_FAMILY = "FontAwesome";
private int codepoint;
FontAwesome(int codepoint) {
@@ -563,7 +563,7 @@ public enum FontAwesome implements FontIcon {
*/
@Override
public String getFontFamily() {
- return fontFamily;
+ return FontAwesome.FONT_FAMILY;
}
/*
@@ -578,8 +578,25 @@ public enum FontAwesome implements FontIcon {
@Override
public String getHtml() {
- return "<span class=\"v-icon\" style=\"font-family: " + fontFamily
- + ";\">&#x" + Integer.toHexString(codepoint) + ";</span>";
+ return GenericFontIcon.getHtml(FontAwesome.FONT_FAMILY, codepoint);
+ }
+
+ /**
+ * Finds an instance of FontAwesome with given codepoint
+ *
+ * @since 7.5
+ * @param codepoint
+ * @return FontAwesome instance with a specific codepoint or null if there
+ * isn't any
+ */
+ public static FontAwesome fromCodepoint(final int codepoint) {
+ for (FontAwesome f : values()) {
+ if (f.getCodepoint() == codepoint) {
+ return f;
+ }
+ }
+ throw new IllegalArgumentException("Codepoint " + codepoint
+ + " not found in FontAwesome");
}
}
diff --git a/server/src/com/vaadin/server/GenericFontIcon.java b/server/src/com/vaadin/server/GenericFontIcon.java
new file mode 100644
index 0000000000..ba61390fb0
--- /dev/null
+++ b/server/src/com/vaadin/server/GenericFontIcon.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * 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.vaadin.server;
+
+/**
+ * A generic implementation of {@link FontIcon} interface
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+@SuppressWarnings("serial")
+public class GenericFontIcon implements FontIcon {
+ private final String fontFamily;
+ private final int codePoint;
+
+ /**
+ * Creates a new instance of GenericFontIcon with given font family and
+ * codepoint
+ *
+ * @param fontFamily
+ * Name of the type face that is used to display icons
+ * @param codepoint
+ * Numerical code point in the font
+ */
+ public GenericFontIcon(String fontFamily, int codepoint) {
+ this.fontFamily = fontFamily;
+ codePoint = codepoint;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.server.FontIcon#getFontFamily()
+ */
+ @Override
+ public String getFontFamily() {
+ return fontFamily;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.server.Resource#getMIMEType()
+ */
+ @Override
+ public String getMIMEType() {
+ throw new UnsupportedOperationException(FontIcon.class.getSimpleName()
+ + " should not be used where a MIME type is needed.");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.server.FontIcon#getCodepoint()
+ */
+ @Override
+ public int getCodepoint() {
+ return codePoint;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.server.FontIcon#getHtml()
+ */
+ @Override
+ public String getHtml() {
+ return getHtml(fontFamily, codePoint);
+ }
+
+ /**
+ * Utility method for generating HTML that displays an icon from specific
+ * fontFamiliy with a given codePoint in the font
+ *
+ * @since 7.5
+ * @param fontFamily
+ * Name of the font family
+ * @param codePoint
+ * Icon's character code point in the font
+ * @return
+ */
+ public static String getHtml(String fontFamily, int codePoint) {
+ return "<span class=\"v-icon\" style=\"font-family: " + fontFamily
+ + ";\">&#x" + Integer.toHexString(codePoint) + ";</span>";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + codePoint;
+ result = prime * result
+ + ((fontFamily == null) ? 0 : fontFamily.hashCode());
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof GenericFontIcon)) {
+ return false;
+ }
+ GenericFontIcon other = (GenericFontIcon) obj;
+ if (codePoint != other.codePoint) {
+ return false;
+ }
+ if (fontFamily == null) {
+ if (other.fontFamily != null) {
+ return false;
+ }
+ } else if (!fontFamily.equals(other.fontFamily)) {
+ return false;
+ }
+ return true;
+ }
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/server/ResourceReference.java b/server/src/com/vaadin/server/ResourceReference.java
index 31dfa41ef9..aeb06394b7 100644
--- a/server/src/com/vaadin/server/ResourceReference.java
+++ b/server/src/com/vaadin/server/ResourceReference.java
@@ -64,7 +64,7 @@ public class ResourceReference extends URLReference {
String uri = getConnectorResourceBase(prefix, connector);
return uri;
} else if (resource instanceof ThemeResource) {
- final String uri = "theme://"
+ final String uri = ApplicationConstants.THEME_PROTOCOL_PREFIX
+ ((ThemeResource) resource).getResourceId();
return uri;
} else if (resource instanceof FontIcon) {
diff --git a/server/src/com/vaadin/ui/declarative/DesignFormatter.java b/server/src/com/vaadin/ui/declarative/DesignFormatter.java
index 728b3d1077..b1d2520631 100644
--- a/server/src/com/vaadin/ui/declarative/DesignFormatter.java
+++ b/server/src/com/vaadin/ui/declarative/DesignFormatter.java
@@ -325,9 +325,8 @@ public class DesignFormatter implements Serializable {
// component.
return (Converter<String, T>) stringObjectConverter;
}
- if (sourceType.isEnum()) {
- return (Converter<String, T>) stringEnumConverter;
- } else if (converterMap.containsKey(sourceType)) {
+
+ if (converterMap.containsKey(sourceType)) {
return ((Converter<String, T>) converterMap.get(sourceType));
} else if (!strict) {
for (Class<?> supported : converterMap.keySet()) {
@@ -336,6 +335,10 @@ public class DesignFormatter implements Serializable {
}
}
}
+
+ if (sourceType.isEnum()) {
+ return (Converter<String, T>) stringEnumConverter;
+ }
return null;
}
diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java
index 21f20e6403..ff73b61eb2 100644
--- a/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java
+++ b/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java
@@ -16,14 +16,20 @@
package com.vaadin.ui.declarative.converters;
import java.io.File;
+import java.io.Serializable;
+import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.FileResource;
import com.vaadin.server.FontAwesome;
+import com.vaadin.server.FontIcon;
+import com.vaadin.server.GenericFontIcon;
import com.vaadin.server.Resource;
import com.vaadin.server.ThemeResource;
+import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.declarative.DesignAttributeHandler;
/**
@@ -33,21 +39,28 @@ import com.vaadin.ui.declarative.DesignAttributeHandler;
* @since 7.4
* @author Vaadin Ltd
*/
+@SuppressWarnings("serial")
public class DesignResourceConverter implements Converter<String, Resource> {
@Override
public Resource convertToModel(String value,
Class<? extends Resource> targetType, Locale locale)
throws Converter.ConversionException {
- if (value.startsWith("http://") || value.startsWith("https://")
- || value.startsWith("ftp://") || value.startsWith("ftps://")) {
- return new ExternalResource(value);
- } else if (value.startsWith("theme://")) {
- return new ThemeResource(value.substring(8));
- } else if (value.startsWith("font://")) {
- return FontAwesome.valueOf(value.substring(7));
- } else {
- return new FileResource(new File(value));
+ if (!value.contains("://")) {
+ // assume it'is "file://" protocol, one that is used to access a
+ // file on a given path on the server, this will later be striped
+ // out anyway
+ value = "file://" + value;
+ }
+
+ String protocol = value.split("://")[0];
+ try {
+ ResourceConverterByProtocol converter = ResourceConverterByProtocol
+ .valueOf(protocol.toUpperCase(Locale.ENGLISH));
+ return converter.parse(value);
+ } catch (IllegalArgumentException iae) {
+ throw new ConversionException("Unrecognized protocol: " + protocol,
+ iae);
}
}
@@ -55,20 +68,10 @@ public class DesignResourceConverter implements Converter<String, Resource> {
public String convertToPresentation(Resource value,
Class<? extends String> targetType, Locale locale)
throws Converter.ConversionException {
- if (value instanceof ExternalResource) {
- return ((ExternalResource) value).getURL();
- } else if (value instanceof ThemeResource) {
- return "theme://" + ((ThemeResource) value).getResourceId();
- } else if (value instanceof FontAwesome) {
- return "font://" + ((FontAwesome) value).name();
- } else if (value instanceof FileResource) {
- String path = ((FileResource) value).getSourceFile().getPath();
- if (File.separatorChar != '/') {
- // make sure we use '/' as file separator in templates
- return path.replace(File.separatorChar, '/');
- } else {
- return path;
- }
+ ResourceConverterByProtocol byType = ResourceConverterByProtocol
+ .byType(value.getClass());
+ if (byType != null) {
+ return byType.format(value);
} else {
throw new Converter.ConversionException("unknown Resource type - "
+ value.getClass().getName());
@@ -85,4 +88,140 @@ public class DesignResourceConverter implements Converter<String, Resource> {
return String.class;
}
+ private static interface ProtocolResourceConverter extends Serializable {
+ public String format(Resource value);
+
+ public Resource parse(String value);
+ }
+
+ private static enum ResourceConverterByProtocol implements
+ ProtocolResourceConverter {
+
+ HTTP, HTTPS, FTP, FTPS, THEME {
+
+ @Override
+ public Resource parse(String value) {
+ // strip "theme://" from the url, use the rest as the resource
+ // id
+ return new ThemeResource(value.substring(8));
+ }
+
+ @Override
+ public String format(Resource value)
+ throws Converter.ConversionException {
+ return ApplicationConstants.THEME_PROTOCOL_PREFIX
+ + ((ThemeResource) value).getResourceId();
+ }
+ },
+ FONTICON {
+ @Override
+ public Resource parse(String value) {
+ final String address = (value.split("://", 2))[1];
+ final String[] familyAndCode = address.split("/", 2);
+ final int codepoint = Integer.valueOf(
+ familyAndCode[1].substring(2), 16);
+
+ if (FontAwesome.FONT_FAMILY.equals(familyAndCode[0])) {
+ try {
+ return FontAwesome.fromCodepoint(codepoint);
+ } catch (IllegalArgumentException iae) {
+ throw new ConversionException(
+ "Unknown codepoint in FontAwesome: "
+ + codepoint, iae);
+ }
+ }
+
+ FontIcon generic = new GenericFontIcon(familyAndCode[0],
+ codepoint);
+ return generic;
+
+ }
+
+ @Override
+ public String format(Resource value)
+ throws Converter.ConversionException {
+ FontIcon icon = (FontIcon) value;
+ return ApplicationConstants.FONTICON_PROTOCOL_PREFIX
+ + icon.getFontFamily() + "/0x"
+ + Integer.toHexString(icon.getCodepoint());
+ }
+ },
+ @Deprecated
+ FONT {
+ @Override
+ public Resource parse(String value) {
+ // Deprecated, 7.4 syntax is
+ // font://"+FontAwesome.valueOf(foo) eg. "font://AMBULANCE"
+ final String iconName = (value.split("://", 2))[1];
+
+ try {
+ return FontAwesome.valueOf(iconName);
+ } catch (IllegalArgumentException iae) {
+ throw new ConversionException("Unknown FontIcon constant: "
+ + iconName, iae);
+ }
+ }
+
+ @Override
+ public String format(Resource value)
+ throws Converter.ConversionException {
+ throw new UnsupportedOperationException("Use "
+ + ResourceConverterByProtocol.FONTICON.toString()
+ + " instead");
+ }
+ },
+ FILE {
+ @Override
+ public Resource parse(String value) {
+ return new FileResource(new File(value.split("://")[1]));
+ }
+
+ @Override
+ public String format(Resource value)
+ throws Converter.ConversionException {
+ String path = ((FileResource) value).getSourceFile().getPath();
+ if (File.separatorChar != '/') {
+ // make sure we use '/' as file separator in templates
+ return path.replace(File.separatorChar, '/');
+ } else {
+ return path;
+ }
+ }
+
+ };
+
+ @Override
+ public Resource parse(String value) {
+ // default behavior for HTTP, HTTPS, FTP and FTPS
+ return new ExternalResource(value);
+ }
+
+ @Override
+ public String format(Resource value)
+ throws Converter.ConversionException {
+ // default behavior for HTTP, HTTPS, FTP and FTPS
+ return ((ExternalResource) value).getURL();
+ }
+
+ private static Map<Class<? extends Resource>, ResourceConverterByProtocol> typeToConverter = new HashMap<Class<? extends Resource>, ResourceConverterByProtocol>();
+ static {
+ typeToConverter.put(ExternalResource.class, HTTP);
+ // ^ any of non-specialized would actually work
+ typeToConverter.put(ThemeResource.class, THEME);
+ typeToConverter.put(FontIcon.class, FONTICON);
+ typeToConverter.put(FileResource.class, FILE);
+
+ }
+
+ public static ResourceConverterByProtocol byType(
+ Class<? extends Resource> resourceType) {
+ for (Class<?> type : typeToConverter.keySet()) {
+ if (type.isAssignableFrom(resourceType)) {
+ return typeToConverter.get(type);
+ }
+ }
+ return null;
+ }
+ }
+
}
diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java
index 681b9d80a3..dcabd6c637 100644
--- a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java
+++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java
@@ -18,6 +18,7 @@ package com.vaadin.tests.design;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import java.io.File;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -34,8 +35,12 @@ import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutAction.ModifierKey;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.FileResource;
+import com.vaadin.server.FontAwesome;
+import com.vaadin.server.FontIcon;
+import com.vaadin.server.GenericFontIcon;
import com.vaadin.server.Resource;
import com.vaadin.server.ThemeResource;
+import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.declarative.DesignFormatter;
@@ -265,6 +270,78 @@ public class DesignFormatterTest {
}
}
+ @Test
+ public void testResourceFormat() {
+ String httpUrl = "http://example.com/icon.png";
+ String httpsUrl = "https://example.com/icon.png";
+ String themePath = "icons/icon.png";
+ String fontAwesomeUrl = "fonticon://FontAwesome/0xf0f9";
+ String someOtherFontUrl = "fonticon://SomeOther/0xF0F9";
+ String fileSystemPath = "c:\\app\\resources\\icon.png";
+
+ assertEquals(httpUrl, formatter.format(new ExternalResource(httpUrl)));
+ assertEquals(httpsUrl, formatter.format(new ExternalResource(httpsUrl)));
+ assertEquals(ApplicationConstants.THEME_PROTOCOL_PREFIX + themePath,
+ formatter.format(new ThemeResource(themePath)));
+
+ assertEquals(fontAwesomeUrl, formatter.format(FontAwesome.AMBULANCE));
+ assertEquals(someOtherFontUrl.toLowerCase(),
+ formatter.format(new GenericFontIcon("SomeOther", 0xf0f9))
+ .toLowerCase());
+
+ assertEquals(fileSystemPath,
+ formatter.format(new FileResource(new File(fileSystemPath))));
+ }
+
+ @Test(expected = ConversionException.class)
+ public void testResourceParseException() {
+ String someRandomResourceUrl = "random://url";
+ formatter.parse(someRandomResourceUrl, Resource.class);
+ }
+
+ @Test(expected = ConversionException.class)
+ public void testResourceFormatException() {
+ formatter.format(new Resource() { // must use unknown resource type
+ @Override
+ public String getMIMEType() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ });
+ }
+
+ @Test
+ public void testResourceParse() {
+ String httpUrl = "http://example.com/icon.png";
+ String httpsUrl = "https://example.com/icon.png";
+ String themePath = "icons/icon.png";
+ String fontAwesomeUrl = "fonticon://FontAwesome/0xf0f9";
+ String someOtherFont = "fonticon://SomeOther/0xF0F9";
+ String fontAwesomeUrlOld = "font://AMBULANCE";
+ String fileSystemPath = "c:\\app\\resources\\icon.png";
+
+ assertEquals(new ExternalResource(httpUrl).getURL(),
+ formatter.parse(httpUrl, ExternalResource.class).getURL());
+ assertEquals(new ExternalResource(httpsUrl).getURL(),
+ formatter.parse(httpsUrl, ExternalResource.class).getURL());
+ assertEquals(
+ new ThemeResource(themePath),
+ formatter.parse(ApplicationConstants.THEME_PROTOCOL_PREFIX
+ + themePath, ThemeResource.class));
+ assertEquals(FontAwesome.AMBULANCE,
+ formatter.parse(fontAwesomeUrlOld, FontAwesome.class));
+ assertEquals(FontAwesome.AMBULANCE,
+ formatter.parse(fontAwesomeUrl, FontAwesome.class));
+ assertEquals(new GenericFontIcon("SomeOther", 0xF0F9),
+ formatter.parse(someOtherFont, FontIcon.class));
+
+ assertEquals(
+ new FileResource(new File(fileSystemPath)).getSourceFile(),
+ formatter.parse(fileSystemPath, FileResource.class)
+ .getSourceFile());
+
+ }
+
/**
* A static method to allow comparison two different actions.
*
@@ -294,5 +371,4 @@ public class DesignFormatterTest {
}
return false;
}
-
}
diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java
index d7aaee6267..fa6aa33fc0 100644
--- a/shared/src/com/vaadin/shared/ApplicationConstants.java
+++ b/shared/src/com/vaadin/shared/ApplicationConstants.java
@@ -39,6 +39,14 @@ public class ApplicationConstants implements Serializable {
public static final String PUBLISHED_PROTOCOL_NAME = "published";
public static final String PUBLISHED_PROTOCOL_PREFIX = PUBLISHED_PROTOCOL_NAME
+ "://";
+ /**
+ * Prefix used for theme resource URLs
+ *
+ * @see com.vaadin.server.ThemeResource
+ * @since
+ */
+ public static final String THEME_PROTOCOL_PREFIX = "theme://";
+
public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key";
@Deprecated
diff --git a/shared/src/com/vaadin/shared/VaadinUriResolver.java b/shared/src/com/vaadin/shared/VaadinUriResolver.java
index b45d32f71a..ee8d13f10f 100644
--- a/shared/src/com/vaadin/shared/VaadinUriResolver.java
+++ b/shared/src/com/vaadin/shared/VaadinUriResolver.java
@@ -58,7 +58,7 @@ public abstract class VaadinUriResolver implements Serializable {
if (vaadinUri == null) {
return null;
}
- if (vaadinUri.startsWith("theme://")) {
+ if (vaadinUri.startsWith(ApplicationConstants.THEME_PROTOCOL_PREFIX)) {
final String themeUri = getThemeUri();
vaadinUri = themeUri + vaadinUri.substring(7);
}