blob: aba91b20d7b95f7c4169149c71dbdbfba4ff2017 [file] [log] [blame]
// Copyright 2008, 2009, 2010 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.ioc.internal.services;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.apache.tapestry5.ioc.MethodAdvice;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.OneShotLock;
import org.apache.tapestry5.ioc.services.AspectInterceptorBuilder;
import org.apache.tapestry5.ioc.services.ClassFab;
import org.apache.tapestry5.ioc.services.ClassFabUtils;
import org.apache.tapestry5.ioc.services.ClassFactory;
import org.apache.tapestry5.ioc.services.MethodSignature;
@SuppressWarnings("all")
public class AspectInterceptorBuilderImpl<T> implements AspectInterceptorBuilder<T>
{
private final ClassFactory classFactory;
private final Class<T> serviceInterface;
private final ClassFab interceptorFab;
private final ConstantInjectorImpl injector;
private final String delegateFieldName;
private final String description;
private final OneShotLock lock = new OneShotLock();
private final Set<Method> remainingMethods = CollectionFactory.newSet();
private final Map<Method, AdvisedMethodInvocationBuilder> methodToBuilder = CollectionFactory.newMap();
/**
* Set to true if we ever see toString() as a method of the interface; either advised or pass thru. If false at the
* end, we add our own implementation.
*/
private boolean sawToString;
public AspectInterceptorBuilderImpl(ClassFactory classFactory, Class<T> serviceInterface, T delegate,
String description)
{
this.classFactory = classFactory;
this.serviceInterface = serviceInterface;
this.description = description;
interceptorFab = this.classFactory.newClass(serviceInterface);
injector = new ConstantInjectorImpl(interceptorFab);
delegateFieldName = injector.inject(serviceInterface, delegate);
remainingMethods.addAll(Arrays.asList(serviceInterface.getMethods()));
}
public void adviseMethod(Method method, MethodAdvice advice)
{
assert method != null;
assert advice != null;
lock.check();
AdvisedMethodInvocationBuilder builder = methodToBuilder.get(method);
if (builder == null)
{
if (!remainingMethods.contains(method))
throw new IllegalArgumentException(String.format("Method %s is not defined for interface %s.", method,
serviceInterface));
// One less method to pass thru to the delegate
remainingMethods.remove(method);
sawToString |= ClassFabUtils.isToString(method);
builder = new AdvisedMethodInvocationBuilder(classFactory, serviceInterface, method);
methodToBuilder.put(method, builder);
}
builder.addAdvice(advice);
}
public void adviseAllMethods(MethodAdvice advice)
{
for (Method m : serviceInterface.getMethods())
adviseMethod(m, advice);
}
public Class getInterface()
{
return serviceInterface;
}
public T build()
{
lock.lock();
// Finish up each method that has been advised
for (AdvisedMethodInvocationBuilder builder : methodToBuilder.values())
{
builder.commit(interceptorFab, delegateFieldName, injector);
}
// Hit all the methods that haven't been referenced so far.
addPassthruMethods();
// And if we haven't seen a toString(), we can add it now.
if (!sawToString)
interceptorFab.addToString(description);
injector.implementConstructor();
Class interceptorClass = interceptorFab.createClass();
Object[] parameters = injector.getParameters();
try
{
Constructor constructor = interceptorClass.getConstructors()[0];
Object raw = constructor.newInstance(parameters);
return serviceInterface.cast(raw);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
private void addPassthruMethods()
{
for (Method m : remainingMethods)
{
sawToString |= ClassFabUtils.isToString(m);
MethodSignature sig = new MethodSignature(m);
String body = String.format("return ($r) %s.%s($$);", delegateFieldName, m.getName());
interceptorFab.addMethod(Modifier.PUBLIC, sig, body);
}
}
}