001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.runtime.imageloader.awt; 021 022import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState; 023import static org.hamcrest.CoreMatchers.is; 024 025import java.awt.Canvas; 026import java.awt.Image; 027import java.awt.MediaTracker; 028import java.awt.Toolkit; 029import java.awt.image.IndexColorModel; 030import java.awt.image.MemoryImageSource; 031import java.io.File; 032import java.net.URL; 033import java.util.ArrayList; 034import java.util.Hashtable; 035import java.util.List; 036import java.util.Vector; 037 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import org.apache.isis.core.commons.config.IsisConfiguration; 042import org.apache.isis.core.commons.lang.ResourceUtil; 043import org.apache.isis.core.runtime.imageloader.TemplateImage; 044import org.apache.isis.core.runtime.imageloader.TemplateImageImpl; 045import org.apache.isis.core.runtime.imageloader.TemplateImageLoader; 046 047/** 048 * This class loads up file based images as resources (part of the classpath) or 049 * from the file system. Images of type PNG, GIF and JPEG will be used. The 050 * default directory is images. 051 */ 052public class TemplateImageLoaderAwt implements TemplateImageLoader { 053 054 private final static Logger LOG = LoggerFactory.getLogger(TemplateImageLoaderAwt.class); 055 056 private static final String LOAD_IMAGES_FROM_FILES_KEY = ImageConstants.PROPERTY_BASE + "load-images-from-files"; 057 private static final String[] EXTENSIONS = { "png", "gif", "jpg", "jpeg", "svg" }; 058 private final static String IMAGE_DIRECTORY = "images"; 059 private final static String IMAGE_DIRECTORY_PARAM = ImageConstants.PROPERTY_BASE + "image-directory"; 060 private static final String SEPARATOR = "/"; 061 062 private boolean initialized; 063 064 private boolean alsoLoadAsFiles; 065 protected final MediaTracker mt = new MediaTracker(new Canvas()); 066 067 /** 068 * A keyed list of core images, one for each name, keyed by the image path. 069 */ 070 private final Hashtable<String, TemplateImage> loadedImages = new Hashtable<String, TemplateImage>(); 071 private final Vector<String> missingImages = new Vector<String>(); 072 private final IsisConfiguration configuration; 073 private String directory; 074 075 // //////////////////////////////////////////////////////////// 076 // constructor 077 // //////////////////////////////////////////////////////////// 078 079 public TemplateImageLoaderAwt(final IsisConfiguration configuration) { 080 this.configuration = configuration; 081 } 082 083 // //////////////////////////////////////////////////////////// 084 // init, shutdown 085 // //////////////////////////////////////////////////////////// 086 087 @Override 088 public void init() { 089 ensureNotInitialized(); 090 LOG.info("images to be loaded from " + directory()); 091 alsoLoadAsFiles = getConfiguration().getBoolean(LOAD_IMAGES_FROM_FILES_KEY, true); 092 initialized = true; 093 } 094 095 @Override 096 public void shutdown() { 097 } 098 099 private void ensureNotInitialized() { 100 ensureThatState(initialized, is(false)); 101 } 102 103 private void ensureInitialized() { 104 ensureThatState(initialized, is(true)); 105 } 106 107 // //////////////////////////////////////////////////////////// 108 // getTemplateImage 109 // //////////////////////////////////////////////////////////// 110 111 /** 112 * Returns an image template for the specified image (as specified by a path 113 * to a file or resource). 114 * 115 * <p> 116 * If the path has no extension (<tt>.gif</tt>, <tt>.png</tt> etc) then all 117 * valid {@link #EXTENSIONS extensions} are searched for. 118 * 119 * <p> 120 * This method attempts to load the image from the jar/zip file this class 121 * was loaded from ie, your application, and then from the file system as a 122 * file if can't be found as a resource. If neither method works the default 123 * image is returned. 124 * 125 * @return returns a {@link TemplateImage} for the specified image file, or 126 * null if none found. 127 */ 128 @Override 129 public TemplateImage getTemplateImage(final String name) { 130 ensureInitialized(); 131 132 if (loadedImages.containsKey(name)) { 133 return loadedImages.get(name); 134 } 135 136 if (missingImages.contains(name)) { 137 return null; 138 } 139 140 final List<String> candidates = getCandidates(name); 141 for (final String candidate : candidates) { 142 final Image image = load(candidate); 143 final TemplateImageImpl templateImage = TemplateImageImpl.create(image); 144 if (templateImage != null) { 145 loadedImages.put(name, templateImage); 146 return templateImage; 147 } 148 } 149 150 if (LOG.isDebugEnabled()) { 151 LOG.debug("failed to find image for " + name); 152 } 153 missingImages.addElement(name); 154 return null; 155 } 156 157 // //////////////////////////////////////////////////////////// 158 // helpers: parsing path 159 // //////////////////////////////////////////////////////////// 160 161 private List<String> getCandidates(final String name) { 162 boolean hasExtension = false; 163 for (final String extension : EXTENSIONS) { 164 hasExtension = hasExtension || name.endsWith(extension); 165 } 166 167 final List<String> candidates = new ArrayList<String>(); 168 if (hasExtension) { 169 candidates.add(name); 170 } else { 171 for (final String extension : EXTENSIONS) { 172 candidates.add(name + "." + extension); 173 } 174 } 175 return candidates; 176 } 177 178 // //////////////////////////////////////////////////////////// 179 // helpers: loading 180 // //////////////////////////////////////////////////////////// 181 182 private Image load(final String name) { 183 if (LOG.isDebugEnabled()) { 184 LOG.debug("searching for image " + name); 185 } 186 187 Image image = loadAsResource(name); 188 if (image != null) { 189 return image; 190 } 191 192 final String path = directory() + name; 193 image = loadAsResource(path); 194 if (image != null) { 195 return image; 196 } 197 198 if (alsoLoadAsFiles) { 199 image = loadAsFile(path); 200 if (image != null) { 201 return image; 202 } 203 } 204 205 return null; 206 } 207 208 /** 209 * Get an Image object from the jar/zip file that this class was loaded 210 * from. 211 */ 212 protected Image loadAsResource(final String path) { 213 final URL url = ResourceUtil.getResourceURL(path); 214 if (url == null) { 215 LOG.debug("not found image in resources: " + path); 216 return null; 217 } 218 219 Image image = Toolkit.getDefaultToolkit().getImage(url); 220 if (image != null) { 221 mt.addImage(image, 0); 222 try { 223 mt.waitForAll(); 224 } catch (final Exception e) { 225 e.printStackTrace(); 226 } 227 228 if (mt.isErrorAny()) { 229 LOG.error("found image but failed to load it from resources: " + url + " " + mt.getErrorsAny()[0]); 230 mt.removeImage(image); 231 image = null; 232 } else { 233 mt.removeImage(image); 234 LOG.info("image loaded from resources: " + url); 235 } 236 } 237 238 if (image == null) { 239 throw new RuntimeException("null image"); 240 } else { 241 if (image.getWidth(null) == -1) { 242 throw new RuntimeException(image.toString()); 243 } 244 } 245 246 return image; 247 } 248 249 /** 250 * Get an {@link Image} object from the specified file path on the file 251 * system. 252 */ 253 private Image loadAsFile(final String path) { 254 final File file = new File(path); 255 256 if (!file.exists()) { 257 return null; 258 } 259 final Toolkit t = Toolkit.getDefaultToolkit(); 260 Image image = t.getImage(file.getAbsolutePath()); 261 262 if (image != null) { 263 mt.addImage(image, 0); 264 265 try { 266 mt.waitForAll(); 267 } catch (final Exception e) { 268 e.printStackTrace(); 269 } 270 271 if (mt.isErrorAny()) { 272 LOG.error("found image file but failed to load it: " + file.getAbsolutePath()); 273 mt.removeImage(image); 274 image = null; 275 } else { 276 mt.removeImage(image); 277 LOG.info("image loaded from file: " + file); 278 } 279 } 280 return image; 281 } 282 283 private String directory() { 284 if (directory == null) { 285 directory = getConfiguration().getString(IMAGE_DIRECTORY_PARAM, IMAGE_DIRECTORY); 286 if (!directory.endsWith(SEPARATOR)) { 287 directory = directory.concat(SEPARATOR); 288 } 289 } 290 return directory; 291 } 292 293 // //////////////////////////////////////////////////////////// 294 // unused 295 // //////////////////////////////////////////////////////////// 296 297 /** 298 * This code was commented out. I've reinstated it, even though it is 299 * unused, because it looks interesting and perhaps useful. 300 */ 301 @SuppressWarnings("unused") 302 private Image createImage() { 303 final byte[] pixels = new byte[128 * 128]; 304 for (int i = 0; i < pixels.length; i++) { 305 pixels[i] = (byte) (i % 128); 306 } 307 308 final byte[] r = new byte[] { 0, 127 }; 309 final byte[] g = new byte[] { 0, 127 }; 310 final byte[] b = new byte[] { 0, 127 }; 311 final IndexColorModel colorModel = new IndexColorModel(1, 2, r, g, b); 312 313 final MemoryImageSource producer = new MemoryImageSource(128, 128, colorModel, pixels, 0, 128); 314 final Image image = Toolkit.getDefaultToolkit().createImage(producer); 315 316 return image; 317 } 318 319 // //////////////////////////////////////////////////////////// 320 // dependencies (from singleton) 321 // //////////////////////////////////////////////////////////// 322 323 private IsisConfiguration getConfiguration() { 324 return configuration; 325 } 326 327}