[prev in list] [next in list] [prev in thread] [next in thread] 

List:       tapestry-dev
Subject:    svn commit: r480728 [1/2] - in /tapestry/tapestry5/tapestry-core/trunk/src:
From:       hlship () apache ! org
Date:       2006-11-29 22:02:47
Message-ID: 20061129220250.E35931A984A () eris ! apache ! org
[Download RAW message or body]

Author: hlship
Date: Wed Nov 29 14:02:40 2006
New Revision: 480728

URL: http://svn.apache.org/viewvc?view=rev&rev=480728
Log:
Start adding component messages support.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentMessagesSourceImpl.java
  tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MapMessages.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/ComponentMessagesSource.java
  tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentMessagesSourceImplTest.java
  tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/util/MultiKeyTest.java
  tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/SimpleComponent.properties
  tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/SimpleComponent_en_GB.properties
  tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/SubclassComponent.properties
  tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/SubclassComponent_en_GB.properties
 Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/event/InvalidationEventHub.java
  tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java
  tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentTemplateSourceImpl.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/services/TemplateParserImpl.java
  tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java
  tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.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/test/java/org/apache/tapestry/internal/model/MutableComponentModelImplTest.java


Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/event/InvalidationEventHub.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/internal/event/InvalidationEventHub.java?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/event/InvalidationEventHub.java \
                (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/event/InvalidationEventHub.java \
Wed Nov 29 14:02:40 2006 @@ -12,16 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.event;
-
-import org.apache.tapestry.events.InvalidationListener;
-
-/**
- * An object which manages a list of {@link \
                org.apache.tapestry.events.InvalidationListener}s.
- * 
- * 
- */
-public interface InvalidationEventHub
-{
-    void addInvalidationListener(InvalidationListener listener);
-}
+package org.apache.tapestry.internal.event;
+
+import org.apache.tapestry.events.InvalidationListener;
+
+/**
+ * An object which manages a list of {@link \
org.apache.tapestry.events.InvalidationListener}s. + */
+
+public interface InvalidationEventHub
+{
+    void addInvalidationListener(InvalidationListener listener);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/internal/model/MutableComponentModelImpl.java?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java \
                (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java \
Wed Nov 29 14:02:40 2006 @@ -12,251 +12,257 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.model;
-
+package org.apache.tapestry.internal.model;
+
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.logging.Log;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
 import org.apache.tapestry.ioc.Resource;
 import org.apache.tapestry.ioc.internal.util.Defense;
 import org.apache.tapestry.ioc.internal.util.IdAllocator;
 import org.apache.tapestry.ioc.internal.util.InternalUtils;
-import org.apache.tapestry.model.ComponentModel;
-import org.apache.tapestry.model.EmbeddedComponentModel;
-import org.apache.tapestry.model.MutableComponentModel;
-import org.apache.tapestry.model.MutableEmbeddedComponentModel;
-import org.apache.tapestry.model.ParameterModel;
-
-/**
- * Internal implementation of {@link \
                org.apache.tapestry.model.MutableComponentModel}.
- */
-public final class MutableComponentModelImpl implements MutableComponentModel
-{
-    private final ComponentModel _parentModel;
-
-    private final Resource _baseResource;
-
-    private final String _componentClassName;
-
-    private final IdAllocator _persistentFieldNameAllocator = new IdAllocator();
-
-    private final Log _log;
-
-    private Map<String, ParameterModel> _parameters;
-
-    private Map<String, EmbeddedComponentModel> _embeddedComponents;
-
-    /** Maps from field name to strategy. */
-    private Map<String, String> _persistentFields;
-
-    private List<String> _mixinClassNames;
-
-    private boolean _informalParametersSupported;
-
-    public MutableComponentModelImpl(String componentClassName, Log log, Resource \
                baseResource,
-            ComponentModel parentModel)
-    {
-        _componentClassName = componentClassName;
-        _log = log;
-        _baseResource = baseResource;
-        _parentModel = parentModel;
-
-        // Pre-allocate names from the parent, to avoid name collisions.
-
-        if (_parentModel != null)
-        {
-            for (String name : _parentModel.getPersistentFieldNames())
-            {
-                _persistentFieldNameAllocator.allocateId(name);
-            }
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.format("ComponentModel[%s]", _componentClassName);
-    }
-
-    public Log getLog()
-    {
-        return _log;
-    }
-
-    public Resource getBaseResource()
-    {
-        return _baseResource;
-    }
-
-    public String getComponentClassName()
-    {
-        return _componentClassName;
-    }
-
-    public void addParameter(String name, boolean required)
-    {
-        // TODO: Check for conflict with base model
-
-        if (_parameters == null)
-            _parameters = newMap();
-        else
-        {
-            if (_parameters.containsKey(name))
-                throw new IllegalArgumentException(ModelMessages.duplicateParameter(
-                        name,
-                        _componentClassName));
-        }
-
-        Defense.notBlank(name, "name");
-
-        _parameters.put(name, new ParameterModelImpl(name, required));
-    }
-
-    public ParameterModel getParameterModel(String parameterName)
-    {
-        ParameterModel result = InternalUtils.get(_parameters, parameterName);
-
-        if (result == null && _parentModel != null)
-            result = _parentModel.getParameterModel(parameterName);
-
-        return result;
-    }
-
-    public List<String> getParameterNames()
-    {
-        // TODO: Parameters from base model
-
-        List<String> names = newList();
-
-        if (_parameters != null)
-            names.addAll(_parameters.keySet());
-
-        if (_parentModel != null)
-            names.addAll(_parentModel.getParameterNames());
-
-        Collections.sort(names);
-
-        return names;
-    }
-
-    public MutableEmbeddedComponentModel addEmbeddedComponent(String id, String \
                type,
-            String componentClassName)
-    {
-        // TODO: Parent compent model? Or would we simply override the parent?
-
-        if (_embeddedComponents == null)
-            _embeddedComponents = newMap();
-        else if (_embeddedComponents.containsKey(id))
-            throw new IllegalArgumentException(ModelMessages.duplicateComponentId(
-                    id,
-                    _componentClassName));
-
-        MutableEmbeddedComponentModel embedded = new \
                MutableEmbeddedComponentModelImpl(id, type,
-                componentClassName, _componentClassName);
-
-        _embeddedComponents.put(id, embedded);
-
-        return embedded; // So that parameters can be filled in
-    }
-
-    public List<String> getEmbeddedComponentIds()
-    {
-        List<String> result = newList();
-
-        if (_embeddedComponents != null)
-            result.addAll(_embeddedComponents.keySet());
-
-        if (_parentModel != null)
-            result.addAll(_parentModel.getEmbeddedComponentIds());
-
-        Collections.sort(result);
-
-        return result;
-    }
-
-    public EmbeddedComponentModel getEmbeddedComponentModel(String componentId)
-    {
-        EmbeddedComponentModel result = InternalUtils.get(_embeddedComponents, \
                componentId);
-
-        if (result == null && _parentModel != null)
-            result = _parentModel.getEmbeddedComponentModel(componentId);
-
-        return result;
-    }
-
-    public String getFieldPersistenceStrategy(String fieldName)
-    {
-        String result = InternalUtils.get(_persistentFields, fieldName);
-
-        if (result == null && _parentModel != null)
-            result = _parentModel.getFieldPersistenceStrategy(fieldName);
-
-        if (result == null)
-            throw new \
                IllegalArgumentException(ModelMessages.missingPersistentField(fieldName));
                
-
-        return result;
-    }
-
-    public List<String> getPersistentFieldNames()
-    {
-        // The name allocator is
-        return _persistentFieldNameAllocator.getAllocatedIds();
-    }
-
-    public String setFieldPersistenceStrategy(String fieldName, String strategy)
-    {
-        String stripped = InternalUtils.stripMemberPrefix(fieldName);
-
-        String logicalFieldName = \
                _persistentFieldNameAllocator.allocateId(stripped);
-
-        if (_persistentFields == null)
-            _persistentFields = newMap();
-
-        _persistentFields.put(logicalFieldName, strategy);
-
-        return logicalFieldName;
-    }
-
-    public boolean isRootClass()
-    {
-        return _parentModel == null;
-    }
-
-    public void addMixinClassName(String mixinClassName)
-    {
-        if (_mixinClassNames == null)
-            _mixinClassNames = newList();
-
-        _mixinClassNames.add(mixinClassName);
-    }
-
-    public List<String> getMixinClassNames()
-    {
-        List<String> result = newList();
-        
-        if (_mixinClassNames != null)
-            result.addAll(_mixinClassNames);
-        
-        if (_parentModel != null)
-            result.addAll(_parentModel.getMixinClassNames());
-        
-        Collections.sort(result);
-        
-        return result;
-    }
-
-    public void enableSupportsInformalParameters()
-    {
-        _informalParametersSupported = true;
-    }
-
-    public boolean getSupportsInformalParameters()
-    {
-      return _informalParametersSupported;
-    }
-}
+import org.apache.tapestry.model.ComponentModel;
+import org.apache.tapestry.model.EmbeddedComponentModel;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.model.MutableEmbeddedComponentModel;
+import org.apache.tapestry.model.ParameterModel;
+
+/**
+ * Internal implementation of {@link \
org.apache.tapestry.model.MutableComponentModel}. + */
+public final class MutableComponentModelImpl implements MutableComponentModel
+{
+    private final ComponentModel _parentModel;
+
+    private final Resource _baseResource;
+
+    private final String _componentClassName;
+
+    private final IdAllocator _persistentFieldNameAllocator = new IdAllocator();
+
+    private final Log _log;
+
+    private Map<String, ParameterModel> _parameters;
+
+    private Map<String, EmbeddedComponentModel> _embeddedComponents;
+
+    /** Maps from field name to strategy. */
+    private Map<String, String> _persistentFields;
+
+    private List<String> _mixinClassNames;
+
+    private boolean _informalParametersSupported;
+
+    public MutableComponentModelImpl(String componentClassName, Log log, Resource \
baseResource, +            ComponentModel parentModel)
+    {
+        _componentClassName = componentClassName;
+        _log = log;
+        _baseResource = baseResource;
+        _parentModel = parentModel;
+
+        // Pre-allocate names from the parent, to avoid name collisions.
+
+        if (_parentModel != null)
+        {
+            for (String name : _parentModel.getPersistentFieldNames())
+            {
+                _persistentFieldNameAllocator.allocateId(name);
+            }
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("ComponentModel[%s]", _componentClassName);
+    }
+
+    public Log getLog()
+    {
+        return _log;
+    }
+
+    public Resource getBaseResource()
+    {
+        return _baseResource;
+    }
+
+    public String getComponentClassName()
+    {
+        return _componentClassName;
+    }
+
+    public void addParameter(String name, boolean required)
+    {
+        // TODO: Check for conflict with base model
+
+        if (_parameters == null)
+            _parameters = newMap();
+        else
+        {
+            if (_parameters.containsKey(name))
+                throw new IllegalArgumentException(ModelMessages.duplicateParameter(
+                        name,
+                        _componentClassName));
+        }
+
+        Defense.notBlank(name, "name");
+
+        _parameters.put(name, new ParameterModelImpl(name, required));
+    }
+
+    public ParameterModel getParameterModel(String parameterName)
+    {
+        ParameterModel result = InternalUtils.get(_parameters, parameterName);
+
+        if (result == null && _parentModel != null)
+            result = _parentModel.getParameterModel(parameterName);
+
+        return result;
+    }
+
+    public List<String> getParameterNames()
+    {
+        // TODO: Parameters from base model
+
+        List<String> names = newList();
+
+        if (_parameters != null)
+            names.addAll(_parameters.keySet());
+
+        if (_parentModel != null)
+            names.addAll(_parentModel.getParameterNames());
+
+        Collections.sort(names);
+
+        return names;
+    }
+
+    public MutableEmbeddedComponentModel addEmbeddedComponent(String id, String \
type, +            String componentClassName)
+    {
+        // TODO: Parent compent model? Or would we simply override the parent?
+
+        if (_embeddedComponents == null)
+            _embeddedComponents = newMap();
+        else if (_embeddedComponents.containsKey(id))
+            throw new IllegalArgumentException(ModelMessages.duplicateComponentId(
+                    id,
+                    _componentClassName));
+
+        MutableEmbeddedComponentModel embedded = new \
MutableEmbeddedComponentModelImpl(id, type, +                componentClassName, \
_componentClassName); +
+        _embeddedComponents.put(id, embedded);
+
+        return embedded; // So that parameters can be filled in
+    }
+
+    public List<String> getEmbeddedComponentIds()
+    {
+        List<String> result = newList();
+
+        if (_embeddedComponents != null)
+            result.addAll(_embeddedComponents.keySet());
+
+        if (_parentModel != null)
+            result.addAll(_parentModel.getEmbeddedComponentIds());
+
+        Collections.sort(result);
+
+        return result;
+    }
+
+    public EmbeddedComponentModel getEmbeddedComponentModel(String componentId)
+    {
+        EmbeddedComponentModel result = InternalUtils.get(_embeddedComponents, \
componentId); +
+        if (result == null && _parentModel != null)
+            result = _parentModel.getEmbeddedComponentModel(componentId);
+
+        return result;
+    }
+
+    public String getFieldPersistenceStrategy(String fieldName)
+    {
+        String result = InternalUtils.get(_persistentFields, fieldName);
+
+        if (result == null && _parentModel != null)
+            result = _parentModel.getFieldPersistenceStrategy(fieldName);
+
+        if (result == null)
+            throw new \
IllegalArgumentException(ModelMessages.missingPersistentField(fieldName)); +
+        return result;
+    }
+
+    public List<String> getPersistentFieldNames()
+    {
+        // The name allocator is
+        return _persistentFieldNameAllocator.getAllocatedIds();
+    }
+
+    public String setFieldPersistenceStrategy(String fieldName, String strategy)
+    {
+        String stripped = InternalUtils.stripMemberPrefix(fieldName);
+
+        String logicalFieldName = \
_persistentFieldNameAllocator.allocateId(stripped); +
+        if (_persistentFields == null)
+            _persistentFields = newMap();
+
+        _persistentFields.put(logicalFieldName, strategy);
+
+        return logicalFieldName;
+    }
+
+    public boolean isRootClass()
+    {
+        return _parentModel == null;
+    }
+
+    public void addMixinClassName(String mixinClassName)
+    {
+        if (_mixinClassNames == null)
+            _mixinClassNames = newList();
+
+        _mixinClassNames.add(mixinClassName);
+    }
+
+    public List<String> getMixinClassNames()
+    {
+        List<String> result = newList();
+
+        if (_mixinClassNames != null)
+            result.addAll(_mixinClassNames);
+
+        if (_parentModel != null)
+            result.addAll(_parentModel.getMixinClassNames());
+
+        Collections.sort(result);
+
+        return result;
+    }
+
+    public void enableSupportsInformalParameters()
+    {
+        _informalParametersSupported = true;
+    }
+
+    public boolean getSupportsInformalParameters()
+    {
+        return _informalParametersSupported;
+    }
+
+    public ComponentModel getParentModel()
+    {
+        return _parentModel;
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentMessagesSourceImpl.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/internal/services/ComponentMessagesSourceImpl.java?view=auto&rev=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentMessagesSourceImpl.java \
                (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentMessagesSourceImpl.java \
Wed Nov 29 14:02:40 2006 @@ -0,0 +1,265 @@
+// 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.newList;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newThreadSafeMap;
 +
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.tapestry.events.UpdateListener;
+import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
+import org.apache.tapestry.internal.util.MultiKey;
+import org.apache.tapestry.internal.util.URLChangeTracker;
+import org.apache.tapestry.ioc.Messages;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.ioc.internal.util.LocalizedNameGenerator;
+import org.apache.tapestry.model.ComponentModel;
+import org.apache.tapestry.services.ComponentMessagesSource;
+
+public class ComponentMessagesSourceImpl extends InvalidationEventHubImpl implements
+        ComponentMessagesSource, UpdateListener
+{
+    private final URLChangeTracker _tracker;
+
+    /** Keyed on component class name + locale. */
+    private final Map<MultiKey, Messages> _messagesByClassAndLocaleCache = \
newThreadSafeMap(); +
+    /**
+     * Keyed on component class name and locale, the coooked properties include \
properties inherited +     * from less local-specific properties files, or inherited \
from base classes. +     */
+    private final Map<MultiKey, Map<String, String>> _cookedProperties = \
newThreadSafeMap(); +
+    /**
+     * Raw properties represent just the properties read from a specific properties \
file, in +     * isolation.
+     */
+    private final Map<Resource, Map<String, String>> _rawProperties = \
newThreadSafeMap(); +
+    private final Map<String, String> _emptyMap = Collections.emptyMap();
+
+    public ComponentMessagesSourceImpl()
+    {
+        this(new URLChangeTracker());
+    }
+
+    ComponentMessagesSourceImpl(URLChangeTracker tracker)
+    {
+        _tracker = tracker;
+    }
+
+    public void checkForUpdates()
+    {
+        if (_tracker.containsChanges())
+        {
+            _messagesByClassAndLocaleCache.clear();
+            _cookedProperties.clear();
+            _rawProperties.clear();
+
+            _tracker.clear();
+
+            fireInvalidationEvent();
+        }
+    }
+
+    public Messages getMessages(ComponentModel componentModel, Locale locale)
+    {
+        MultiKey key = new MultiKey(componentModel.getComponentClassName(), locale);
+
+        Messages result = _messagesByClassAndLocaleCache.get(key);
+
+        if (result == null)
+        {
+            result = buildMessages(componentModel, locale);
+            _messagesByClassAndLocaleCache.put(key, result);
+        }
+
+        return result;
+    }
+
+    private Messages buildMessages(ComponentModel componentModel, Locale locale)
+    {
+        Map<String, String> properties = findComponentProperties(componentModel, \
locale); +
+        return new MapMessages(properties);
+    }
+
+    /**
+     * Assembles a set of properties appropriate for the component in question, and \
the desired +     * locale. The properties reflect the properties of the component's \
super class (if any) for the +     * locale, overalyed with any properties defined \
for this component and its locale. +     * 
+     * @param componentModel
+     * @param locale
+     * @return
+     */
+    private Map<String, String> findComponentProperties(ComponentModel \
componentModel, Locale locale) +    {
+        if (componentModel == null)
+            return _emptyMap;
+
+        MultiKey key = new MultiKey(componentModel.getComponentClassName(), locale);
+
+        Map<String, String> existing = _cookedProperties.get(key);
+
+        if (existing != null)
+            return existing;
+
+        // What would be cool is if we could maintain a cache of component class \
name + locale --> +        // Resource. That would optimize quite a bit of this; may \
need to use an alternative to +        // LocalizedNameGenerator.
+
+        Resource propertiesResource = \
componentModel.getBaseResource().withExtension("properties"); +
+        List<Resource> localizations = newList();
+
+        for (String localizedFile : new \
LocalizedNameGenerator(propertiesResource.getFile(), locale)) +        {
+            Resource localized = propertiesResource.forFile(localizedFile);
+
+            localizations.add(localized);
+        }
+
+        Collections.reverse(localizations);
+
+        // Localizations are now in least-specific to most-specific order.
+
+        Map<String, String> previous = findComponentProperties(
+                componentModel.getParentModel(),
+                locale);
+
+        for (Resource localization : localizations)
+        {
+            Map<String, String> rawProperties = getRawProperties(localization);
+
+            Map<String, String> properties = extend(previous, rawProperties);
+
+            // Woould be nice to write into the _cookedProperties cache here,
+            // but we can't because we don't know the locale part of the MultiKey.
+
+            previous = properties;
+        }
+
+        _cookedProperties.put(key, previous);
+
+        return previous;
+    }
+
+    /**
+     * Returns a new map consisting of all the properties in previous overlayed with \
all the +     * properties in rawProperties. If rawProperties is empty, returns just \
the base map. +     */
+    private Map<String, String> extend(Map<String, String> base, Map<String, String> \
rawProperties) +    {
+        if (rawProperties.isEmpty())
+            return base;
+
+        Map<String, String> result = newMap(base);
+
+        result.putAll(rawProperties);
+
+        return result;
+    }
+
+    private Map<String, String> getRawProperties(Resource localization)
+    {
+        Map<String, String> result = _rawProperties.get(localization);
+
+        if (result == null)
+        {
+            result = readProperties(localization);
+
+            _rawProperties.put(localization, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates and returns a new map that contains properties read from the \
properties file. +     */
+    private Map<String, String> readProperties(Resource resource)
+    {
+        URL url = resource.toURL();
+
+        if (url == null)
+            return _emptyMap;
+
+        _tracker.add(url);
+
+        Map<String, String> result = newMap();
+
+        Properties p = new Properties();
+        InputStream is = null;
+
+        try
+        {
+            is = new BufferedInputStream(url.openStream());
+
+            p.load(is);
+
+            is.close();
+
+            is = null;
+        }
+        catch (Exception ex)
+        {
+            throw new \
RuntimeException(ServicesMessages.failureReadingComponentMessages(url, ex), +         \
ex); +        }
+        finally
+        {
+            close(is);
+        }
+
+        for (Map.Entry e : p.entrySet())
+        {
+            String key = e.getKey().toString();
+
+            String value = p.getProperty(key);
+
+            // Add new value, or overwrite value inherited from less-specific \
localization, or from +            // parent component class.
+
+            result.put(key, value);
+        }
+
+        return result;
+    }
+
+    private final void close(Closeable stream)
+    {
+        if (stream != null)
+            try
+            {
+                stream.close();
+            }
+            catch (IOException ex)
+            { // Ignore.
+
+            }
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentTemplateSourceImpl.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/internal/services/ComponentTemplateSourceImpl.java?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentTemplateSourceImpl.java \
                (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentTemplateSourceImpl.java \
Wed Nov 29 14:02:40 2006 @@ -26,6 +26,7 @@
 import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
 import org.apache.tapestry.internal.parser.ComponentTemplate;
 import org.apache.tapestry.internal.parser.TemplateToken;
+import org.apache.tapestry.internal.util.MultiKey;
 import org.apache.tapestry.internal.util.URLChangeTracker;
 import org.apache.tapestry.ioc.Resource;
 import org.apache.tapestry.ioc.internal.util.ClasspathResource;
@@ -46,7 +47,7 @@
      * parsed from the same "foo.html" resource). The resource may end up being \
                null, meaning the
      * template does not exist in any locale.
      */
-    private final Map<String, Resource> _templateResources = newThreadSafeMap();
+    private final Map<MultiKey, Resource> _templateResources = newThreadSafeMap();
 
     /**
      * Cache of parsed templates, keyed on resource.
@@ -98,7 +99,7 @@
      */
     public ComponentTemplate getTemplate(String componentName, Locale locale)
     {
-        String key = componentName + ":" + locale.toString();
+        MultiKey key = new MultiKey(componentName, locale);
 
         // First cache is key to resource.
 
@@ -128,7 +129,7 @@
         // In a race condition, we may parse the same template more than once. This \
                will likely add
         // the resource to the tracker multiple times. Not likely this will cause a \
big issue.  
-        URL resourceURL = r.getResourceURL();
+        URL resourceURL = r.toURL();
 
         if (resourceURL == null)
             return _missingTemplate;
@@ -145,7 +146,7 @@
 
         String path = componentName.replace('.', '/') + ".html";
         Resource baseResource = new ClasspathResource(_loader, path);
-        Resource localized = baseResource.getLocalization(locale);
+        Resource localized = baseResource.forLocale(locale);
 
         // In a race condition, we may hit this method a couple of times, and \
overwrite previous  // results with identical new results.

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MapMessages.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MapMessages.java?view=auto&rev=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MapMessages.java \
                (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MapMessages.java \
Wed Nov 29 14:02:40 2006 @@ -0,0 +1,46 @@
+// 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;
+
+/**
+ * An implementation  of {@link Messages} that is based on a map.
+ * 
+ */
+import java.util.Map;
+
+import org.apache.tapestry.ioc.Messages;
+import org.apache.tapestry.ioc.util.AbstractMessages;
+
+/**
+ * Implementation of {@link Messages} based on a simple Map (of string keys and \
values). + */
+public class MapMessages extends AbstractMessages
+{
+    private final Map<String, String> _properties;
+
+    /**
+     * A new instance <strong>retaining</strong> (not copying) the provided map.
+     */
+    public MapMessages(final Map<String, String> properties)
+    {
+        _properties = properties;
+    }
+
+    @Override
+    protected String valueForKey(String key)
+    {
+        return _properties.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=480728&r1=480727&r2=480728
 ==============================================================================
--- 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 \
Wed Nov 29 14:02:40 2006 @@ -14,6 +14,7 @@
 
 package org.apache.tapestry.internal.services;
 
+import java.net.URL;
 import java.util.Collection;
 import java.util.List;
 
@@ -243,5 +244,10 @@
         return MESSAGES.format("component-instance-is-not-a-page", \
                methodDescription, component
                 .getComponentResources().getCompleteId(), \
                result.getComponentResources()
                 .getCompleteId());
+    }
+
+    static String failureReadingComponentMessages(URL url, Throwable cause)
+    {
+        return MESSAGES.format("failure-reading-component-message", url, cause);
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/internal/services/TemplateParserImpl.java?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java \
                (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java \
Wed Nov 29 14:02:40 2006 @@ -12,511 +12,511 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.services;
-
+package org.apache.tapestry.internal.services;
+
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
-
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.internal.parser.AttributeToken;
-import org.apache.tapestry.internal.parser.BodyToken;
-import org.apache.tapestry.internal.parser.CDATAToken;
-import org.apache.tapestry.internal.parser.CommentToken;
-import org.apache.tapestry.internal.parser.ComponentTemplate;
-import org.apache.tapestry.internal.parser.ComponentTemplateImpl;
-import org.apache.tapestry.internal.parser.EndElementToken;
-import org.apache.tapestry.internal.parser.ExpansionToken;
-import org.apache.tapestry.internal.parser.StartComponentToken;
-import org.apache.tapestry.internal.parser.StartElementToken;
-import org.apache.tapestry.internal.parser.TemplateToken;
-import org.apache.tapestry.internal.parser.TextToken;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.internal.parser.AttributeToken;
+import org.apache.tapestry.internal.parser.BodyToken;
+import org.apache.tapestry.internal.parser.CDATAToken;
+import org.apache.tapestry.internal.parser.CommentToken;
+import org.apache.tapestry.internal.parser.ComponentTemplate;
+import org.apache.tapestry.internal.parser.ComponentTemplateImpl;
+import org.apache.tapestry.internal.parser.EndElementToken;
+import org.apache.tapestry.internal.parser.ExpansionToken;
+import org.apache.tapestry.internal.parser.StartComponentToken;
+import org.apache.tapestry.internal.parser.StartElementToken;
+import org.apache.tapestry.internal.parser.TemplateToken;
+import org.apache.tapestry.internal.parser.TextToken;
 import org.apache.tapestry.ioc.Location;
 import org.apache.tapestry.ioc.Resource;
 import org.apache.tapestry.ioc.internal.util.InternalUtils;
 import org.apache.tapestry.ioc.internal.util.LocationImpl;
 import org.apache.tapestry.ioc.internal.util.TapestryException;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Non-threadsafe implementation.
- */
-public class TemplateParserImpl extends DefaultHandler implements TemplateParser, \
                LexicalHandler
-{
-    public static final String TAPESTRY_SCHEMA_5_0_0 = \
                "http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";
-
-    private final SAXParserFactory _parserFactory;
-
-    private SAXParser _parser;
-
-    // Resource being parsed
-    private Resource _templateResource;
-
-    private Locator _locator;
-
-    private final List<TemplateToken> _tokens = newList();
-
-    // A stack of maps, each map contains mappings from prefix to namespace URL
-
-    private final List<Map<String, String>> _prefixResolutionStack = newList();
-
-    // Non-blank ids from start component (<comp>) elements
-
-    private final Set<String> _componentIds = newSet();
-
-    // Used to accumulate text provided by the characters(). Even contiguous \
                characters may be
-    // broken up across multiple invocations due to parser internals. We accumulate \
                those together
-    // before forming a text token.
-
-    private final StringBuilder _textBuffer = new StringBuilder();
-
-    private Location _textStartLocation;
-
-    private boolean _textIsCData;
-
-    private boolean _insideBody;
-
-    private boolean _insideBodyErrorLogged;
-
-    private boolean _ignoreEvents;
-
-    private final Log _log;
-
-    private final Pattern EXPANSION_PATTERN = Pattern.compile(
-            "\\$\\{\\s*(.*)\\s*\\}",
-            Pattern.MULTILINE);
-
-    public TemplateParserImpl(Log log)
-    {
-        _log = log;
-
-        _parserFactory = SAXParserFactory.newInstance();
-
-        _parserFactory.setNamespaceAware(true);
-
-        reset();
-    }
-
-    private void reset()
-    {
-        _tokens.clear();
-        _prefixResolutionStack.clear();
-        _componentIds.clear();
-        _templateResource = null;
-        _locator = null;
-        _textBuffer.setLength(0);
-        _textStartLocation = null;
-        _textIsCData = false;
-        _insideBody = false;
-        _insideBodyErrorLogged = false;
-        _ignoreEvents = true;
-
-    }
-
-    public ComponentTemplate parseTemplate(Resource templateResource)
-    {
-        if (_parser == null)
-        {
-            try
-            {
-                _parser = _parserFactory.newSAXParser();
-
-                _parser.setProperty("http://xml.org/sax/properties/lexical-handler", \
                this);
-            }
-            catch (Exception ex)
-            {
-                throw new \
                RuntimeException(ServicesMessages.newParserError(templateResource, \
                ex),
-                        ex);
-            }
-        }
-
-        URL resourceURL = templateResource.getResourceURL();
-
-        if (resourceURL == null)
-            throw new \
                RuntimeException(ServicesMessages.missingTemplateResource(templateResource));
                
-
-        Map<String, String> rootPrefixMap = newMap();
-        _prefixResolutionStack.add(rootPrefixMap);
-
-        _templateResource = templateResource;
-
-        try
-        {
-            InputSource source = new InputSource(resourceURL.openStream());
-
-            _parser.parse(source, this);
-
-            return new ComponentTemplateImpl(_templateResource, _tokens, \
                _componentIds);
-        }
-        catch (Exception ex)
-        {
-            // Some parsers get in an unknown state when an error occurs, and are \
                are not
-            // subsequently useable.
-
-            _parser = null;
-
-            throw new \
                TapestryException(ServicesMessages.templateParseError(templateResource, \
                ex),
-                    getCurrentLocation(), ex);
-        }
-        finally
-        {
-            reset();
-        }
-    }
-
-    @Override
-    public void setDocumentLocator(Locator locator)
-    {
-        _locator = locator;
-    }
-
-    /**
-     * Creates a new prefix to URI mapping map and adds this new prefix/URI mapping \
                into the map.
-     * The new map is pushed onto the stack of such maps.
-     */
-    @Override
-    public void startPrefixMapping(String prefix, String uri) throws SAXException
-    {
-        Map<String, String> topMap = _prefixResolutionStack.get(0);
-        Map<String, String> newMap = newMap(topMap);
-        newMap.put(prefix, uri);
-
-        _prefixResolutionStack.add(0, newMap);
-    }
-
-    /** Pops the top prefix mapping map off the stack. */
-    @Override
-    public void endPrefixMapping(String prefix) throws SAXException
-    {
-        _prefixResolutionStack.remove(0);
-    }
-
-    /** Accumulates the characters into a text buffer. */
-    @Override
-    public void characters(char[] ch, int start, int length) throws SAXException
-    {
-        if (_ignoreEvents)
-            return;
-
-        if (insideBody())
-            return;
-
-        if (_textBuffer.length() == 0)
-            _textStartLocation = getCurrentLocation();
-
-        _textBuffer.append(ch, start, length);
-    }
-
-    /**
-     * Adds tokens corresponding to the content in the text buffer. For a non-CDATA \
                section, we also
-     * search for expansions (thus we may add more than one token). Clears the text \
                buffer.
-     */
-    private void processTextBuffer()
-    {
-        if (_textBuffer.length() == 0)
-            return;
-
-        String text = _textBuffer.toString();
-
-        if (_textIsCData)
-        {
-            _tokens.add(new CDATAToken(text, _textStartLocation));
-        }
-        else
-        {
-            addTokensForText(text);
-        }
-
-        _textBuffer.setLength(0);
-    }
-
-    /**
-     * Scans the text, using a regular expression pattern, for expansion patterns, \
                and adds
-     * appropriate tokens for what it finds.
-     * 
-     * @param text
-     */
-    private void addTokensForText(String text)
-    {
-        Matcher matcher = EXPANSION_PATTERN.matcher(text);
-
-        int startx = 0;
-
-        // The big problem with all this code is that everything gets assigned to \
                the
-        // start of the text block, even if there are line breaks leading up to it.
-        // That's going to take a lot more work and there are bigger fish to fry.
-
-        while (matcher.find())
-        {
-            int matchStart = matcher.start();
-
-            if (matchStart != startx)
-            {
-                String prefix = text.substring(startx, matchStart);
-
-                _tokens.add(new TextToken(prefix, _textStartLocation));
-            }
-
-            String expression = matcher.group(1);
-
-            _tokens.add(new ExpansionToken(expression, _textStartLocation));
-
-            startx = matcher.end();
-        }
-
-        // Catch anything after the final regexp match.
-
-        if (startx < text.length())
-            _tokens.add(new TextToken(text.substring(startx, text.length()), \
                _textStartLocation));
-    }
-
-    @Override
-    public void startElement(String uri, String localName, String qName, Attributes \
                attributes)
-            throws SAXException
-    {
-        _ignoreEvents = false;
-
-        if (_insideBody)
-            throw new IllegalStateException(ServicesMessages
-                    .mayNotNestElementsInsideBody(localName));
-
-        // Add any accumulated text into a text token
-        processTextBuffer();
-
-        if (TAPESTRY_SCHEMA_5_0_0.equals(uri))
-        {
-            startTapestryElement(localName, attributes);
-            return;
-        }
-
-        // TODO: Handle tapestry namespace elements
-        // TODO: Handle tapestry namespace attributes in ordinary namespace \
                elements?
-        // TODO: Handle interpolations inside attributes?
-
-        Location location = getCurrentLocation();
-
-        _tokens.add(new StartElementToken(localName, location));
-
-        int count = attributes.getLength();
-
-        for (int i = 0; i < count; i++)
-        {
-            String name = attributes.getLocalName(i);
-
-            if (InternalUtils.isBlank(name))
-                continue;
-
-            String value = attributes.getValue(i);
-
-            _tokens.add(new AttributeToken(name, value, location));
-        }
-    }
-
-    /**
-     * Checks to see if currently inside a t:body element (which should always be \
                empty). Content is
-     * ignored inside a body. If inside a body, then a warning is logged (but only \
                one warning per
-     * body element).
-     * 
-     * @return true if inside t:body, false otherwise
-     */
-    private boolean insideBody()
-    {
-        if (_insideBody)
-        {
-            // Limit to one logged error per infraction.
-
-            if (!_insideBodyErrorLogged)
-                _log.error(ServicesMessages.contentInsideBodyNotAllowed(getCurrentLocation()));
                
-
-            _insideBodyErrorLogged = true;
-        }
-
-        return _insideBody;
-    }
-
-    private void startTapestryElement(String localName, Attributes attributes)
-    {
-        if (localName.equals("comp"))
-        {
-            startComponent(localName, attributes);
-            return;
-        }
-
-        if (localName.equals("body"))
-        {
-            startBody();
-            return;
-        }
-
-        throw new IllegalArgumentException("Unknown localName: '" + localName + \
                "'.");
-    }
-
-    private void startComponent(String localName, Attributes attributes)
-    {
-        String id = null;
-        String type = null;
-        String mixins = null;
-        int count = attributes.getLength();
-        Location location = getCurrentLocation();
-        List<TemplateToken> attributeTokens = newList();
-
-        for (int i = 0; i < count; i++)
-        {
-            String name = attributes.getLocalName(i);
-            String value = attributes.getValue(i);
-
-            // TODO: Validate that the id is a reasonable string.
-
-            if (name.equals("id"))
-            {
-                if (InternalUtils.isNonBlank(value))
-                    id = value;
-                continue;
-            }
-
-            if (name.equals("type"))
-            {
-
-                if (InternalUtils.isNonBlank(value))
-                    type = value;
-                continue;
-            }
-
-            if (name.equals("mixins"))
-            {
-                if (InternalUtils.isNonBlank(value))
-                    mixins = value;
-                continue;
-            }
-
-            attributeTokens.add(new AttributeToken(name, value, location));
-        }
-
-        if (id == null && type == null)
-            throw new TapestryException(ServicesMessages.compRequiresIdOrType(), \
                location, null);
-
-        if (id != null)
-            _componentIds.add(id);
-
-        // Add the component
-        _tokens.add(new StartComponentToken(id, type, mixins, location));
-        _tokens.addAll(attributeTokens);
-    }
-
-    private void startBody()
-    {
-        _tokens.add(new BodyToken(getCurrentLocation()));
-
-        _insideBody = true;
-        _insideBodyErrorLogged = false;
-    }
-
-    @Override
-    public void endElement(String uri, String localName, String qName) throws \
                SAXException
-    {
-        processTextBuffer();
-
-        // TODO: Handle tapestry namespace elements?
-
-        // Because XML tags are always balanced, we don't even need to know what \
                element just closed
-        // when we assemble things later.
-
-        if (!_insideBody)
-            _tokens.add(new EndElementToken(getCurrentLocation()));
-
-        _insideBody = false;
-    }
-
-    private Location getCurrentLocation()
-    {
-        if (_locator == null)
-            return null;
-
-        return new LocationImpl(_templateResource, _locator.getLineNumber(), \
                _locator
-                .getColumnNumber());
-    }
-
-    public void comment(char[] ch, int start, int length) throws SAXException
-    {
-        if (_ignoreEvents || insideBody())
-            return;
-
-        processTextBuffer();
-
-        String comment = new String(ch, start, length);
-
-        // TODO: Perhaps comments need to be "aggregated" the same way we aggregate \
                text and CDATA.
-        // Hm. Probably not. Any whitespace between one comment and the next will \
                become a
-        // TextToken.
-        // Unless we trim whitespace between consecutive comments ... and on down \
                the rabbit hole.
-        // Oops -- unless a single comment may be passed into this method as \
                multiple calls
-        // (have to check how multiline comments are handled).
-        // Tests against Sun's built in parser does show that multiline comments are \
                still
-        // provided as a single call to comment(), so we're good for the meantime \
                (until we find
-        // out some parsers aren't so compliant).
-
-        _tokens.add(new CommentToken(comment, getCurrentLocation()));
-    }
-
-    public void endCDATA() throws SAXException
-    {
-        // Add a token for any accumulated CDATA.
-
-        processTextBuffer();
-
-        // Again, CDATA doesn't nest, so we know we're back to ordinary markup.
-
-        _textIsCData = false;
-    }
-
-    public void endDTD() throws SAXException
-    {
-    }
-
-    public void endEntity(String name) throws SAXException
-    {
-    }
-
-    public void startCDATA() throws SAXException
-    {
-        if (_ignoreEvents || insideBody())
-            return;
-
-        processTextBuffer();
-
-        // Because CDATA doesn't mix with any other SAX/lexical events, we can \
                simply turn on a flag
-        // here and turn it off when we see the end.
-
-        _textIsCData = true;
-    }
-
-    public void startDTD(String name, String publicId, String systemId) throws \
                SAXException
-    {
-    }
-
-    public void startEntity(String name) throws SAXException
-    {
-
-    }
-
-    @Override
-    public void ignorableWhitespace(char[] ch, int start, int length) throws \
                SAXException
-    {
-    }
-
-}
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Non-threadsafe implementation.
+ */
+public class TemplateParserImpl extends DefaultHandler implements TemplateParser, \
LexicalHandler +{
+    public static final String TAPESTRY_SCHEMA_5_0_0 = \
"http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"; +
+    private final SAXParserFactory _parserFactory;
+
+    private SAXParser _parser;
+
+    // Resource being parsed
+    private Resource _templateResource;
+
+    private Locator _locator;
+
+    private final List<TemplateToken> _tokens = newList();
+
+    // A stack of maps, each map contains mappings from prefix to namespace URL
+
+    private final List<Map<String, String>> _prefixResolutionStack = newList();
+
+    // Non-blank ids from start component (<comp>) elements
+
+    private final Set<String> _componentIds = newSet();
+
+    // Used to accumulate text provided by the characters(). Even contiguous \
characters may be +    // broken up across multiple invocations due to parser \
internals. We accumulate those together +    // before forming a text token.
+
+    private final StringBuilder _textBuffer = new StringBuilder();
+
+    private Location _textStartLocation;
+
+    private boolean _textIsCData;
+
+    private boolean _insideBody;
+
+    private boolean _insideBodyErrorLogged;
+
+    private boolean _ignoreEvents;
+
+    private final Log _log;
+
+    private final Pattern EXPANSION_PATTERN = Pattern.compile(
+            "\\$\\{\\s*(.*)\\s*\\}",
+            Pattern.MULTILINE);
+
+    public TemplateParserImpl(Log log)
+    {
+        _log = log;
+
+        _parserFactory = SAXParserFactory.newInstance();
+
+        _parserFactory.setNamespaceAware(true);
+
+        reset();
+    }
+
+    private void reset()
+    {
+        _tokens.clear();
+        _prefixResolutionStack.clear();
+        _componentIds.clear();
+        _templateResource = null;
+        _locator = null;
+        _textBuffer.setLength(0);
+        _textStartLocation = null;
+        _textIsCData = false;
+        _insideBody = false;
+        _insideBodyErrorLogged = false;
+        _ignoreEvents = true;
+
+    }
+
+    public ComponentTemplate parseTemplate(Resource templateResource)
+    {
+        if (_parser == null)
+        {
+            try
+            {
+                _parser = _parserFactory.newSAXParser();
+
+                _parser.setProperty("http://xml.org/sax/properties/lexical-handler", \
this); +            }
+            catch (Exception ex)
+            {
+                throw new \
RuntimeException(ServicesMessages.newParserError(templateResource, ex), +             \
ex); +            }
+        }
+
+        URL resourceURL = templateResource.toURL();
+
+        if (resourceURL == null)
+            throw new \
RuntimeException(ServicesMessages.missingTemplateResource(templateResource)); +
+        Map<String, String> rootPrefixMap = newMap();
+        _prefixResolutionStack.add(rootPrefixMap);
+
+        _templateResource = templateResource;
+
+        try
+        {
+            InputSource source = new InputSource(resourceURL.openStream());
+
+            _parser.parse(source, this);
+
+            return new ComponentTemplateImpl(_templateResource, _tokens, \
_componentIds); +        }
+        catch (Exception ex)
+        {
+            // Some parsers get in an unknown state when an error occurs, and are \
are not +            // subsequently useable.
+
+            _parser = null;
+
+            throw new \
TapestryException(ServicesMessages.templateParseError(templateResource, ex), +        \
getCurrentLocation(), ex); +        }
+        finally
+        {
+            reset();
+        }
+    }
+
+    @Override
+    public void setDocumentLocator(Locator locator)
+    {
+        _locator = locator;
+    }
+
+    /**
+     * Creates a new prefix to URI mapping map and adds this new prefix/URI mapping \
into the map. +     * The new map is pushed onto the stack of such maps.
+     */
+    @Override
+    public void startPrefixMapping(String prefix, String uri) throws SAXException
+    {
+        Map<String, String> topMap = _prefixResolutionStack.get(0);
+        Map<String, String> newMap = newMap(topMap);
+        newMap.put(prefix, uri);
+
+        _prefixResolutionStack.add(0, newMap);
+    }
+
+    /** Pops the top prefix mapping map off the stack. */
+    @Override
+    public void endPrefixMapping(String prefix) throws SAXException
+    {
+        _prefixResolutionStack.remove(0);
+    }
+
+    /** Accumulates the characters into a text buffer. */
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException
+    {
+        if (_ignoreEvents)
+            return;
+
+        if (insideBody())
+            return;
+
+        if (_textBuffer.length() == 0)
+            _textStartLocation = getCurrentLocation();
+
+        _textBuffer.append(ch, start, length);
+    }
+
+    /**
+     * Adds tokens corresponding to the content in the text buffer. For a non-CDATA \
section, we also +     * search for expansions (thus we may add more than one token). \
Clears the text buffer. +     */
+    private void processTextBuffer()
+    {
+        if (_textBuffer.length() == 0)
+            return;
+
+        String text = _textBuffer.toString();
+
+        if (_textIsCData)
+        {
+            _tokens.add(new CDATAToken(text, _textStartLocation));
+        }
+        else
+        {
+            addTokensForText(text);
+        }
+
+        _textBuffer.setLength(0);
+    }
+
+    /**
+     * Scans the text, using a regular expression pattern, for expansion patterns, \
and adds +     * appropriate tokens for what it finds.
+     * 
+     * @param text
+     */
+    private void addTokensForText(String text)
+    {
+        Matcher matcher = EXPANSION_PATTERN.matcher(text);
+
+        int startx = 0;
+
+        // The big problem with all this code is that everything gets assigned to \
the +        // start of the text block, even if there are line breaks leading up to \
it. +        // That's going to take a lot more work and there are bigger fish to \
fry. +
+        while (matcher.find())
+        {
+            int matchStart = matcher.start();
+
+            if (matchStart != startx)
+            {
+                String prefix = text.substring(startx, matchStart);
+
+                _tokens.add(new TextToken(prefix, _textStartLocation));
+            }
+
+            String expression = matcher.group(1);
+
+            _tokens.add(new ExpansionToken(expression, _textStartLocation));
+
+            startx = matcher.end();
+        }
+
+        // Catch anything after the final regexp match.
+
+        if (startx < text.length())
+            _tokens.add(new TextToken(text.substring(startx, text.length()), \
_textStartLocation)); +    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes \
attributes) +            throws SAXException
+    {
+        _ignoreEvents = false;
+
+        if (_insideBody)
+            throw new IllegalStateException(ServicesMessages
+                    .mayNotNestElementsInsideBody(localName));
+
+        // Add any accumulated text into a text token
+        processTextBuffer();
+
+        if (TAPESTRY_SCHEMA_5_0_0.equals(uri))
+        {
+            startTapestryElement(localName, attributes);
+            return;
+        }
+
+        // TODO: Handle tapestry namespace elements
+        // TODO: Handle tapestry namespace attributes in ordinary namespace \
elements? +        // TODO: Handle interpolations inside attributes?
+
+        Location location = getCurrentLocation();
+
+        _tokens.add(new StartElementToken(localName, location));
+
+        int count = attributes.getLength();
+
+        for (int i = 0; i < count; i++)
+        {
+            String name = attributes.getLocalName(i);
+
+            if (InternalUtils.isBlank(name))
+                continue;
+
+            String value = attributes.getValue(i);
+
+            _tokens.add(new AttributeToken(name, value, location));
+        }
+    }
+
+    /**
+     * Checks to see if currently inside a t:body element (which should always be \
empty). Content is +     * ignored inside a body. If inside a body, then a warning is \
logged (but only one warning per +     * body element).
+     * 
+     * @return true if inside t:body, false otherwise
+     */
+    private boolean insideBody()
+    {
+        if (_insideBody)
+        {
+            // Limit to one logged error per infraction.
+
+            if (!_insideBodyErrorLogged)
+                _log.error(ServicesMessages.contentInsideBodyNotAllowed(getCurrentLocation()));
 +
+            _insideBodyErrorLogged = true;
+        }
+
+        return _insideBody;
+    }
+
+    private void startTapestryElement(String localName, Attributes attributes)
+    {
+        if (localName.equals("comp"))
+        {
+            startComponent(localName, attributes);
+            return;
+        }
+
+        if (localName.equals("body"))
+        {
+            startBody();
+            return;
+        }
+
+        throw new IllegalArgumentException("Unknown localName: '" + localName + \
"'."); +    }
+
+    private void startComponent(String localName, Attributes attributes)
+    {
+        String id = null;
+        String type = null;
+        String mixins = null;
+        int count = attributes.getLength();
+        Location location = getCurrentLocation();
+        List<TemplateToken> attributeTokens = newList();
+
+        for (int i = 0; i < count; i++)
+        {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            // TODO: Validate that the id is a reasonable string.
+
+            if (name.equals("id"))
+            {
+                if (InternalUtils.isNonBlank(value))
+                    id = value;
+                continue;
+            }
+
+            if (name.equals("type"))
+            {
+
+                if (InternalUtils.isNonBlank(value))
+                    type = value;
+                continue;
+            }
+
+            if (name.equals("mixins"))
+            {
+                if (InternalUtils.isNonBlank(value))
+                    mixins = value;
+                continue;
+            }
+
+            attributeTokens.add(new AttributeToken(name, value, location));
+        }
+
+        if (id == null && type == null)
+            throw new TapestryException(ServicesMessages.compRequiresIdOrType(), \
location, null); +
+        if (id != null)
+            _componentIds.add(id);
+
+        // Add the component
+        _tokens.add(new StartComponentToken(id, type, mixins, location));
+        _tokens.addAll(attributeTokens);
+    }
+
+    private void startBody()
+    {
+        _tokens.add(new BodyToken(getCurrentLocation()));
+
+        _insideBody = true;
+        _insideBodyErrorLogged = false;
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws \
SAXException +    {
+        processTextBuffer();
+
+        // TODO: Handle tapestry namespace elements?
+
+        // Because XML tags are always balanced, we don't even need to know what \
element just closed +        // when we assemble things later.
+
+        if (!_insideBody)
+            _tokens.add(new EndElementToken(getCurrentLocation()));
+
+        _insideBody = false;
+    }
+
+    private Location getCurrentLocation()
+    {
+        if (_locator == null)
+            return null;
+
+        return new LocationImpl(_templateResource, _locator.getLineNumber(), \
_locator +                .getColumnNumber());
+    }
+
+    public void comment(char[] ch, int start, int length) throws SAXException
+    {
+        if (_ignoreEvents || insideBody())
+            return;
+
+        processTextBuffer();
+
+        String comment = new String(ch, start, length);
+
+        // TODO: Perhaps comments need to be "aggregated" the same way we aggregate \
text and CDATA. +        // Hm. Probably not. Any whitespace between one comment and \
the next will become a +        // TextToken.
+        // Unless we trim whitespace between consecutive comments ... and on down \
the rabbit hole. +        // Oops -- unless a single comment may be passed into this \
method as multiple calls +        // (have to check how multiline comments are \
handled). +        // Tests against Sun's built in parser does show that multiline \
comments are still +        // provided as a single call to comment(), so we're good \
for the meantime (until we find +        // out some parsers aren't so compliant).
+
+        _tokens.add(new CommentToken(comment, getCurrentLocation()));
+    }
+
+    public void endCDATA() throws SAXException
+    {
+        // Add a token for any accumulated CDATA.
+
+        processTextBuffer();
+
+        // Again, CDATA doesn't nest, so we know we're back to ordinary markup.
+
+        _textIsCData = false;
+    }
+
+    public void endDTD() throws SAXException
+    {
+    }
+
+    public void endEntity(String name) throws SAXException
+    {
+    }
+
+    public void startCDATA() throws SAXException
+    {
+        if (_ignoreEvents || insideBody())
+            return;
+
+        processTextBuffer();
+
+        // Because CDATA doesn't mix with any other SAX/lexical events, we can \
simply turn on a flag +        // here and turn it off when we see the end.
+
+        _textIsCData = true;
+    }
+
+    public void startDTD(String name, String publicId, String systemId) throws \
SAXException +    {
+    }
+
+    public void startEntity(String name) throws SAXException
+    {
+
+    }
+
+    @Override
+    public void ignorableWhitespace(char[] ch, int start, int length) throws \
SAXException +    {
+    }
+
+}

Added: 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=auto&rev=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java \
                (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/MultiKey.java \
Wed Nov 29 14:02:40 2006 @@ -0,0 +1,83 @@
+// 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.util;
+
+import java.util.Arrays;
+
+/** A general purpose key for multiple values. */
+public final class MultiKey
+{
+    private static final int PRIME = 31;
+
+    private final Object[] _values;
+
+    private final int _hashCode;
+
+    /**
+     * 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() ). +     * 
+     * @param values
+     */
+    public MultiKey(Object... values)
+    {
+        _values = values;
+
+        _hashCode = PRIME * Arrays.hashCode(_values);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return _hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final MultiKey other = (MultiKey) obj;
+
+        return Arrays.equals(_values, other._values);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder("MultiKey[");
+
+        boolean first = true;
+
+        for (Object o : _values)
+        {
+            if (!first)
+                builder.append(", ");
+
+            builder.append(o);
+
+            first = false;
+        }
+
+        builder.append("]");
+
+        return builder.toString();
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/internal/util/URLChangeTracker.java?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java \
                (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java \
Wed Nov 29 14:02:40 2006 @@ -99,4 +99,15 @@
 
     }
 
+    /**
+     * Needed for testing; changes file timestamps so that a change will be detected \
by +     * {@link #containsChanges()}.
+     */
+    public void forceChange()
+    {
+        for (Map.Entry<URL, Long> e : _urlToTimestamp.entrySet())
+        {
+            e.setValue(0l);
+        }
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java \
/org/apache/tapestry/model/ComponentModel.java?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java \
                (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java \
Wed Nov 29 14:02:40 2006 @@ -12,98 +12,108 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.model;
-
-import java.util.List;
-
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.annotations.ComponentClass;
-import org.apache.tapestry.annotations.Persist;
-import org.apache.tapestry.annotations.SupportsInformalParameters;
+package org.apache.tapestry.model;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.annotations.SupportsInformalParameters;
 import org.apache.tapestry.ioc.Resource;
-
-/**
- * Defines a component in terms of its capabilities, parameters, sub-components, \
                etc. During
- * <em>runtime</em>, the component model is immutable. During <em>construction</em> \
                time, when
- * the class is being transformed and loaded, the model is mutable.
- * 
- * @see MutableComponentModel
- */
-public interface ComponentModel
-{
-    /**
-     * Returns the resource corresponding to the class file for this component. This \
                is used to find
-     * related resources, such as the component's template and message catalog.
-     */
-    Resource getBaseResource();
-
-    /** The FQCN of the component. */
-    String getComponentClassName();
-
-    /**
-     * Returns the ids of all embedded components defined within the component class \
                (via the
-     * {@link org.apache.tapestry.annotations.Component} annotation).
-     */
-    List<String> getEmbeddedComponentIds();
-
-    /**
-     * Returns an embedded component.
-     * 
-     * @param componentId
-     *            the id of the embedded component
-     * @return the embedded component model, or null if no component exists with \
                that id
-     */
-    EmbeddedComponentModel getEmbeddedComponentModel(String componentId);
-
-    /**
-     * Returns the persistent strategy associated with the field.
-     * 
-     * @param fieldName
-     * @return the corresponding strategy
-     * @throw IllegalArgumentException if the named field is not marked as \
                persistent
-     */
-    String getFieldPersistenceStrategy(String fieldName);
-
-    /** Returns object that will be used to log warnings and errors related to this \
                component. */
-    Log getLog();
-
-    /** Returns a list of the class names of mixins that are part of the component's \
                implementation. */
-    List<String> getMixinClassNames();
-
-    /** Return a single parameter model by parameter name, or null if the parameter \
                is not defined. */
-    ParameterModel getParameterModel(String parameterName);
-
-    /**
-     * Returns an alphabetically sorted list of the names of all formal parameters. \
                This includes
-     * parameters defined by a base class.
-     */
-
-    List<String> getParameterNames();
-
-    /**
-     * Returns a list of the names of all persistent fields (within this class, or \
                any super-class).
-     * The names are sorted alphabetically.
-     * 
-     * @see Persist
-     */
-    List<String> getPersistentFieldNames();
-
-    /**
-     * Returns true if the modeled component is a root class, a component class \
                whose parent does
-     * not have the {@link ComponentClass} annotation. This is often used to \
                determine whether to
-     * invoke the super-class implementation of certain methods.
-     * 
-     * @return true if a root class, false if a subclass
-     */
-    boolean isRootClass();
-
-    /**
-     * Returns true if the model indicates that informal parameters, additional \
                parameters beyond
-     * the formal parameter defined for the component, are supported. This is false \
                in most cases,
-     * but may be set to true for specific classes (when the {@link \
                SupportsInformalParameters}
-     * annotation is present, or inherited from a super-class).
-     * 
-     * @return true if this component model supports informal parameters
-     */
-    boolean getSupportsInformalParameters();
-}
+
+/**
+ * Defines a component in terms of its capabilities, parameters, sub-components, \
etc. During + * <em>runtime</em>, the component model is immutable. During \
<em>construction</em> time, when + * the class is being transformed and loaded, the \
model is mutable. + * 
+ * @see MutableComponentModel
+ */
+public interface ComponentModel
+{
+    /**
+     * Returns the resource corresponding to the class file for this component. This \
is used to find +     * related resources, such as the component's template and \
message catalog. +     */
+    Resource getBaseResource();
+
+    /** The FQCN of the component. */
+    String getComponentClassName();
+
+    /**
+     * Returns the ids of all embedded components defined within the component class \
(via the +     * {@link org.apache.tapestry.annotations.Component} annotation).
+     */
+    List<String> getEmbeddedComponentIds();
+
+    /**
+     * Returns an embedded component.
+     * 
+     * @param componentId
+     *            the id of the embedded component
+     * @return the embedded component model, or null if no component exists with \
that id +     */
+    EmbeddedComponentModel getEmbeddedComponentModel(String componentId);
+
+    /**
+     * Returns the persistent strategy associated with the field.
+     * 
+     * @param fieldName
+     * @return the corresponding strategy
+     * @throw IllegalArgumentException if the named field is not marked as \
persistent +     */
+    String getFieldPersistenceStrategy(String fieldName);
+
+    /** Returns object that will be used to log warnings and errors related to this \
component. */ +    Log getLog();
+
+    /** Returns a list of the class names of mixins that are part of the component's \
implementation. */ +    List<String> getMixinClassNames();
+
+    /** Return a single parameter model by parameter name, or null if the parameter \
is not defined. */ +    ParameterModel getParameterModel(String parameterName);
+
+    /**
+     * Returns an alphabetically sorted list of the names of all formal parameters. \
This includes +     * parameters defined by a base class.
+     */
+
+    List<String> getParameterNames();
+
+    /**
+     * Returns a list of the names of all persistent fields (within this class, or \
any super-class). +     * The names are sorted alphabetically.
+     * 
+     * @see Persist
+     */
+    List<String> getPersistentFieldNames();
+
+    /**
+     * Returns true if the modeled component is a root class, a component class \
whose parent does +     * not have the {@link ComponentClass} annotation. This is \
often used to determine whether to +     * invoke the super-class implementation of \
certain methods. +     * 
+     * @return true if a root class, false if a subclass
+     */
+    boolean isRootClass();
+
+    /**
+     * Returns true if the model indicates that informal parameters, additional \
parameters beyond +     * the formal parameter defined for the component, are \
supported. This is false in most cases, +     * but may be set to true for specific \
classes (when the {@link SupportsInformalParameters} +     * annotation is present, \
or inherited from a super-class). +     * 
+     * @return true if this component model supports informal parameters
+     */
+    boolean getSupportsInformalParameters();
+
+    /**
+     * Returns the component model for this component's super-class, if it exists. \
Remember that +     * only classes with the {@link ComponentClass} annotation, and in \
the correct packages, are +     * considered component classes.
+     * 
+     * @return the parent class model, or null if this component's super class is \
not itself a +     *         component class
+     */
+    ComponentModel getParentModel();
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentMessagesSource.java
                
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentMessagesSource.java?view=auto&rev=480728
 ==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentMessagesSource.java \
                (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentMessagesSource.java \
Wed Nov 29 14:02:40 2006 @@ -0,0 +1,36 @@
+// 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.internal.event.InvalidationEventHub;
+import org.apache.tapestry.ioc.Messages;
+import org.apache.tapestry.model.ComponentModel;
+
+public interface ComponentMessagesSource extends InvalidationEventHub
+{
+    /**
+     * Used to obtain a {@link Messages} instance for a particular component, within \
a particular +     * locale. If the component extends from another component, then \
its localized properties will +     * merge with its parent's properties (with the \
subclass overriding the super class on any +     * conflicts).
+     * 
+     * @param componentModel
+     * @param locale
+     * @return
+     */
+    Messages getMessages(ComponentModel componentModel, Locale locale);
+}

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=480728&r1=480727&r2=480728
 ==============================================================================
--- 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 \
Wed Nov 29 14:02:40 2006 @@ -38,8 +38,9 @@
 import org.apache.tapestry.MarkupWriter;
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.ioc.Location;
+import org.apache.tapestry.ioc.Resource;
 import org.apache.tapestry.ioc.ServiceLocator;
-import org.apache.tapestry.ioc.services.ThreadLocale;
+import org.apache.tapestry.ioc.internal.util.ClasspathResource;
 import org.apache.tapestry.ioc.test.IOCTestCase;
 import org.apache.tapestry.model.ComponentModel;
 import org.apache.tapestry.model.MutableComponentModel;
@@ -63,6 +64,9 @@
 public abstract class TapestryTestCase extends IOCTestCase
 {
 
+    protected final Resource _simpleComponentResource = new ClasspathResource(
+                "org/apache/tapestry/internal/services/SimpleComponent.class");
+
     protected final void train_findFieldsWithAnnotation(ClassTransformation \
                transformation,
             Class<? extends Annotation> annotationClass, String... fieldNames)
     {
@@ -382,10 +386,7 @@
         return newMock(WebRequestHandler.class);
     }
 
-    protected final ThreadLocale newThreadLocale()
-    {
-        return newMock(ThreadLocale.class);
-    }
+
 
     protected final void train_service(WebRequestHandler handler, WebRequest \
request, WebResponse response, boolean result) throws IOException  {
@@ -395,5 +396,15 @@
     protected final void train_getLocale(WebRequest request, Locale locale)
     {
         expect(request.getLocale()).andReturn(locale).atLeastOnce();
+    }
+
+    protected final void train_getParentModel(ComponentModel model, ComponentModel \
parentModel) +    {
+        expect(model.getParentModel()).andReturn(parentModel).atLeastOnce();
+    }
+
+    protected final void train_getBaseResource(ComponentModel model, Resource \
resource) +    {
+        expect(model.getBaseResource()).andReturn(resource).atLeastOnce();
     }
 }

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/reso \
urces/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=480728&r1=480727&r2=480728
 ==============================================================================
--- 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 \
Wed Nov 29 14:02:40 2006 @@ -58,4 +58,5 @@
 could-not-resolve-mixin-type=Unable to resolve mixin type '%s' to a component class \
name.  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. \ No newline at end of file
+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


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic