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}