content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.aries.proxy.impl.gen;
  
  import static java.lang.reflect.Modifier.isFinal;
  
  import java.io.IOException;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.security.ProtectionDomain;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.WeakHashMap;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentMap;
  
  import org.apache.aries.proxy.FinalModifierException;
  import org.apache.aries.proxy.UnableToProxyException;
  import org.objectweb.asm.ClassReader;
  import org.objectweb.asm.ClassVisitor;
  import org.objectweb.asm.ClassWriter;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import sun.reflect.ReflectionFactory;
  
  @SuppressWarnings("restriction")
  public class ProxySubclassGenerator
  {
  
    private final static Logger LOGGER = LoggerFactory.getLogger(ProxySubclassGenerator.class);
  
    // This map holds references to the names of classes created by this Class
    // It is a weak map (so when a ClassLoader is garbage collected we remove
    // the map of
    // Class names to sub-Class names)
    private static final Map<ClassLoader, ConcurrentMap<String, String>> proxyClassesByClassLoader;
    
    private static final ClassLoader defaultClassLoader = new ClassLoader() {};
  
    static {
      // Ensure that this is a synchronized map as we may use it from multiple
      // threads concurrently
      //
      proxyClassesByClassLoader = Collections
          .synchronizedMap(new WeakHashMap<ClassLoader, ConcurrentMap<String, String>>());
    }
  
    private static final char FINAL_MODIFIER = '!';
    private static final char UNABLE_TO_PROXY = '#';
  
    public static Class<?> getProxySubclass(Class<?> aClass) throws UnableToProxyException
    {
      return getProxySubclass(aClass, aClass.getClassLoader());
    }
  
    public static Class<?> getProxySubclass(Class<?> aClass, ClassLoader loader) throws UnableToProxyException
    {
      LOGGER.debug(Constants.LOG_ENTRY, "getProxySubclass", new Object[] { aClass });
  
      // in the special case where the loader is null we use a default classloader
      // this is for subclassing java.* or javax.* packages, so that one will do
      if (loader == null) loader = defaultClassLoader;
  
      ConcurrentMap<String, String> proxyMap;
      synchronized (loader) {
        proxyMap = proxyClassesByClassLoader.get(loader);
        if (proxyMap == null) {
          proxyMap = new ConcurrentHashMap<String, String>();
          proxyClassesByClassLoader.put(loader, proxyMap);
        }
      }
  
      // check the map to see if we have already generated a subclass for this
      // class
      // if we have return the mapped class object
      // if we haven't generate the subclass and return it
      Class<?> classToReturn = null;
      synchronized (aClass) {
        String key = aClass.getName();
        String className = proxyMap.get(key);
        if (className != null) {
  
          LOGGER.debug("Found proxy subclass with key {} and name {}.", key, className);
  
          if (className.charAt(0) == FINAL_MODIFIER) {
            String[] exceptionParts = className.substring(1).split(":");
            if (exceptionParts.length == 1) {
              throw new FinalModifierException(aClass);
            } else {
              throw new FinalModifierException(aClass, exceptionParts[1]);
            }
          } else if (className.charAt(0) == UNABLE_TO_PROXY) {
            throw new UnableToProxyException(aClass);
          }
  
          try {
            classToReturn = loader.loadClass(className);
          } catch (ClassNotFoundException cnfe) {
            LOGGER.debug(Constants.LOG_EXCEPTION, cnfe);
            throw new UnableToLoadProxyException(className, cnfe);
          }
        } else {
  
          LOGGER.debug("Need to generate subclass. Using key {}.", key);
          try {
            scanForFinalModifiers(aClass);
  
            classToReturn = generateAndLoadSubclass(aClass, loader);
  
            if (classToReturn != null) {
              proxyMap.put(key, classToReturn.getName());
            } else {
              proxyMap.put(key, UNABLE_TO_PROXY + aClass.getName());
              throw new UnableToProxyException(aClass);
            }
          } catch (FinalModifierException e) {
            if (e.isFinalClass()) {
              proxyMap.put(key, FINAL_MODIFIER + e.getClassName());
              throw e;
            } else {
              proxyMap.put(key, FINAL_MODIFIER + e.getClassName() + ':' + e.getFinalMethods());
              throw e;
            }
          }
  
        }
      }
  
      LOGGER.debug(Constants.LOG_EXIT, "getProxySubclass", classToReturn);
  
      return classToReturn;
    }
  
    public static Object newProxySubclassInstance(Class<?> classToProxy, InvocationHandler ih)
        throws UnableToProxyException
    {
      return newProxySubclassInstance(classToProxy, classToProxy.getClassLoader(), ih);
    }
  
    public static Object newProxySubclassInstance(Class<?> classToProxy, ClassLoader loader, InvocationHandler ih)
        throws UnableToProxyException
    {
  
      LOGGER.debug(Constants.LOG_ENTRY, "newProxySubclassInstance", new Object[] {
          classToProxy, loader, ih });
  
      Object proxySubclassInstance = null;
      try {
        Class<?> generatedProxySubclass = getProxySubclass(classToProxy, loader);
        LOGGER.debug("Getting the proxy subclass constructor");
        // Because the newer JVMs throw a VerifyError if a class attempts to in a constructor other than their superclasses constructor,
        // and because we can't know what objects/values we need to pass into the class being proxied constructor, 
        // we instantiate the proxy class using the ReflectionFactory.newConstructorForSerialization() method which allows us to instantiate the 
        // proxy class without calling the proxy class' constructor. It is in fact using the java.lang.Object constructor so is in effect 
        // doing what we were doing before.
        ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
        Constructor<?> constr = Object.class.getConstructor();
        Constructor<?> subclassConstructor = factory.newConstructorForSerialization(generatedProxySubclass, constr);
        proxySubclassInstance = subclassConstructor.newInstance();
        
        Method setIHMethod = proxySubclassInstance.getClass().getMethod("setInvocationHandler", InvocationHandler.class);
        setIHMethod.invoke(proxySubclassInstance, ih);
        LOGGER.debug("Invoked proxy subclass constructor");
      } catch (NoSuchMethodException nsme) {
        LOGGER.debug(Constants.LOG_EXCEPTION, nsme);
        throw new ProxyClassInstantiationException(classToProxy, nsme);
      } catch (InvocationTargetException ite) {
        LOGGER.debug(Constants.LOG_EXCEPTION, ite);
        throw new ProxyClassInstantiationException(classToProxy, ite);
      } catch (InstantiationException ie) {
        LOGGER.debug(Constants.LOG_EXCEPTION, ie);
        throw new ProxyClassInstantiationException(classToProxy, ie);
      } catch (IllegalAccessException iae) {
        LOGGER.debug(Constants.LOG_EXCEPTION, iae);
        throw new ProxyClassInstantiationException(classToProxy, iae);
      } catch (VerifyError ve) {
          LOGGER.info(String.format("The no-argument constructor of class %s is private and therefore it may not be possible to generate a valid proxy.", 
                                    classToProxy));
          LOGGER.debug(Constants.LOG_EXCEPTION, ve);
          throw new ProxyClassInstantiationException(classToProxy, ve);
      }
  
      LOGGER.debug(Constants.LOG_EXIT, "newProxySubclassInstance", proxySubclassInstance);
  
      return proxySubclassInstance;
    }
  
    private static Class<?> generateAndLoadSubclass(Class<?> aClass, ClassLoader loader)
        throws UnableToProxyException
    {
      LOGGER.debug(Constants.LOG_ENTRY, "generateAndLoadSubclass", new Object[] { aClass,
          loader });
  
      // set the newClassName
      String newClassName = "$" + aClass.getSimpleName() + aClass.hashCode();
      String packageName = aClass.getPackage().getName();
      if (packageName.startsWith("java.") || packageName.startsWith("javax.")) {
        packageName = "org.apache.aries.blueprint.proxy." + packageName;
      }
      String fullNewClassName = (packageName + "." + newClassName).replaceAll("\\.", "/");
  
      LOGGER.debug("New class name: {}", newClassName);
      LOGGER.debug("Full new class name: {}", fullNewClassName);
  
      Class<?> clazz = null;
      try {
        ClassReader cReader = new ClassReader(loader.getResourceAsStream(aClass.getName().replaceAll(
            "\\.", "/")
            + ".class"));
        ClassWriter cWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor dynamicSubclassAdapter = new ProxySubclassAdapter(cWriter, fullNewClassName,
            loader);
        byte[] byteClassData = processClass(cReader, cWriter, dynamicSubclassAdapter);
        clazz = loadClassFromBytes(loader, getBinaryName(fullNewClassName), byteClassData, aClass
            .getName());
      } catch (IOException ioe) {
        LOGGER.debug(Constants.LOG_EXCEPTION, ioe);
        throw new ProxyClassBytecodeGenerationException(aClass.getName(), ioe);
      } catch (TypeNotPresentException tnpe) {
        LOGGER.debug(Constants.LOG_EXCEPTION, tnpe);
        throw new ProxyClassBytecodeGenerationException(tnpe.typeName(), tnpe.getCause());
      }
  
      LOGGER.debug(Constants.LOG_EXIT, "generateAndLoadSubclass", clazz);
  
      return clazz;
    }
  
    private static byte[] processClass(ClassReader cReader, ClassWriter cWriter, ClassVisitor cVisitor)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "processClass", new Object[] { cReader, cWriter,
          cVisitor });
  
      cReader.accept(cVisitor, ClassReader.SKIP_DEBUG);
      byte[] byteClassData = cWriter.toByteArray();
  
      LOGGER.debug(Constants.LOG_EXIT, "processClass", byteClassData);
  
      return byteClassData;
    }
  
    private static String getBinaryName(String name)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "getBinaryName", name);
  
      String binaryName = name.replaceAll("/", "\\.");
  
      LOGGER.debug(Constants.LOG_EXIT, "getBinaryName", binaryName);
  
      return binaryName;
    }
  
    private static Class<?> loadClassFromBytes(ClassLoader loader, String name, byte[] classData,
        String classToProxyName) throws UnableToProxyException
    {
      LOGGER.debug(Constants.LOG_ENTRY, "loadClassFromBytes", new Object[] { loader, name,
          classData });
  
      Class<?> clazz = null;
      try {
        Method defineClassMethod = Class.forName("java.lang.ClassLoader").getDeclaredMethod(
            "defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class);
        defineClassMethod.setAccessible(true);
        // define the class in the same classloader where aClass is loaded,
        // but use the protection domain of our code
        clazz = (Class<?>) defineClassMethod.invoke(loader, name, classData, 0, classData.length,
            ProxySubclassGenerator.class.getProtectionDomain());
        defineClassMethod.setAccessible(false);
      } catch (ClassNotFoundException cnfe) {
        LOGGER.debug(Constants.LOG_EXCEPTION, cnfe);
        throw new ProxyClassDefinitionException(classToProxyName, cnfe);
      } catch (NoSuchMethodException nsme) {
        LOGGER.debug(Constants.LOG_EXCEPTION, nsme);
        throw new ProxyClassDefinitionException(classToProxyName, nsme);
      } catch (InvocationTargetException ite) {
        LOGGER.debug(Constants.LOG_EXCEPTION, ite);
        throw new ProxyClassDefinitionException(classToProxyName, ite);
      } catch (IllegalAccessException iae) {
        LOGGER.debug(Constants.LOG_EXCEPTION, iae);
        throw new ProxyClassDefinitionException(classToProxyName, iae);
      }
  
      LOGGER.debug(Constants.LOG_EXIT, "loadClassFromBytes", clazz);
  
      return clazz;
    }
  
    public static boolean isProxySubclass(Class<?> aClass)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "isProxySubclass", new Object[] { aClass });
  
      // We will always have a proxy map for the class loader of any proxy
      // class, so if
      // this is null we know to return false
      Map<String, String> proxies = proxyClassesByClassLoader.get(aClass.getClassLoader());
  
      boolean isProxySubclass = (proxies != null && proxies.containsValue(aClass.getName()));
  
      LOGGER.debug(Constants.LOG_EXIT, "isProxySubclass", isProxySubclass);
  
      return isProxySubclass;
    }
  
    private static void scanForFinalModifiers(Class<?> clazz) throws FinalModifierException
    {
      LOGGER.debug(Constants.LOG_ENTRY, "scanForFinalModifiers", new Object[] { clazz });
  
      if (isFinal(clazz.getModifiers())) {
        throw new FinalModifierException(clazz);
      }
  
      List<String> finalMethods = new ArrayList<String>();
  
      // we don't want to check for final methods on java.* or javax.* Class
      // also, clazz can never be null here (we will always hit
      // java.lang.Object first)
      while (!clazz.getName().startsWith("java.") && !clazz.getName().startsWith("javax.")) {
        for (Method m : clazz.getDeclaredMethods()) {
          //Static finals are ok, because we won't be overriding them :)
          if (isFinal(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
            finalMethods.add(m.toGenericString());
          }
        }
        clazz = clazz.getSuperclass();
      }
  
      if (!finalMethods.isEmpty()) {
  
        String methodList = finalMethods.toString();
        methodList = methodList.substring(1, methodList.length() - 1);
        throw new FinalModifierException(clazz, methodList);
      }
  
      LOGGER.debug(Constants.LOG_EXIT, "scanForFinalModifiers");
  
    }
  
    public static InvocationHandler getInvocationHandler(Object o)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "getInvoationHandler", new Object[] { o });
  
      InvocationHandler ih = null;
      if (isProxySubclass(o.getClass())) {
        // we have to catch exceptions here, but we just log them
        // the reason for this is that it should be impossible to get these
        // exceptions
        // since the Object we are dealing with is a class we generated on
        // the fly
        try {
          ih = (InvocationHandler) o.getClass().getDeclaredMethod("getInvocationHandler",
              new Class[] {}).invoke(o, new Object[] {});
        } catch (IllegalArgumentException e) {
          LOGGER.debug(Constants.LOG_EXCEPTION, e);
        } catch (SecurityException e) {
          LOGGER.debug(Constants.LOG_EXCEPTION, e);
        } catch (IllegalAccessException e) {
          LOGGER.debug(Constants.LOG_EXCEPTION, e);
        } catch (InvocationTargetException e) {
          LOGGER.debug(Constants.LOG_EXCEPTION, e);
        } catch (NoSuchMethodException e) {
          LOGGER.debug(Constants.LOG_EXCEPTION, e);
        }
      }
      LOGGER.debug(Constants.LOG_EXIT, "getInvoationHandler", ih);
      return ih;
    }
  
  }
  