/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.pfl.dynamic.copyobject.impl;

import java.util.LinkedHashMap ;
import java.util.IdentityHashMap ;

import java.lang.reflect.Field ;
import java.lang.reflect.Method ;
import java.lang.reflect.Constructor ;

import java.security.AccessControlContext ;

import org.glassfish.pfl.dynamic.copyobject.spi.ReflectiveCopyException ;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Properties;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger ;
import java.util.logging.LogManager ;

A factory used for creating ClassCopier instances. An instance of this factory can be created and customized to handle special copying semantics for certain classes. This maintains a cache of ClassCopiers, so that a ClassCopier is never created more than once for a particular class.
/** A factory used for creating ClassCopier instances. * An instance of this factory can be created and customized to * handle special copying semantics for certain classes. * This maintains a cache of ClassCopiers, so that a ClassCopier is * never created more than once for a particular class. */
public class ClassCopierFactoryPipelineImpl implements PipelineClassCopierFactory { // Note that we reflectively copy many transient fields, // because otherwise we could not copy quite a few // important classes. For example, we copy Map classes // reflectively in the normal case this way. However, // this can be a rather risky procedure: some classes // should never be copied this way. // // Putting these sorts of fields in a Serializable class // should normally be avoided, and so we won't try // to optimize these cases, but we do want to avoid // problems. Consequently we will allow the code // to fallback to a stream copier, which may // take advantage of readObject/writeObject to // deal with some of these cases. // notCopyable contains some common classes that should // never be copied reflectively. This is an optimization, // as these would also be picked up in DefaultClassCopierFactory // in the internal notCopyable method. private static final Class<?>[] notCopyable = new Class<?>[] { Thread.class, ThreadGroup.class, ProcessBuilder.class, } ; // List of some immutable classes that are copied simply // with the identity copier. private static final Class<?>[] immutable = new Class<?>[] { Process.class, Class.class, ClassLoader.class, SecurityManager.class, Runtime.class, System.class, Package.class, Field.class, Method.class, Constructor.class, AccessControlContext.class, Object.class, String.class, Byte.class, Character.class, Short.class, Integer.class, Long.class, Double.class, Float.class, Boolean.class, Logger.class, LogManager.class } ; private static final Class<?>[] mapClasses = { // ConcurrentHashMap.class, // ConcurrentSkipListMap.class, // EnumMap.class, // HashMap.class, // Hashtable.class, IdentityHashMap.class, LinkedHashMap.class, // Properties.class, // TreeMap.class, // WeakHashMap.class } ; private CachingClassCopierFactory factoryCache ; private ClassCopierFactory specialFactory ; private ClassCopierFactory arrayFactory ; private ClassCopierFactory ordinaryFactory ; private ClassCopier errorCopier ; public ClassCopierFactoryPipelineImpl() { // Set up internal ClassCopierFactory instances factoryCache = DefaultClassCopierFactories.makeCachingClassCopierFactory() ; specialFactory = DefaultClassCopierFactories.getNullClassCopierFactory() ; arrayFactory = DefaultClassCopierFactories.makeArrayClassCopierFactory( this ) ; ordinaryFactory = DefaultClassCopierFactories.makeOrdinaryClassCopierFactory( this ) ; errorCopier = DefaultClassCopiers.getErrorClassCopier() ; // Register Immutables for (Class<?> cls : immutable) { registerImmutable(cls); } ClassCopier mapCopier = DefaultClassCopiers.makeMapClassCopier( this ) ; // Note that the identity hash map can never be copied by reflection, // due to the identity equality semantics required by NULL_KEY. // This also means that no subclass can ever be copied by // reflection. // // Another problem is that Linked classes (like LinkedHashMap) can // cause stack overflow if analyzed reflectively (issue 13996), // so make sure that LinkedHashMap is copied this way as well. for (Class<?> cls : mapClasses) { factoryCache.put( cls, mapCopier ) ; } // Make sure that all non-copyable classes have the error // copier in the cache. for (Class<?> cls : notCopyable) { factoryCache.put(cls, errorCopier); } } @Override public boolean reflectivelyCopyable( Class<?> cls ) { for (Class<?> cl : notCopyable) { if (cls == cl) { return false; } } return true ; }
Look for cls only in the cache; do not create a ClassCopier if there isn't one already in the cache.
/** Look for cls only in the cache; do not create a ClassCopier * if there isn't one already in the cache. */
@Override public ClassCopier lookupInCache( Class<?> cls ) { try { // TIME enter_lookupInCache return factoryCache.getClassCopier( cls ) ; // TIME exit_lookupInCache } catch (ReflectiveCopyException exc) { // ignore this: it cannot occur on the cache class copier get. return null ; } }
Register an immutable class, so that it will not be copied, but just passed by reference.
/** Register an immutable class, so that it will not be copied, but just * passed by reference. */
@Override public synchronized final void registerImmutable( Class<?> cls ) { factoryCache.put( cls, DefaultClassCopiers.getIdentityClassCopier() ) ; }
Set a special ClassCopierFactory to handle some application specific needs.
/** Set a special ClassCopierFactory to handle some application specific * needs. */
@Override public void setSpecialClassCopierFactory( ClassCopierFactory ccf ) { specialFactory = ccf ; } // Issue 14455: introduce read/write lock to reduce contention // on getClassCopier. private ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock() ;
Analyze cls to determine the appropriate ClassCopier and return the ClassCopier instance. Will only create a ClassCopier for a given Class once.
/** Analyze cls to determine the appropriate ClassCopier * and return the ClassCopier instance. Will only create * a ClassCopier for a given Class once. */
@Override public ClassCopier getClassCopier( // TIME enter_getClassCopier Class<?> cls ) throws ReflectiveCopyException { if (cls.isInterface()) { throw Exceptions.self.cannotCopyInterface( cls ) ; } rwlock.readLock().lock() ; boolean readLocked = true ; try { ClassCopier result = factoryCache.getClassCopier( cls ) ; if (result == null) { // New for Java SE 5.0: all Enums are immutable. // We'll figure that out here and cache the result. if (Enum.class.isAssignableFrom(cls)) { result = DefaultClassCopiers.getIdentityClassCopier(); } if (result == null) { result = specialFactory.getClassCopier(cls); } if (result == null) { result = arrayFactory.getClassCopier(cls); } if (result == null) { result = ordinaryFactory.getClassCopier(cls); } if (result == null) { throw Exceptions.self.couldNotFindClassCopier( cls ) ; } // Result was not cached, so update the cache rwlock.readLock().unlock() ; readLocked = false ; rwlock.writeLock().lock() ; try { if (factoryCache.getClassCopier(cls) == null) { factoryCache.put( cls, result ) ; } } finally { rwlock.writeLock().unlock() ; } } if (result == errorCopier) { throw Exceptions.self.cannotCopyClass( cls ) ; } // TIME exit_getClassCopier return result ; } finally { if (readLocked) { rwlock.readLock().unlock() ; } } } }