/**
* Copyright (C) 2015 Valkyrie RCP
*
* 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.valkyriercp.image;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.binding.collection.AbstractCachingMapDecorator;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.style.StylerUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.awt.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* A collection of image resources, each indexed by a common key alias.
* <p>
* For example, <code>action.edit.copy = /images/edit/copy.gif</code>
* <p>
* This class by default performs caching of all loaded image resources using
* soft references (TODO it just lazy loads them, but it doesn't use
* SoftReference).
*
* <p>Image resources can be set in the constructor or through the property <code>imageResources</code>.
* The may should be a String-Resource or String-String where a string value will be converted to
* a resource using the configured <code>ResourceLoader</code>. Because the value part of the pairs can be a
* string you can use the following code in your context to configure the map:<pre>
* <bean id="imageResourcesFactory" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:org/springframework/richclient/image/images.properties</value>
<value>...your other locations here...</value>
</list>
</property>
</bean>
<bean id="imageSource" class="test.DefaultImageSource">
<property name="brokenImageIndicator"
value="/org/springframework/richclient/images/alert/error_obj.gif" />
<property name="imageResources" ref="imageResourcesFactory"/>
</bean>
* </pre>
* Spring 2.5 users can also use the deprecated <code>ResourceMapFactoryBean</code>.
*
* <p>
* An image {@link Handler} is available that handles the 'image' protocol.
* Check the javadocs of the handler to know how to use/register it.
* </p>
*
* @author Keith Donald
*/
@Component
public class DefaultImageSource implements ImageSource {
protected static final Log logger = LogFactory.getLog(DefaultImageSource.class);
private Map imageResources;
private ImageCache imageCache;
private AwtImageResource brokenImageIndicatorResource;
private Image brokenImageIndicator;
@Autowired
private ResourceLoader resourceLoader;
/**
* Creates a image resource bundle containing the specified map of keys to
* resource paths.
* <p>
* A custom URL protocol {@link Handler handler}will be installed for the
* "image:" protocol. This allows for images in this image source to be
* located using the Java URL classes: <br>
* <code>URL imageUrl = new URL("image:the.image.key")</code>
*
* @param imageResources a map of key-to-image-resources.
*/
public DefaultImageSource(Map imageResources) {
this(true, imageResources);
}
/**
* Creates a image resource bundle containing the specified map of keys to
* resource paths.
*
* @param installUrlHandler should a URL handler be installed.
* @param imageResources a map of key-to-image-resources.
*/
public DefaultImageSource(boolean installUrlHandler, Map imageResources) {
Assert.notNull(imageResources);
this.imageResources = new HashMap(imageResources);
debugPrintResources();
this.imageCache = new ImageCache();
if (installUrlHandler) {
Handler.installImageUrlHandler(this);
}
}
public DefaultImageSource() {
this.imageCache = new ImageCache();
Handler.installImageUrlHandler(this);
}
public void setImageResources(Map imageResources) {
this.imageResources = new HashMap(imageResources);
debugPrintResources();
}
private void debugPrintResources() {
if (logger.isDebugEnabled()) {
logger.debug("Initialing image source with resources: " + StylerUtils.style(this.imageResources));
}
}
public Image getImage(String key) {
Assert.notNull(key);
AwtImageResource resource = getImageResource(key);
try {
return (Image) imageCache.get(resource);
}
catch (RuntimeException e) {
if (brokenImageIndicator != null) {
return returnBrokenImageIndicator(resource);
}
throw e;
}
}
public AwtImageResource getImageResource(String key) {
Assert.notNull(key);
Resource resource = null;
final Object tmp = imageResources.get(key);
if(tmp instanceof Resource)
resource = (Resource)tmp;
if(tmp instanceof String) {
resource = resourceLoader.getResource((String)tmp);
Assert.notNull(resourceLoader, "Resource loader must be set to resolve resources");
}
if (resource == null) {
throw new NoSuchImageResourceException(key);
}
try {
resource.getInputStream();
return new AwtImageResource(resource);
}
catch (IOException e) {
if (brokenImageIndicatorResource == null) {
throw new NoSuchImageResourceException(resource, e);
}
logger.warn("Unable to load image resource at '" + resource + "'; returning the broken image indicator.");
return brokenImageIndicatorResource;
}
}
public boolean containsKey(Object key) {
return imageResources.containsKey(key);
}
private Image returnBrokenImageIndicator(Resource resource) {
logger.warn("Unable to load image resource at '" + resource + "'; returning the broken image indicator.");
return brokenImageIndicator;
}
public Image getImageAtLocation(Resource location) {
try {
return new AwtImageResource(location).getImage();
}
catch (IOException e) {
if (brokenImageIndicator == null) {
throw new NoSuchImageResourceException(location, e);
}
return returnBrokenImageIndicator(location);
}
}
public int size() {
return imageResources.size();
}
public void setBrokenImageIndicator(Resource resource) {
try {
brokenImageIndicatorResource = new AwtImageResource(resource);
brokenImageIndicator = brokenImageIndicatorResource.getImage();
}
catch (IOException e) {
brokenImageIndicatorResource = null;
throw new NoSuchImageResourceException(resource, e);
}
}
public String toString() {
return new ToStringCreator(this).append("imageResources", imageResources).toString();
}
private static class ImageCache extends AbstractCachingMapDecorator {
public ImageCache() {
super(true);
}
public Object create(Object resource) {
try {
return ((AwtImageResource) resource).getImage();
}
catch (IOException e) {
throw new NoSuchImageResourceException("No image found at resource '" + resource + '"', e);
}
}
}
}