org.apache.wicket.markup.html.image.resource.LocalizedImageResource.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.markup.html.image.resource.LocalizedImageResource.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.wicket.markup.html.image.resource;

import java.util.Locale;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IResourceFactory;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.border.Border;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.IResource.Attributes;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.lang.Objects;
import org.apache.wicket.util.parse.metapattern.Group;
import org.apache.wicket.util.parse.metapattern.MetaPattern;
import org.apache.wicket.util.parse.metapattern.OptionalMetaPattern;
import org.apache.wicket.util.parse.metapattern.parsers.MetaPatternParser;
import org.apache.wicket.util.string.Strings;

/**
 * THIS CLASS IS INTENDED FOR INTERNAL USE IN IMPLEMENTING LOCALE SENSITIVE COMPONENTS THAT USE
 * IMAGE RESOURCES AND SHOULD NOT BE USED DIRECTLY BY END-USERS.
 * <p>
 * This class contains the logic for extracting static image resources referenced by the SRC
 * attribute of component tags and keeping these static image resources in sync with the component
 * locale.
 * <p>
 * If no image is specified by the SRC attribute of an IMG tag, then any VALUE attribute is
 * inspected. If there is a VALUE attribute, it must be of the form
 * "[factoryName]:[sharedImageName]?:[specification]". [factoryName] is the name of a resource
 * factory that has been added to Application (for example, DefaultButtonImageResourceFactory is
 * installed by default under the name "buttonFactory"). The [sharedImageName] value is optional and
 * gives a name under which a given generated image is shared. For example, a cancel button image
 * generated by the VALUE attribute "buttonFactory:cancelButton:Cancel" is shared under the name
 * "cancelButton" and this specification will cause a component to reference the same image resource
 * no matter what page it appears on, which is a very convenient and efficient way to create and
 * share images. The [specification] string which follows the second colon is passed directly to the
 * image factory and its format is dependent on the specific image factory. For details on the
 * default buttonFactory, see
 * {@link org.apache.wicket.markup.html.image.resource.DefaultButtonImageResourceFactory}.
 * <p>
 * Finally, if there is no SRC attribute and no VALUE attribute, the Image component's model is
 * inspected. If the model contains a resource or resource reference, this image is used, otherwise
 * the model is converted to a String and that value is used as a path to load the image.
 * 
 * @author Jonathan Locke
 */
public final class LocalizedImageResource implements IClusterable {
    private static final long serialVersionUID = 1L;

    /**
     * What kind of resource it is. TRUE==Resource is set, FALSE==ResourceReference is set, null
     * none
     */
    private Boolean resourceKind;

    /** The component that is referencing this image resource */
    private final Component component;

    /** The image resource this image component references */
    private IResource resource;

    /** The resource reference */
    private ResourceReference resourceReference;

    /** The resource parameters */
    private PageParameters resourceParameters;

    /** The locale of the image resource */
    private Locale locale;

    /** The style of the image resource */
    private String style;

    /** The component's variation (of the style) */
    private String variation;

    /**
     * Parses image value specifications of the form "[factoryName]:
     * [shared-image-name]?:[specification]"
     * 
     * @author Jonathan Locke
     */
    private static final class ImageValueParser extends MetaPatternParser {
        /** Factory name */
        private static final Group factoryName = new Group(MetaPattern.VARIABLE_NAME);

        /** Image reference name */
        private static final Group imageReferenceName = new Group(MetaPattern.VARIABLE_NAME);

        /** Factory specification string */
        private static final Group specification = new Group(MetaPattern.ANYTHING_NON_EMPTY);

        /** Meta pattern. */
        private static final MetaPattern pattern = new MetaPattern(factoryName, MetaPattern.COLON,
                new OptionalMetaPattern(new MetaPattern[] { imageReferenceName }), MetaPattern.COLON,
                specification);

        /**
         * Construct.
         * 
         * @param input
         *            to parse
         */
        private ImageValueParser(final CharSequence input) {
            super(pattern, input);
        }

        /**
         * @return The factory name
         */
        private String getFactoryName() {
            return factoryName.get(matcher());
        }

        /**
         * @return Returns the imageReferenceName.
         */
        private String getImageReferenceName() {
            return imageReferenceName.get(matcher());
        }

        /**
         * @return Returns the specification.
         */
        private String getSpecification() {
            return specification.get(matcher());
        }
    }

    /**
     * Constructor
     * 
     * @param component
     *            The component that owns this localized image resource
     */
    public LocalizedImageResource(final Component component) {
        this.component = component;
        locale = component.getLocale();
        style = component.getStyle();
        variation = component.getVariation();
    }

    /**
     * Binds this resource if it is shared
     */
    public final void bind() {
        // If we have a resource reference
        if (resourceReference != null && resourceReference.canBeRegistered() && Application.exists()) {
            // Bind the reference to the application
            Application.get().getResourceReferenceRegistry().registerResourceReference(resourceReference);
        }
    }

    /**
     * @param parameters
     *            page parameters
     */
    public final void onResourceRequested(PageParameters parameters) {
        bind();
        RequestCycle requestCycle = RequestCycle.get();
        Attributes attributes = new Attributes(requestCycle.getRequest(), requestCycle.getResponse(), parameters);
        resource.respond(attributes);
    }

    /**
     * @param resource
     *            The resource to set.
     */
    public final void setResource(final IResource resource) {
        if (this.resource != resource) {
            resourceKind = Boolean.TRUE;
            this.resource = resource;
        }
    }

    /**
     * @param resourceReference
     *            The resource to set.
     */
    public final void setResourceReference(final ResourceReference resourceReference) {
        setResourceReference(resourceReference, resourceParameters);
    }

    /**
     * @return true if it has a resourceReference. (it points to a shared resource)
     */
    public final boolean isStateless() {
        return resourceReference != null;
    }

    /**
     * @param resourceReference
     *            The resource to set.
     * @param resourceParameters
     *            The resource parameters for the shared resource
     */
    public final void setResourceReference(final ResourceReference resourceReference,
            final PageParameters resourceParameters) {
        if (resourceReference != this.resourceReference) {
            resourceKind = Boolean.FALSE;
            this.resourceReference = resourceReference;
        }
        this.resourceParameters = resourceParameters;
        bind();
    }

    /**
     * @param tag
     *            The tag to inspect for an optional src attribute that might reference an image.
     * @throws WicketRuntimeException
     *             Thrown if an image is required by the caller, but none can be found.
     */
    public final void setSrcAttribute(final ComponentTag tag) {
        // If locale has changed from the initial locale used to attach image
        // resource, then we need to reload the resource in the new locale
        Locale l = component.getLocale();
        String s = component.getStyle();
        String v = component.getVariation();
        if (resourceKind == null
                && (!Objects.equal(locale, l) || !Objects.equal(style, s) || !Objects.equal(variation, v))) {
            // Get new component locale and style
            locale = l;
            style = s;
            variation = v;

            // Invalidate current resource so it will be reloaded/recomputed
            resourceReference = null;
            resource = null;
        } else {
            // TODO post 1.2: should we have support for locale changes when the
            // resource reference (or resource??) is set manually..
            // We should get a new resource reference for the current locale
            // then that points to the same resource but with another locale if
            // it exists. Something like
            // SharedResource.getResourceReferenceForLocale(resourceReference);
        }

        // check if the model contains a resource, if so, load the resource from
        // the model.
        Object modelObject = component.getDefaultModelObject();
        if (modelObject instanceof ResourceReference) {
            resourceReference = (ResourceReference) modelObject;
        } else if (modelObject instanceof IResource) {
            resource = (IResource) modelObject;
        }

        // Need to load image resource for this component?
        if (resource == null && resourceReference == null) {
            // Get SRC attribute of tag
            final CharSequence src = tag.getAttribute("src");
            if (src != null) {
                // Try to load static image
                loadStaticImage(src.toString());
            } else {
                // Get VALUE attribute of tag
                final CharSequence value = tag.getAttribute("value");
                if (value != null) {
                    // Try to generate an image using an image factory
                    newImage(value);
                } else {
                    // Load static image using model object as the path
                    loadStaticImage(component.getDefaultModelObjectAsString());
                }
            }
        }

        // Get URL for resource
        final CharSequence url;
        if (resourceReference != null) {
            // Create URL to resource
            url = RequestCycle.get().urlFor(resourceReference, resourceParameters);
        } else {
            // Create URL to component
            url = component.urlForListener(resourceParameters);
        }

        // Set the SRC attribute to point to the component or shared resource
        tag.put("src", url);
    }

    /**
     * @param application
     *            The application
     * @param factoryName
     *            The name of the image resource factory
     * @return The resource factory
     * @throws WicketRuntimeException
     *             Thrown if factory cannot be found
     */
    private IResourceFactory getResourceFactory(final Application application, final String factoryName) {
        final IResourceFactory factory = application.getResourceSettings().getResourceFactory(factoryName);

        // Found factory?
        if (factory == null) {
            throw new WicketRuntimeException("Could not find image resource factory named " + factoryName);
        }
        return factory;
    }

    static class SimpleStaticResourceReference extends ResourceReference {
        final IResource resource;

        public SimpleStaticResourceReference(Class<?> scope, String name, Locale locale, String style,
                String variation, IResource resource) {
            super(scope, name, locale, style, variation);
            this.resource = resource;
        }

        private static final long serialVersionUID = 1L;

        @Override
        public IResource getResource() {
            return resource;
        }

    }

    /**
     * Tries to load static image at the given path and throws an exception if the image cannot be
     * located.
     * 
     * @param path
     *            The path to the image
     * @throws WicketRuntimeException
     *             Thrown if the image cannot be located
     */
    private void loadStaticImage(final String path) {
        MarkupContainer parent = component.findParentWithAssociatedMarkup();
        if (parent instanceof Border) {
            parent = parent.getParent();
        }
        final Class<?> scope = parent.getClass();
        resourceReference = new PackageResourceReference(scope, path, locale, style, variation);
        bind();
    }

    /**
     * Generates an image resource based on the attribute values on tag
     * 
     * @param value
     *            The value to parse
     */
    private void newImage(final CharSequence value) {
        // Parse value
        final ImageValueParser valueParser = new ImageValueParser(value);

        // Does value match parser?
        if (valueParser.matches()) {
            final String imageReferenceName = valueParser.getImageReferenceName();
            final String specification = Strings.replaceHtmlEscapeNumber(valueParser.getSpecification());
            final String factoryName = valueParser.getFactoryName();
            final Application application = component.getApplication();

            // Do we have a reference?
            if (!Strings.isEmpty(imageReferenceName)) {
                // Is resource already available via the application?
                if (application.getResourceReferenceRegistry().getResourceReference(Application.class,
                        imageReferenceName, locale, style, variation, true, false) == null) {
                    // Resource not available yet, so create it with factory and
                    // share via Application
                    final IResource imageResource = getResourceFactory(application, factoryName)
                            .newResource(specification, locale, style, variation);

                    ResourceReference ref = new SimpleStaticResourceReference(Application.class, imageReferenceName,
                            locale, style, variation, imageResource);

                    application.getResourceReferenceRegistry().registerResourceReference(ref);
                }

                // Create resource reference
                resourceReference = new PackageResourceReference(Application.class, imageReferenceName, locale,
                        style, variation);
            } else {
                resource = getResourceFactory(application, factoryName).newResource(specification, locale, style,
                        variation);
            }
        } else {
            throw new WicketRuntimeException("Could not generate image for value attribute '" + value
                    + "'.  Was expecting a value attribute of the form \"[resourceFactoryName]:[resourceReferenceName]?:[factorySpecification]\".");
        }
    }

    /**
     * return the resource
     * 
     * @return resource or <code>null</code> if there is none
     */
    public final IResource getResource() {
        return resource;
    }

    /**
     * return the resource
     * 
     * @return resource or <code>null</code> if there is none
     */
    public final ResourceReference getResourceReference() {
        return resourceReference;
    }
}