You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2006/12/01 18:49:35 UTC

svn commit: r481321 [1/2] - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/ main/java/org/apache/tapestry/annotations/ main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/internal/util/ main/jav...

Author: hlship
Date: Fri Dec  1 09:49:32 2006
New Revision: 481321

URL: http://svn.apache.org/viewvc?view=rev&rev=481321
Log:
Add basic Asset support (for context assets).

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Asset.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetInjectWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetSourceImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextAssetFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextResource.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAnonymousWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectNamedWorker.java
      - copied, changed from r480115, tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetSource.java
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/assets.apt
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/images/
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/images/tapestry_banner.gif   (with props)
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Img.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/AssetDemo.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetInjectWorkerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetSourceImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextAssetFactoryTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextResourceTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectAnonymousWorkerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectNamedWorkerTest.java
      - copied, changed from r480115, tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectWorkerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/pages/FindFieldClass.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/AssetDemo.html
Removed:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectionKey.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectWorkerTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformation.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/infrastructure.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/inject.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/localization.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Asset.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Asset.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Asset.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Asset.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,37 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry;
+
+import org.apache.tapestry.ioc.Resource;
+
+/**
+ * An Asset is any kind of resource that can be exposed to the client web browser. Although quite
+ * often an Asset is a resource in a web application's context folder, within Tapestry, Assets may
+ * also be resources on the classpath (i.e., packaged inside JARs).
+ * <p>
+ * An Asset's toString() will return the URL for the resource (the same value as
+ * {@link #toClientURL()}).
+ */
+public interface Asset
+{
+    /**
+     * Returns a URL that can be passed, unchanged, to the client in order for it to access the
+     * resource.
+     */
+    String toClientURL();
+
+    /** Returns the underlying Resource for the Asset. */
+    Resource getResource();
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java Fri Dec  1 09:49:32 2006
@@ -21,8 +21,24 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.ComponentResources;
+
 /**
- * Allows injection of various objects into a component class.
+ * Allows injection of various objects into a component class. In certain cases, the type of the
+ * field guides the interpretation of the value. For {@link Asset}, the value is the path, relative
+ * to the component's class file, to the resource from which the Asset is obtained.
+ * <p>
+ * In most other cases, the value is an object reference. A common example:
+ * 
+ * <pre>
+ * @Inject(&quot;infrastructure:request&quot;)
+ * private WebRequest _request;
+ * </pre>
+ * 
+ * <p>
+ * Finally, for certain specific types (such as {@link ComponentResources}), the value is omitted
+ * entirely, and an object appropriate to the component instance is injected.
  * 
  * @see org.apache.tapestry.services.InjectionProvider
  */

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetInjectWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetInjectWorker.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetInjectWorker.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetInjectWorker.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,116 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import java.util.List;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.ioc.util.BodyBuilder;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.AssetSource;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+import org.apache.tapestry.services.TransformConstants;
+
+/**
+ * Supports injection for fields of type {@link Asset}. This worker must be scheduled
+ * <em>before</em> {@link InjectNamedWorker}.
+ * 
+ * @see AssetSource
+ */
+public class AssetInjectWorker implements ComponentClassTransformWorker
+{
+    static final String ASSET_TYPE_NAME = Asset.class.getName();
+
+    private final AssetSource _assetSource;
+
+    public AssetInjectWorker(final AssetSource assetSource)
+    {
+        _assetSource = assetSource;
+    }
+
+    public void transform(ClassTransformation transformation, MutableComponentModel model)
+    {
+        List<String> names = transformation.findFieldsOfType(ASSET_TYPE_NAME);
+
+        String assetSourceFieldName = null;
+        String baseResourceFieldName = null;
+        String resourcesFieldName = null;
+
+        BodyBuilder builder = new BodyBuilder();
+        builder.begin();
+
+        for (String name : names)
+        {
+
+            Inject annotation = transformation.getFieldAnnotation(name, Inject.class);
+
+            // If the field has no annotation, or no value for its annotation, that's probably
+            // a programmer error, but we'll let the later Inject-related workers complain about it.
+            
+            if (annotation == null)
+                continue;
+
+            String path = annotation.value();
+
+            if (path.equals(""))
+                continue;
+
+            // This is tricky because we support sublcasses; if we ask the component at runtime for
+            // its Resource (via the ComponentModel), we get the subclass, which will break the link
+            // to any Asset resources from super-classes.
+
+            if (assetSourceFieldName == null)
+            {
+                assetSourceFieldName = transformation.addInjectedField(
+                        AssetSource.class,
+                        "assetSource",
+                        _assetSource);
+                baseResourceFieldName = transformation.addInjectedField(
+                        Resource.class,
+                        "baseResource",
+                        model.getBaseResource());
+                resourcesFieldName = transformation.getResourcesFieldName();
+            }
+
+            builder.addln(
+                    "%s = %s.findAsset(%s, \"%s\", %s.getLocale());",
+                    name,
+                    assetSourceFieldName,
+                    baseResourceFieldName,
+                    path,
+                    resourcesFieldName);
+
+            transformation.makeReadOnly(name);
+
+            // Keep InjectNamedWorker from doing anything to it.
+            
+            transformation.claimField(name, annotation);
+        }
+
+        // If no matches
+
+        if (assetSourceFieldName == null)
+            return;
+
+        builder.end();
+
+        transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE, builder
+                .toString());
+
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetSourceImpl.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetSourceImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/AssetSourceImpl.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,114 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newThreadSafeMap;
+
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.ioc.util.StrategyRegistry;
+import org.apache.tapestry.services.AssetFactory;
+import org.apache.tapestry.services.AssetSource;
+
+/**
+ * Implementation of {@link AssetSource} available as service:tapestry.AssetSource or
+ * infrastructure:assetSource.
+ */
+public class AssetSourceImpl implements AssetSource
+{
+    private final StrategyRegistry<AssetFactory> _registry;
+
+    private final Map<String, Resource> _prefixToRootResource = newMap();
+
+    private final Map<Resource, Asset> _cache = newThreadSafeMap();
+
+    public AssetSourceImpl(Map<String, AssetFactory> configuration)
+    {
+        Map<Class, AssetFactory> byResourceClass = newMap();
+
+        for (Map.Entry<String, AssetFactory> e : configuration.entrySet())
+        {
+            String prefix = e.getKey();
+            AssetFactory factory = e.getValue();
+
+            Resource rootResource = factory.getRootResource();
+
+            byResourceClass.put(rootResource.getClass(), factory);
+
+            _prefixToRootResource.put(prefix, rootResource);
+        }
+
+        _registry = StrategyRegistry.newInstance(AssetFactory.class, byResourceClass);
+    }
+
+    public Asset findAsset(Resource baseResource, String path, Locale locale)
+    {
+        int colonx = path.indexOf(':');
+
+        if (colonx < 0)
+            return findRelativeAsset(baseResource, path, locale);
+
+        String prefix = path.substring(0, colonx);
+
+        Resource rootResource = _prefixToRootResource.get(prefix);
+
+        if (rootResource == null)
+            throw new IllegalArgumentException(ServicesMessages.unknownAssetPrefix(path));
+
+        return findRelativeAsset(rootResource, path.substring(colonx + 1), locale);
+    }
+
+    private Asset findRelativeAsset(Resource baseResource, String path, Locale locale)
+    {
+        Resource unlocalized = baseResource.forFile(path);
+        Resource localized = unlocalized.forLocale(locale);
+
+        if (localized == null)
+            throw new RuntimeException(ServicesMessages.assetDoesNotExist(unlocalized));
+
+        return getAssetForResource(localized);
+    }
+
+    private Asset getAssetForResource(Resource resource)
+    {
+        Asset result = _cache.get(resource);
+
+        if (result == null)
+        {
+            result = createAssetFromResource(resource);
+            _cache.put(resource, result);
+        }
+
+        return result;
+    }
+
+    private Asset createAssetFromResource(Resource resource)
+    {
+        // The class of the resource is derived from the class of the base resource.
+        // So we can then use the class of the resource as a key to locate the correct asset
+        // factory.
+
+        Class resourceClass = resource.getClass();
+
+        AssetFactory factory = _registry.get(resourceClass);
+
+        return factory.createAsset(resource);
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextAssetFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextAssetFactory.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextAssetFactory.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextAssetFactory.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,74 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.services.AssetFactory;
+import org.apache.tapestry.services.WebContext;
+import org.apache.tapestry.services.WebRequest;
+
+/**
+ * Implementation of {@link AssetFactory} for assets that are part of the web application context.
+ * 
+ * @see ContextResource
+ */
+public class ContextAssetFactory implements AssetFactory
+{
+    private final WebRequest _request;
+
+    private final WebContext _context;
+
+    public ContextAssetFactory(WebRequest request, WebContext context)
+    {
+        _request = request;
+        _context = context;
+    }
+
+    public Asset createAsset(final Resource resource)
+    {
+        final String contextPath = _request.getContextPath() + "/" + resource.getPath();
+
+        return new Asset()
+        {
+            public Resource getResource()
+            {
+                return resource;
+            }
+
+            public String toClientURL()
+            {
+                return contextPath;
+            }
+
+            /**
+             * Returns the client URL, which is essiential to allow informal parameters of type
+             * Asset to generate a proper value.
+             */
+            @Override
+            public String toString()
+            {
+                return toClientURL();
+            }
+        };
+    }
+
+    /** Returns the root {@link ContextResource}. */
+    public Resource getRootResource()
+    {
+        return new ContextResource(_context, "/");
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextResource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextResource.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextResource.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ContextResource.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,85 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
+
+import java.net.URL;
+
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.ioc.internal.util.AbstractResource;
+import org.apache.tapestry.services.WebContext;
+
+/**
+ * A resource stored with in the web application context.
+ */
+public class ContextResource extends AbstractResource
+{
+    private static final int PRIME = 37;
+
+    private WebContext _context;
+
+    public ContextResource(WebContext context, String path)
+    {
+        super(path);
+
+        notNull(context, "context");
+
+        _context = context;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("context:%s", getPath());
+    }
+
+    @Override
+    protected Resource newResource(String path)
+    {
+        return new ContextResource(_context, path);
+    }
+
+    public URL toURL()
+    {
+        // This is so easy to screw up; ClassLoader.getResource() doesn't want a leading slash,
+        // and HttpServletContext.getResource() does. This is what I mean when I say that
+        // a framework is an accumulation of the combined experience of many users and developers.
+
+        return _context.getResource("/" + getPath());
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return PRIME * _context.hashCode() + getPath().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+
+        final ContextResource other = (ContextResource) obj;
+
+        return _context == other._context && getPath().equals(other.getPath());
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAnonymousWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAnonymousWorker.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAnonymousWorker.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAnonymousWorker.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,71 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.ioc.ServiceLocator;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+import org.apache.tapestry.services.InjectionProvider;
+
+/**
+ * Performs anonymous injection, for the cases where a field is labled with the {@link Inject}
+ * annotation, but no specific value was provided. This worker must be scheduled <em>after</em>
+ * {@link InjectNamedWorker}.
+ * <p>
+ * The implementation of this worker mostly delegates to a chain of command of
+ * {@link InjectionProvider}s.
+ */
+public class InjectAnonymousWorker implements ComponentClassTransformWorker
+{
+
+    private final ServiceLocator _locator;
+
+    // Really, a chain of command
+
+    private final InjectionProvider _injectionProvider;
+
+    public InjectAnonymousWorker(final ServiceLocator locator,
+            final InjectionProvider injectionProvider)
+    {
+        _locator = locator;
+        _injectionProvider = injectionProvider;
+    }
+
+    public void transform(ClassTransformation transformation, MutableComponentModel model)
+    {
+        for (String fieldName : transformation.findFieldsWithAnnotation(Inject.class))
+        {
+            Inject annotation = transformation.getFieldAnnotation(fieldName, Inject.class);
+
+            String fieldType = transformation.getFieldType(fieldName);
+
+            boolean result = _injectionProvider.provideInjection(
+                    fieldName,
+                    fieldType,
+                    _locator,
+                    transformation,
+                    model);
+
+            if (!result)
+                throw new RuntimeException(ServicesMessages.noInjectionFound(transformation
+                        .getClassName(), fieldName, fieldType));
+
+            transformation.claimField(fieldName, annotation);
+        }
+    }
+
+}

Copied: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectNamedWorker.java (from r480115, tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectWorker.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectNamedWorker.java?view=diff&rev=481321&p1=tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectWorker.java&r1=480115&p2=tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectNamedWorker.java&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectWorker.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectNamedWorker.java Fri Dec  1 09:49:32 2006
@@ -21,27 +21,26 @@
 import org.apache.tapestry.model.MutableComponentModel;
 import org.apache.tapestry.services.ClassTransformation;
 import org.apache.tapestry.services.ComponentClassTransformWorker;
-import org.apache.tapestry.services.InjectionProvider;
 
 /**
- * Worker for the {@link org.apache.tapestry.annotations.Inject} annotation.
+ * Worker for the {@link org.apache.tapestry.annotations.Inject} annotation, but only works with
+ * annotations where the annotation has a value (a name). In some cases, there are specific types
+ * where the meaning of the value for the Inject annoation is different, works for those cases must
+ * be scheduled <em>before</em> this worker, and must be sure to
+ * {@link ClassTransformation#claimField(String, Object) claim the field}.
+ * 
+ * @see ObjectProvider
  */
-public class InjectWorker implements ComponentClassTransformWorker
+public class InjectNamedWorker implements ComponentClassTransformWorker
 {
     private final ObjectProvider _objectProvider;
 
     private final ServiceLocator _locator;
 
-    // Really, a chain of command
-
-    private final InjectionProvider _injectionProvider;
-
-    public InjectWorker(ObjectProvider objectProvider, ServiceLocator locator,
-            InjectionProvider injectionProvider)
+    public InjectNamedWorker(ObjectProvider objectProvider, ServiceLocator locator)
     {
         _objectProvider = objectProvider;
         _locator = locator;
-        _injectionProvider = injectionProvider;
     }
 
     public void transform(ClassTransformation transformation, MutableComponentModel model)
@@ -52,10 +51,12 @@
 
             String value = annotation.value();
 
+            // A later worker will tackle this.
+
             if (InternalUtils.isBlank(value))
-                injectAnnonymous(fieldName, transformation, model);
-            else
-                injectNamed(fieldName, value, transformation, model);
+                continue;
+
+            injectNamed(fieldName, value, transformation, model);
 
             transformation.claimField(fieldName, annotation);
         }
@@ -75,20 +76,4 @@
         transformation.injectField(fieldName, inject);
     }
 
-    private void injectAnnonymous(String fieldName, ClassTransformation transformation,
-            MutableComponentModel model)
-    {
-        String fieldType = transformation.getFieldType(fieldName);
-
-        boolean result = _injectionProvider.provideInjection(
-                fieldName,
-                fieldType,
-                _locator,
-                transformation,
-                model);
-
-        if (!result)
-            throw new RuntimeException(ServicesMessages.noInjectionFound(transformation
-                    .getClassName(), fieldName, fieldType));
-    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformation.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformation.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformation.java Fri Dec  1 09:49:32 2006
@@ -12,58 +12,57 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.services;
-
-import java.util.List;
-
+package org.apache.tapestry.internal.services;
+
+import java.util.List;
+
+import org.apache.tapestry.internal.util.MultiKey;
 import org.apache.tapestry.ioc.internal.util.IdAllocator;
-import org.apache.tapestry.services.ClassTransformation;
-import org.apache.tapestry.services.ComponentClassTransformWorker;
-
-/**
- * Extends {@link org.apache.tapestry.services.ClassTransformation} with additional methods that may
- * only be used internally by Tapestry.
- * 
- * 
- */
-public interface InternalClassTransformation extends ClassTransformation
-{
-    /**
-     * Returns the name of the protected field that is injected with the
-     * {@link org.apache.tapestry.internal.InternalComponentResources}.
-     */
-    String getResourcesFieldName();
-
-    /**
-     * Invoked after all {@link ComponentClassTransformWorker}s have had their chance to work over
-     * the class. This performs any final operations for the class transformation, which includes
-     * coming up with the final constructor method for the class.
-     */
-    void finish();
-
-    /**
-     * Called (after {@link #finish()}) to construct an instantiator for the component.
-     * 
-     * @param componentClass
-     *            the class to be instantiated
-     * @return the component's instantiator
-     */
-    Instantiator createInstantiator(Class componentClass);
-
-    /**
-     * Returns a copy of the transformation's IdAllocator. Used when creating a child class
-     * transformation. May only be invoked on a frozen transformation.
-     */
-    IdAllocator getIdAllocator();
-
-    /**
-     * Returns a copy of the list of constructor arguments for this class.
-     */
-    List<ConstructorArg> getConstructorArgs();
-
-    /**
-     * Searchs for an existing injection of an object, returning the name of the protected field
-     * into which the value was injected.
-     */
-    String searchForPreviousInjection(InjectionKey key);
-}
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+
+/**
+ * Extends {@link org.apache.tapestry.services.ClassTransformation} with additional methods that may
+ * only be used internally by Tapestry.
+ */
+public interface InternalClassTransformation extends ClassTransformation
+{
+    /**
+     * Returns the name of the protected field that is injected with the
+     * {@link org.apache.tapestry.internal.InternalComponentResources}.
+     */
+    String getResourcesFieldName();
+
+    /**
+     * Invoked after all {@link ComponentClassTransformWorker}s have had their chance to work over
+     * the class. This performs any final operations for the class transformation, which includes
+     * coming up with the final constructor method for the class.
+     */
+    void finish();
+
+    /**
+     * Called (after {@link #finish()}) to construct an instantiator for the component.
+     * 
+     * @param componentClass
+     *            the class to be instantiated
+     * @return the component's instantiator
+     */
+    Instantiator createInstantiator(Class componentClass);
+
+    /**
+     * Returns a copy of the transformation's IdAllocator. Used when creating a child class
+     * transformation. May only be invoked on a frozen transformation.
+     */
+    IdAllocator getIdAllocator();
+
+    /**
+     * Returns a copy of the list of constructor arguments for this class.
+     */
+    List<ConstructorArg> getConstructorArgs();
+
+    /**
+     * Searchs for an existing injection of an object, returning the name of the protected field
+     * into which the value was injected.
+     */
+    String searchForPreviousInjection(MultiKey key);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java Fri Dec  1 09:49:32 2006
@@ -46,6 +46,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.internal.InternalComponentResources;
+import org.apache.tapestry.internal.util.MultiKey;
 import org.apache.tapestry.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry.ioc.internal.util.IdAllocator;
 import org.apache.tapestry.ioc.internal.util.InternalUtils;
@@ -73,7 +74,7 @@
     private final IdAllocator _idAllocator;
 
     /** Map, keyed on InjectKey, of field name. */
-    private final Map<InjectionKey, String> _injectionCache = newMap();
+    private final Map<MultiKey, String> _injectionCache = newMap();
 
     /** Map from a field to the annotation objects for that field. */
     private Map<String, List<Annotation>> _fieldAnnotations = newMap();
@@ -520,6 +521,7 @@
     public void claimField(String fieldName, Object tag)
     {
         notBlank(fieldName, "fieldName");
+        notNull(tag, "tag");
 
         failIfFrozen();
 
@@ -791,6 +793,9 @@
 
             String fieldName = field.getName();
 
+            if (_claimedFields.containsKey(fieldName))
+                continue;
+
             List<Annotation> annotations = findFieldAnnotations(fieldName);
 
             // Check to see if any of the annotations are the right type, if
@@ -811,6 +816,40 @@
         return result;
     }
 
+    public List<String> findFieldsOfType(String type)
+    {
+        failIfFrozen();
+
+        List<String> result = newList();
+
+        for (CtField field : _ctClass.getDeclaredFields())
+        {
+            if (!isInstanceField(field))
+                continue;
+
+            String fieldName = field.getName();
+
+            try
+            {
+                if (!field.getType().getName().equals(type))
+                    continue;
+            }
+            catch (NotFoundException ex)
+            {
+                throw new RuntimeException(ex);
+            }
+
+            if (_claimedFields.containsKey(fieldName))
+                continue;
+
+            result.add(fieldName);
+        }
+
+        Collections.sort(result);
+
+        return result;
+    }
+
     public List<MethodSignature> findMethodsWithAnnotation(
             Class<? extends Annotation> annotationClass)
     {
@@ -963,7 +1002,7 @@
 
         failIfFrozen();
 
-        InjectionKey key = new InjectionKey(type, value);
+        MultiKey key = new MultiKey(type, value);
 
         String fieldName = searchForPreviousInjection(key);
 
@@ -1009,7 +1048,7 @@
         return fieldName;
     }
 
-    public String searchForPreviousInjection(InjectionKey key)
+    public String searchForPreviousInjection(MultiKey key)
     {
         String result = _injectionCache.get(key);
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java Fri Dec  1 09:49:32 2006
@@ -250,4 +250,14 @@
     {
         return MESSAGES.format("failure-reading-component-message", url, cause);
     }
+
+    static String unknownAssetPrefix(String path)
+    {
+        return MESSAGES.format("unknown-asset-prefix", path);
+    }
+
+    static String assetDoesNotExist(Resource resource)
+    {
+        return MESSAGES.format("asset-does-not-exist", resource);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java Fri Dec  1 09:49:32 2006
@@ -16,7 +16,10 @@
 
 import java.util.Arrays;
 
-/** A general purpose key for multiple values. */
+/**
+ * Combines multiple values to form a single composite key. MultiKey can often be used as an
+ * alternative to nested maps.
+ */
 public final class MultiKey
 {
     private static final int PRIME = 31;
@@ -27,7 +30,8 @@
 
     /**
      * Creates a new instance from the provided values. It is assumed that the values provided are
-     * good map keys themselves (i.e., immutable, and implement equals() and hashCode() ).
+     * good map keys themselves -- immutable, with proper implementations of equals() and
+     * hashCode().
      * 
      * @param values
      */

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetFactory.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetFactory.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetFactory.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,32 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.services;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.ioc.Resource;
+
+/**
+ * Used by {@link AssetSource} to create new {@link Asset}s as needed.
+ */
+public interface AssetFactory
+{
+    /**
+     * Returns the Resource reprsenting the root folder of the domain this factory is responsible
+     * for.
+     */
+    Resource getRootResource();
+
+    Asset createAsset(Resource resource);
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetSource.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetSource.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/AssetSource.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,43 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.services;
+
+import java.util.Locale;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.ioc.Resource;
+
+/**
+ * Used to find or create an {@link Asset} with a given path.
+ */
+public interface AssetSource
+{
+    /**
+     * Finds the asset. The path may either be a simple file name or a relative path (relative to
+     * the base resource) <em>or</em> it may have a prefix, such as "context:" or "classpath:", in
+     * which case it is treated as a complete path within the indicated domain. The resulting
+     * Resource is then localized (to the provided Locale) and returned as an Asset.
+     * <p>
+     * The AssetSource caches its results, so a single Asset instance may be shared among many
+     * different components.
+     * 
+     * @param baseResource
+     *            base resource for computing relative paths
+     * @param path
+     * @param locale
+     * @return
+     */
+    Asset findAsset(Resource baseResource, String path, Locale locale);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java Fri Dec  1 09:49:32 2006
@@ -40,9 +40,8 @@
  */
 public interface ClassTransformation
 {
-
     /**
-     * Returns the name of the class being transformed.
+     * Returns the fully qualified class name of the class being transformed.
      */
     String getClassName();
 
@@ -50,8 +49,6 @@
      * Returns the name of a new member (field or method). Ensures that the resulting name does not
      * conflict with any existing member (declared by the underlying class, or inherited from a base
      * class).
-     * <p>
-     * TODO: This method may be removed (see {@link #addField(int, String, String)})
      * 
      * @param suggested
      *            the suggested value for the member
@@ -61,9 +58,17 @@
 
     /**
      * Generates a list of the names of declared instance fields that have the indicated annotation.
-     * Only the names of private instance fields are returned.
+     * Only the names of private instance fields are returned. Any
+     * {@link #claimField(String, Object) claimed} fields are excluded.
      */
     List<String> findFieldsWithAnnotation(Class<? extends Annotation> annotationClass);
+
+    /**
+     * Generates a list of the names of declared instance fields that exactly match the specified
+     * type. Only the names of private instance fields are returned. Any
+     * {@link #claimField(String, Object) claimed} fields are excluded.
+     */
+    List<String> findFieldsOfType(String type);
 
     /**
      * Finds all methods defined in the class that are marked with the provided annotation.

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java Fri Dec  1 09:49:32 2006
@@ -44,6 +44,8 @@
 import org.apache.tapestry.internal.bindings.LiteralBindingFactory;
 import org.apache.tapestry.internal.bindings.MessageBindingFactory;
 import org.apache.tapestry.internal.services.ApplicationGlobalsImpl;
+import org.apache.tapestry.internal.services.AssetInjectWorker;
+import org.apache.tapestry.internal.services.AssetSourceImpl;
 import org.apache.tapestry.internal.services.BindingSourceImpl;
 import org.apache.tapestry.internal.services.CommonResourcesInjectionProvider;
 import org.apache.tapestry.internal.services.ComponentClassFactoryImpl;
@@ -56,14 +58,16 @@
 import org.apache.tapestry.internal.services.ComponentResourcesInjectionProvider;
 import org.apache.tapestry.internal.services.ComponentSourceImpl;
 import org.apache.tapestry.internal.services.ComponentWorker;
+import org.apache.tapestry.internal.services.ContextAssetFactory;
 import org.apache.tapestry.internal.services.DefaultInjectionProvider;
 import org.apache.tapestry.internal.services.EnvironmentImpl;
 import org.apache.tapestry.internal.services.EnvironmentalWorker;
 import org.apache.tapestry.internal.services.HeartbeatImpl;
 import org.apache.tapestry.internal.services.InfrastructureImpl;
 import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
+import org.apache.tapestry.internal.services.InjectAnonymousWorker;
 import org.apache.tapestry.internal.services.InjectPageWorker;
-import org.apache.tapestry.internal.services.InjectWorker;
+import org.apache.tapestry.internal.services.InjectNamedWorker;
 import org.apache.tapestry.internal.services.InternalModule;
 import org.apache.tapestry.internal.services.LinkFactory;
 import org.apache.tapestry.internal.services.MarkupWriterImpl;
@@ -403,6 +407,7 @@
         add(configuration, locator, ComponentSource.class);
         add(configuration, locator, BindingSource.class);
         add(configuration, locator, ComponentMessagesSource.class);
+        add(configuration, locator, AssetSource.class);
 
         configuration.add(new InfrastructureContribution("request", request));
         configuration.add(new InfrastructureContribution("response", response));
@@ -526,6 +531,8 @@
      * <li>Mixin -- adds a mixin as part of a component's implementation</li>
      * <li>Environment -- allows fields to contain values extracted from the {@link Environment}
      * service</li>
+     * <li>InjectNamed -- used with the {@link Inject} annotation, when a value is supplied</li>
+     * <li>InjectAnnonymous -- used with the {@link Inject} annotation, when no value is supplied</li>
      * <li>InjectPage -- adds code to allow access to other pages via the {@link InjectPage} field
      * annotation</li>
      * <li>SupportsInformalParameters -- checks for the annotation</li>
@@ -541,13 +548,20 @@
             InjectionProvider injectionProvider, @InjectService("Environment")
             Environment environment, @InjectService("tapestry.ComponentClassResolver")
             ComponentClassResolver resolver, @InjectService("tapestry.internal.RequestPageCache")
-            RequestPageCache requestPageCache)
+            RequestPageCache requestPageCache, @Inject("infrastructure:assetSource")
+            AssetSource assetSource)
     {
         // TODO: Proper scheduling of all of this. Since a given field or method should
         // only have a single annotation, the order doesn't matter so much, as long as
         // UnclaimedField is last.
 
-        configuration.add("Inject", new InjectWorker(objectProvider, locator, injectionProvider));
+        configuration.add("InjectNamed", new InjectNamedWorker(objectProvider, locator));
+        configuration.add(
+                "InjectAnonymous",
+                new InjectAnonymousWorker(locator, injectionProvider),
+                "after:InjectNamed");
+        configuration.add("AssetInject", new AssetInjectWorker(assetSource), "before:InjectNamed");
+
         configuration.add("Parameter", new ParameterWorker());
         configuration.add("Component", new ComponentWorker(resolver));
         configuration.add("Environment", new EnvironmentalWorker(environment));
@@ -744,5 +758,18 @@
         updateListenerHub.addUpdateListener(service);
 
         return service;
+    }
+
+    public static AssetSource buildAssetSource(Map<String, AssetFactory> configuration)
+    {
+        return new AssetSourceImpl(configuration);
+    }
+
+    public void contributeAssetSource(MappedConfiguration<String, AssetFactory> configuration)
+    {
+        AssetFactory contextAssetFactory = new ContextAssetFactory(_request, _applicationGlobals
+                .getWebContext());
+
+        configuration.add("context", contextAssetFactory);
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java Fri Dec  1 09:49:32 2006
@@ -34,8 +34,10 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
+import org.apache.tapestry.Asset;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.annotations.Inject;
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.ioc.Location;
 import org.apache.tapestry.ioc.Resource;
@@ -47,12 +49,14 @@
 import org.apache.tapestry.model.MutableComponentModel;
 import org.apache.tapestry.model.ParameterModel;
 import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.services.AssetFactory;
 import org.apache.tapestry.services.Binding;
 import org.apache.tapestry.services.BindingFactory;
 import org.apache.tapestry.services.ClassTransformation;
 import org.apache.tapestry.services.ComponentClassResolver;
 import org.apache.tapestry.services.InjectionProvider;
 import org.apache.tapestry.services.MethodSignature;
+import org.apache.tapestry.services.WebContext;
 import org.apache.tapestry.services.WebRequest;
 import org.apache.tapestry.services.WebRequestHandler;
 import org.apache.tapestry.services.WebResponse;
@@ -412,5 +416,45 @@
     protected final void train_getLocale(ThreadLocale threadLocale, Locale locale)
     {
         expect(threadLocale.getLocale()).andReturn(locale);
+    }
+
+    protected final Asset newAsset()
+    {
+        return newMock(Asset.class);
+    }
+
+    protected final void train_createAsset(AssetFactory factory, Resource resource, Asset asset)
+    {
+        expect(factory.createAsset(resource)).andReturn(asset);
+    }
+
+    protected final void train_getRootResource(AssetFactory factory, Resource rootResource)
+    {
+        expect(factory.getRootResource()).andReturn(rootResource);
+    }
+
+    protected final AssetFactory newAssetFactory()
+    {
+        return newMock(AssetFactory.class);
+    }
+
+    protected final WebContext newWebContext()
+    {
+        return newMock(WebContext.class);
+    }
+
+    protected final void train_getClassName(ClassTransformation transformation, String className)
+    {
+        expect(transformation.getClassName()).andReturn(className).atLeastOnce();
+    }
+
+    protected final void train_value(Inject annotation, String value)
+    {
+        expect(annotation.value()).andReturn(value).atLeastOnce();
+    }
+
+    protected final <T extends Annotation> void train_getMethodAnnotation(ClassTransformation ct, MethodSignature signature, Class<T> annotationClass, T annotation)
+    {
+        expect(ct.getMethodAnnotation(signature, annotationClass)).andReturn(annotation);
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties Fri Dec  1 09:49:32 2006
@@ -59,4 +59,6 @@
 parameter-name-must-be-unique=Parameter names are required to be unique.  Parameter '%s' already has the value '%s'.
 page-is-dirty=Page %s is dirty, and will be discarded (rather than returned to the page pool).
 component-instance-is-not-a-page=Method %s (for component %s) returned component %s, which is not a page component. The page containing the component will render the client response.
-failure-reading-component-messages=Unable to read component message catalog from %s: %s
\ No newline at end of file
+failure-reading-component-messages=Unable to read component message catalog from %s: %s
+unknown-asset-prefix=Unknown prefix for asset path '%s'.
+asset-does-not-exist=Unable to locate asset '%s' (the file does not exist).
\ No newline at end of file

Added: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/assets.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/assets.apt?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/assets.apt (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/assets.apt Fri Dec  1 09:49:32 2006
@@ -0,0 +1,59 @@
+ ----
+  Assets
+ ----
+ 
+Assets
+
+  Assets are any kind of file that may be downloaded to a client web browser in addition to the dynamically generated HTML.
+  
+  Assets are most often images, stylesheets, and JavaScript libraries.
+  
+  Normal assets are stored in the web application's context folder ... stored inside the web application WAR file in the ordinary way.
+  
+  Tapestry will also make files stored <on the classpath>, with your Java class files, visible to the web browser.  <<Note: not yet implemented.>>
+  
+  Assets are exposed to your code as instances of the {{{../apidocs/org/apache/tapestry/Asset.html}Asset}} interface.
+  
+Injecting Assets
+
+  Components learn about assets via injection.  The 
+  {{{inject.html}Inject}} annotation allows Assets to be injected into components as read-only properties.
+  
++----+
+  @Inject("context:images/tapestry_banner.gif")
+  private Asset _banner;
++----+
+
+  Assets are located within <domains>; these domains are identified by the prefix on the Inject annotation's value.
+  
+  If the prefix is omitted, the value will be interpreted as a path relative to the Java class file itself, within
+  the "classpath:" domain. This is often used when creating component libraries, where the assets used by the components
+  are packaged in the JAR with the components themselves.
+  
+Relative Assets
+
+  You can use relative paths with domains (if you omit the prefix):
+
++----+
+  @Inject("../edit.png")
+  private Asset _icon;
++----+  
+
+  Since you must omit the prefix, this only makes sense for components packaged in a library for reuse.
+  
+Localization of Assets
+
+  Assets are {{{localization.html}localized}}; Tapestry will search for a variation of the file appropriate
+  to the effective locale for the request. In the previous example, a German user of the application may
+  see a file named <<<edit_de.png>>> (if such a file exists).
+  
+New Asset Domains
+
+  If you wish to create new domains for assets, for example to allow assets to be stored on the file system or in a database,
+  you may define a new
+  {{{../apidocs/org/apache/tapestry/services/AssetFactory.html}AssetFactory}}
+  and contribute it to the tapestry.AssetSource service configuration.  
+  
+  
+
+  
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/infrastructure.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/infrastructure.apt?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/infrastructure.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/infrastructure.apt Fri Dec  1 09:49:32 2006
@@ -46,6 +46,11 @@
   The following table identifies the properties that are available via the infrastructure provider
   by default:
   
+  [assetSource]
+    {{{../apidocs/org/apache/tapestry/services/AssetSource.html}AssetSource}} (tapestry.AssetSource)
+    
+    Obtains assets from public and internal resources.
+  
   [bindingSource]
     {{{../apidocs/org/apache/tapestry/services/BindingSource.html}BindingSource}} (tapestry.BindingSource)
     

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/inject.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/inject.apt?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/inject.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/inject.apt Fri Dec  1 09:49:32 2006
@@ -28,6 +28,11 @@
   at runtime (which is very important in terms of being able to test your components).
   Attempting to update an injected field will result in a runtime exception.
   
+* Asset Injection
+
+  When the field type is
+  {{{../apidocs/org/apache/tapestry/Asset.html}Asset}}, the value of the Inject annoation is treated specially, as a path
+  (relative to the component) to an {{{assets.html}asset}}. 
   
 * Named Injection
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/localization.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/localization.apt?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/localization.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/localization.apt Fri Dec  1 09:49:32 2006
@@ -123,6 +123,11 @@
 Reloading
 
   If you change a property file in a message catalog, you'll see the change immediately, just as with component classes and component templates.
+  
+Asset Localization
+
+  When {{{inject.html}injecting assets}}, the injected asset will be localized as well.  A search for the closest match for the active locale
+  is made, and the final Asset will reflect that.
       
 Locale Selection
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml Fri Dec  1 09:49:32 2006
@@ -57,6 +57,7 @@
             <item name="Component Events" href="guide/event.html"/>
             <item name="Component Mixins" href="guide/mixins.html"/>
             <item name="Localization" href="guide/localization.html"/>
+            <item name="Assets" href="guide/assets.html"/>
             <item name="Type Coercion" href="guide/coercion.html"/>
             <item name="Component Rendering" href="guide/rendering.html"/>
             <item name="Persistent Data" href="guide/persist.html"/>
@@ -66,11 +67,7 @@
             <item name="Request Processing" href="guide/request.html"/>
             <item name="DOM" href="guide/dom.html"/>
             <item name="Class Reloading" href="guide/reload.html"/>
-        </menu>
-        
-        <menu name="Other">
-            <item name="Dev Wiki" href="tap5devwiki.html#MasterIndex"/>
-        </menu>   
+        </menu> 
                         
         <menu ref="reports"/>
         

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/images/tapestry_banner.gif
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/images/tapestry_banner.gif?view=auto&rev=481321
==============================================================================
Binary file - no diff available.

Propchange: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/images/tapestry_banner.gif
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html Fri Dec  1 09:49:32 2006
@@ -48,6 +48,9 @@
             <li>
                 <a href="Localization.html">Localization</a> -- accessing localized messages from the component catalog
             </li>
+            <li>
+                <a href="AssetDemo.html">AssetDemo</a> -- declaring an using Assets
+            </li>
             </ul>
         </p>
     </body>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Fri Dec  1 09:49:32 2006
@@ -320,9 +320,33 @@
         assertTextPresent("Page locale: [en]");
     }
 
+    @Test
+    public void app1_assets()
+    {
+        _selenium.open(BASE_URL);
+        clickAndWait("link=AssetDemo");
+
+        assertText("//img[@id='img']/@src", "/images/tapestry_banner.gif");
+    }
+
     private void assertText(String locator, String expected)
     {
-        String actual = _selenium.getText(locator);
+        String actual = null;
+        
+        try
+        {
+            actual = _selenium.getText(locator);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.printf(
+                    "Error accessing %s: %s, in:\n\n%s\n\n",
+                    locator,
+                    ex.getMessage(),
+                    _selenium.getHtmlSource());
+
+            throw ex;
+        }
 
         if (actual.equals(expected))
             return;

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Img.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Img.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Img.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Img.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,60 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.integration.app1.components;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.annotations.AfterRender;
+import org.apache.tapestry.annotations.BeforeRenderBody;
+import org.apache.tapestry.annotations.BeginRender;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Environmental;
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.annotations.Parameter;
+import org.apache.tapestry.services.PageRenderSupport;
+
+@ComponentClass
+public class Img
+{
+    @Environmental
+    private PageRenderSupport _support;
+
+    @Inject
+    private ComponentResources _resources;
+
+    @Parameter(required = true)
+    private Asset _src;
+
+    @BeginRender
+    void begin(MarkupWriter writer)
+    {
+        String clientId = _support.allocateClientId(_resources.getId());
+
+        writer.element("img", "src", _src, "id", clientId);
+    }
+
+    @BeforeRenderBody
+    boolean beforeRenderBody()
+    {
+        return false;
+    }
+
+    @AfterRender
+    void after(MarkupWriter writer)
+    {
+        writer.end();
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/AssetDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/AssetDemo.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/AssetDemo.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/AssetDemo.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,31 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.integration.app1.pages;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Inject;
+
+@ComponentClass
+public class AssetDemo
+{
+    @Inject("context:images/tapestry_banner.gif")
+    private Asset _icon;
+
+    public Asset getIcon()
+    {
+        return _icon;
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetInjectWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetInjectWorkerTest.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetInjectWorkerTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetInjectWorkerTest.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,111 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import java.util.Arrays;
+
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.AssetSource;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.TransformConstants;
+import org.testng.annotations.Test;
+
+public class AssetInjectWorkerTest extends InternalBaseTestCase
+{
+    @Test
+    public void asset_field_without_annotation()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+
+        train_findFieldsOfType(ct, AssetInjectWorker.ASSET_TYPE_NAME, "_fred");
+
+        train_getFieldAnnotation(ct, "_fred", Inject.class, null);
+
+        replay();
+
+        new AssetInjectWorker(null).transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void asset_field_annotation_has_blank_value()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        Inject annotation = newMock(Inject.class);
+
+        train_findFieldsOfType(ct, AssetInjectWorker.ASSET_TYPE_NAME, "_fred");
+
+        train_getFieldAnnotation(ct, "_fred", Inject.class, annotation);
+
+        train_value(annotation, "");
+
+        replay();
+
+        new AssetInjectWorker(null).transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void asset_field_with_full_annotation()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        Inject annotation = newMock(Inject.class);
+        AssetSource source = newMock(AssetSource.class);
+        Resource r = newResource();
+
+        train_findFieldsOfType(ct, AssetInjectWorker.ASSET_TYPE_NAME, "_fred");
+
+        train_getFieldAnnotation(ct, "_fred", Inject.class, annotation);
+
+        train_value(annotation, "foo.gif");
+
+        train_addInjectedField(ct, AssetSource.class, "assetSource", source, "as");
+
+        train_getBaseResource(model, r);
+
+        train_addInjectedField(ct, Resource.class, "baseResource", r, "res");
+        train_getResourcesFieldName(ct, "resources");
+
+        ct.makeReadOnly("_fred");
+        ct.claimField("_fred", annotation);
+
+        train_extendMethod(
+                ct,
+                TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
+                "{",
+                "_fred = as.findAsset(res, \"foo.gif\", resources.getLocale());",
+                "}");
+
+        replay();
+
+        new AssetInjectWorker(source).transform(ct, model);
+
+        verify();
+    }
+
+    protected final void train_findFieldsOfType(ClassTransformation transformation,
+            String typeName, String... names)
+    {
+        expect(transformation.findFieldsOfType(typeName)).andReturn(Arrays.asList(names));
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetSourceImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetSourceImplTest.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetSourceImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/AssetSourceImplTest.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,149 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.ioc.internal.util.ClasspathResource;
+import org.apache.tapestry.services.AssetFactory;
+import org.apache.tapestry.services.AssetSource;
+import org.testng.annotations.Test;
+
+public class AssetSourceImplTest extends InternalBaseTestCase
+{
+    private final Resource _baseResource = new ClasspathResource(
+            "org/apache/tapestry/internal/services/SimpleComponent.class");
+
+    private final Resource _rootResource = new ClasspathResource("/");
+
+    @Test
+    public void relative_asset()
+    {
+        AssetFactory factory = newAssetFactory();
+        Asset asset = newAsset();
+
+        Resource expectedResource = _baseResource.forFile("SimpleComponent_en_GB.properties");
+
+        train_getRootResource(factory, _rootResource);
+
+        train_createAsset(factory, expectedResource, asset);
+
+        Map<String, AssetFactory> configuration = Collections.singletonMap("classpath", factory);
+
+        replay();
+
+        AssetSource source = new AssetSourceImpl(configuration);
+
+        // First try creates it:
+
+        assertSame(source.findAsset(_baseResource, "SimpleComponent.properties", Locale.UK), asset);
+
+        // Second try shows that it is cached
+
+        assertSame(source.findAsset(_baseResource, "SimpleComponent.properties", Locale.UK), asset);
+
+        verify();
+    }
+
+    @Test
+    public void absolute_asset_with_known_prefix()
+    {
+        AssetFactory factory = newAssetFactory();
+        Asset asset = newAsset();
+
+        Resource expectedResource = _rootResource
+                .forFile("org/apache/tapestry/internal/services/SimpleComponent_en_GB.properties");
+
+        train_getRootResource(factory, _rootResource);
+
+        train_createAsset(factory, expectedResource, asset);
+
+        Map<String, AssetFactory> configuration = Collections.singletonMap("classpath", factory);
+
+        replay();
+
+        AssetSource source = new AssetSourceImpl(configuration);
+
+        assertSame(source.findAsset(
+                _baseResource,
+                "classpath:org/apache/tapestry/internal/services/SimpleComponent.properties",
+                Locale.UK), asset);
+
+        // Check that a leading slash is not a problem:
+
+        assertSame(source.findAsset(
+                _baseResource,
+                "classpath:/org/apache/tapestry/internal/services/SimpleComponent.properties",
+                Locale.UK), asset);
+
+        verify();
+    }
+
+    @Test
+    public void unknown_asset_prefix()
+    {
+        Map<String, AssetFactory> configuration = Collections.emptyMap();
+
+        replay();
+
+        AssetSource source = new AssetSourceImpl(configuration);
+
+        try
+        {
+            source.findAsset(
+                    _baseResource,
+                    "classpath:org/apache/tapestry/internal/services/SimpleComponent.properties",
+                    Locale.UK);
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unknown prefix for asset path 'classpath:org/apache/tapestry/internal/services/SimpleComponent.properties'.");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void missing_resource()
+    {
+        Map<String, AssetFactory> configuration = Collections.emptyMap();
+
+        replay();
+
+        AssetSource source = new AssetSourceImpl(configuration);
+
+        try
+        {
+            source.findAsset(_baseResource, "DoesNotExist.properties", Locale.UK);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unable to locate asset 'classpath:org/apache/tapestry/internal/services/DoesNotExist.properties' (the file does not exist).");
+        }
+
+        verify();
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java?view=diff&rev=481321&r1=481320&r2=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java Fri Dec  1 09:49:32 2006
@@ -272,9 +272,4 @@
         verify();
     }
 
-    protected final void train_getClassName(ClassTransformation transformation, String className)
-    {
-        expect(transformation.getClassName()).andReturn(className);
-    }
-
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextAssetFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextAssetFactoryTest.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextAssetFactoryTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextAssetFactoryTest.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,64 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import org.apache.tapestry.Asset;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.services.AssetFactory;
+import org.apache.tapestry.services.WebContext;
+import org.apache.tapestry.services.WebRequest;
+import org.testng.annotations.Test;
+
+public class ContextAssetFactoryTest extends InternalBaseTestCase
+{
+    @Test
+    public void root_resource()
+    {
+        WebContext context = newWebContext();
+        WebRequest request = newWebRequest();
+
+        replay();
+
+        AssetFactory factory = new ContextAssetFactory(request, context);
+
+        assertEquals(factory.getRootResource().toString(), "context:/");
+
+        verify();
+    }
+
+    @Test
+    public void generated_asset()
+    {
+        WebContext context = newWebContext();
+        WebRequest request = newWebRequest();
+
+        train_getContextPath(request, "/context");
+
+        replay();
+
+        AssetFactory factory = new ContextAssetFactory(request, context);
+
+        Resource r = factory.getRootResource().forFile("foo/Bar.txt");
+
+        Asset asset = factory.createAsset(r);
+
+        assertSame(asset.getResource(), r);
+        assertEquals(asset.toClientURL(), "/context/foo/Bar.txt");
+        assertEquals(asset.toString(), asset.toClientURL());
+
+        verify();
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextResourceTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextResourceTest.java?view=auto&rev=481321
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextResourceTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ContextResourceTest.java Fri Dec  1 09:49:32 2006
@@ -0,0 +1,103 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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.apache.tapestry.internal.services;
+
+import java.net.URL;
+
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.services.WebContext;
+import org.testng.annotations.Test;
+
+public class ContextResourceTest extends InternalBaseTestCase
+{
+    @Test
+    public void get_url() throws Exception
+    {
+        URL url = getClass().getResource("ContextResourceTest.class");
+
+        WebContext context = newWebContext();
+
+        expect(context.getResource("/foo/Bar.txt")).andReturn(url);
+
+        replay();
+
+        Resource r = new ContextResource(context, "foo/Bar.txt");
+
+        assertSame(r.toURL(), url);
+
+        verify();
+    }
+
+    @Test
+    public void to_string()
+    {
+        WebContext context = newWebContext();
+
+        replay();
+
+        Resource r = new ContextResource(context, "foo/Bar.txt");
+
+        assertEquals(r.toString(), "context:foo/Bar.txt");
+
+        verify();
+    }
+
+    @Test
+    public void hash_code()
+    {
+        WebContext context1 = newWebContext();
+        WebContext context2 = newWebContext();
+
+        replay();
+
+        Resource r1 = new ContextResource(context1, "foo");
+        Resource r2 = new ContextResource(context1, "foo");
+        Resource r3 = new ContextResource(context2, "foo");
+        Resource r4 = new ContextResource(context1, "bar");
+
+        assertTrue(r1.hashCode() == r2.hashCode());
+        assertFalse(r1.hashCode() == r3.hashCode());
+        assertFalse(r1.hashCode() == r4.hashCode());
+
+        verify();
+    }
+
+    @Test
+    public void equals()
+    {
+        WebContext context1 = newWebContext();
+        WebContext context2 = newWebContext();
+        Resource r = newResource();
+
+        replay();
+
+        Resource r1 = new ContextResource(context1, "foo");
+        Resource r2 = new ContextResource(context1, "foo");
+        Resource r3 = new ContextResource(context2, "foo");
+        Resource r4 = new ContextResource(context1, "bar");
+
+        assertTrue(r1.equals(r2));
+        assertFalse(r1.equals(r3));
+        assertFalse(r1.equals(r4));
+
+        assertFalse(r1.equals(null));
+        assertTrue(r1.equals(r1));
+
+        assertFalse(r1.equals(r));
+
+        verify();
+    }
+}