/*
* Copyright 2011 SpringSource
*
* 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 grails.build.support;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaClassRegistryChangeEvent;
import groovy.lang.MetaClassRegistryChangeEventListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
/**
* Allows clean-up of changes made to the MetaClassRegistry.
*
* @author Graeme Rocher
* @since 2.0
*/
@SuppressWarnings("rawtypes")
public class MetaClassRegistryCleaner implements MetaClassRegistryChangeEventListener {
private Map<Class, Object> alteredClasses = new ConcurrentHashMap<Class, Object> ();
private Map<IdentityWeakReference, Object> alteredInstances = new ConcurrentHashMap<IdentityWeakReference, Object>();
private static final Object NO_CUSTOM_METACLASS = new Object();
private static boolean cleaning;
private static final MetaClassRegistryCleaner INSTANCE = new MetaClassRegistryCleaner();
private MetaClassRegistryCleaner() {
}
public static MetaClassRegistryCleaner createAndRegister() {
MetaClassRegistry metaClassRegistry = GroovySystem.getMetaClassRegistry();
MetaClassRegistryChangeEventListener[] listeners = metaClassRegistry.getMetaClassRegistryChangeEventListeners();
boolean registered = false;
for (MetaClassRegistryChangeEventListener listener : listeners) {
if (listener == INSTANCE) {
registered = true;break;
}
}
if (!registered) {
GroovySystem.getMetaClassRegistry().addMetaClassRegistryChangeEventListener(INSTANCE);
}
return INSTANCE;
}
public static void cleanAndRemove(MetaClassRegistryCleaner cleaner) {
cleaner.clean();
GroovySystem.getMetaClassRegistry().removeMetaClassRegistryChangeEventListener(cleaner);
}
public static void addAlteredMetaClass(Class cls, MetaClass altered) {
INSTANCE.alteredClasses.put(cls, altered);
}
public void updateConstantMetaClass(MetaClassRegistryChangeEvent cmcu) {
if (!cleaning) {
MetaClass oldMetaClass = cmcu.getOldMetaClass();
Class classToUpdate = cmcu.getClassToUpdate();
Object instanceToUpdate = cmcu.getInstance();
if (instanceToUpdate == null && (cmcu.getNewMetaClass() instanceof ExpandoMetaClass)) {
updateMetaClassOfClass(oldMetaClass, classToUpdate);
} else if (instanceToUpdate != null) {
updateMetaClassOfInstance(oldMetaClass, instanceToUpdate);
}
}
}
private void updateMetaClassOfInstance(MetaClass oldMetaClass, Object instanceToUpdate) {
IdentityWeakReference key = new IdentityWeakReference(instanceToUpdate);
if (oldMetaClass != null) {
Object current = alteredInstances.get(key);
if (current == null || current == NO_CUSTOM_METACLASS) {
alteredInstances.put(key, oldMetaClass);
}
} else {
alteredInstances.put(key, NO_CUSTOM_METACLASS);
}
}
private void updateMetaClassOfClass(MetaClass oldMetaClass, Class classToUpdate) {
if (oldMetaClass != null && !(oldMetaClass.getClass().getName().equals("groovy.mock.interceptor.MockProxyMetaClass"))) {
Object current = alteredClasses.get(classToUpdate);
if (current == null ) {
alteredClasses.put(classToUpdate, oldMetaClass);
}
}
else {
alteredClasses.put(classToUpdate, NO_CUSTOM_METACLASS);
}
}
public synchronized void clean() {
try {
cleaning = true;
MetaClassRegistryImpl registry = (MetaClassRegistryImpl) GroovySystem.getMetaClassRegistry();
cleanMetaClassOfClass(registry);
cleanMetaClassOfInstance(registry);
} finally {
cleaning = false;
}
}
private void cleanMetaClassOfInstance(MetaClassRegistryImpl registry) {
List<IdentityWeakReference> keys = new ArrayList<IdentityWeakReference>(alteredInstances.keySet());
for (IdentityWeakReference key : keys) {
Object instance = key.get();
if (instance != null) {
Object alteredMetaClass = alteredInstances.get(key);
if (alteredMetaClass == NO_CUSTOM_METACLASS) {
alteredMetaClass = null;
}
registry.setMetaClass(instance, (MetaClass) alteredMetaClass);
}
}
alteredInstances.clear();
}
private void cleanMetaClassOfClass(MetaClassRegistryImpl registry) {
Set<Class> classes = new HashSet<Class>(alteredClasses.keySet());
for (Class aClass : classes) {
Object alteredMetaClass = alteredClasses.get(aClass);
if (alteredMetaClass == NO_CUSTOM_METACLASS) {
registry.removeMetaClass(aClass);
}
else {
registry.setMetaClass(aClass, (MetaClass) alteredMetaClass);
}
}
alteredClasses.clear();
}
private static final class IdentityWeakReference extends WeakReference<Object> {
private int hash;
public IdentityWeakReference(Object referent) {
super(referent);
hash = System.identityHashCode(referent);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return get() == ((IdentityWeakReference)obj).get();
}
}
}