// Copyright 2011 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.tapestry5.internal.plastic;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.apache.tapestry5.plastic.ClassInstantiator;
import org.apache.tapestry5.plastic.InstanceContext;

@SuppressWarnings("all")
public class ClassInstantiatorImpl<T> implements ClassInstantiator<T>, InstanceContext
{
    private final Class clazz;

    private final Constructor<T> ctor;

    private final StaticContext staticContext;

    // Keeping a whole HashMap around for just one or two values feels wasteful, perhaps
    // come up with something else later.

    private final Map instanceContextMap;

    ClassInstantiatorImpl(Class<T> clazz, Constructor ctor, StaticContext staticContext)
    {
        this(clazz, ctor, staticContext, null);
    }

    private ClassInstantiatorImpl(Class clazz, Constructor ctor, StaticContext staticContext, Map instanceContextMap)
    {
        this.clazz = clazz;
        this.ctor = ctor;
        this.staticContext = staticContext;
        this.instanceContextMap = instanceContextMap;
    }

    public <V> ClassInstantiator<T> with(Class<V> valueType, V instanceContextValue)
    {
        assert valueType != null;
        assert instanceContextValue != null;

        Object existing = getFromMap(valueType);

        if (existing != null)
            throw new IllegalStateException(String.format(
                    "An instance context value of type %s has already been added.", valueType.getName()));

        Map newMap = instanceContextMap == null ? new HashMap() : new HashMap(instanceContextMap);

        newMap.put(valueType, instanceContextValue);

        return new ClassInstantiatorImpl(clazz, ctor, staticContext, newMap);
    }

    public <T> T get(Class<T> valueType)
    {
        T result = getFromMap(valueType);

        if (result == null)
            throw new IllegalArgumentException(String.format(
                    "Instance context for class %s does not contain a value for type %s.", clazz.getName(), valueType));

        return result;
    }

    private <T> T getFromMap(Class<T> valueType)
    {
        return instanceContextMap == null ? null : valueType.cast(instanceContextMap.get(valueType));
    }

    public T newInstance()
    {
        if (Modifier.isAbstract(clazz.getModifiers()))
            throw new IllegalStateException(String.format("Class %s is abstract and can not be instantiated.",
                    clazz.getName()));

        try
        {
            return ctor.newInstance(staticContext, this);
        }
        catch (Throwable ex)
        {
            throw new RuntimeException(String.format("Unable to instantiate instance of transformed class %s: %s",
                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
        }
    }

    public Class<T> getInstanceType()
    {
        return clazz;
    }

    public String toString()
    {
        return String.format("ClassInstantiator[%s]", clazz.getName());
    }
}

@@ -1118,176 +1118,125 @@
     
-// Keeping a whole HashMap around for just one or two values feels wasteful, perhaps%0A    // come up with something else later.%0A%0A    private final Map instanceContextMap
+private final ClassInstantiatorImpl%3CT%3E parent;%0A%0A    private final Class withType;%0A%0A    private final Object withValue
 ;%0A%0A 
@@ -1369,16 +1369,28 @@
 Context,
+ null, null,
  null);%0A
@@ -1407,16 +1407,20 @@
  private
+ %3CW%3E
  ClassIn
@@ -1497,31 +1497,84 @@
 ext,
- Map instanceContextMap
+%0A            ClassInstantiatorImpl%3CT%3E parent, Class%3CW%3E withType, W withValue
 )%0A  
@@ -1692,47 +1692,93 @@
 his.
-instanceContextMap = instanceContextMap
+parent = parent;%0A        this.withType = withType;%0A        this.withValue = withValue
 ;%0A  
@@ -1979,26 +1979,20 @@
 sting = 
-getFromMap
+find
 (valueTy
@@ -2206,229 +2206,270 @@
     
-Map newMap = instanceContextMap == null ? new HashMap() : new HashMap(instanceContextMap);%0A%0A        newMap.put(valueType, instanceContextValue);%0A%0A        return new ClassInstantiatorImpl(clazz, ctor, staticContext, newMap
+// A little optimization: the new CI doesn't need this CI as a parent, if this CI has no type/value pair%0A        %0A        return new ClassInstantiatorImpl(clazz, ctor, staticContext, withType == null ? null : this, valueType,%0A                instanceContextValue
 );%0A 
@@ -2486,20 +2486,20 @@
 public %3C
-T%3E T
+V%3E V
  get(Cla
@@ -2497,25 +2497,25 @@
 V get(Class%3C
-T
+V
 %3E valueType)
@@ -2529,17 +2529,17 @@
         
-T
+V
  result 
@@ -2540,26 +2540,20 @@
 esult = 
-getFromMap
+find
 (valueTy
@@ -2817,31 +2817,25 @@
 te %3C
-T%3E T getFromMap
+V%3E V find
 (Class%3C
-T
+V
 %3E va
@@ -2861,100 +2861,245 @@
     
-return instanceContextMap == null ? null : valueType.cast(instanceContextMap.get(valueType))
+ClassInstantiatorImpl cursor = this;%0A%0A        while (cursor != null)%0A        %7B%0A            if (cursor.withType == valueType) %7B return valueType.cast(cursor.withValue); %7D%0A%0A            cursor = cursor.parent;%0A        %7D%0A%0A        return null
 ;%0A  

Optimize the ClassInstantiatorImpl to not use a Map to store the instance context type/value pairs (since there's rarely more than one or two of them)