/*
* Copyright 2006 the original author or authors.
*
* 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.springmodules.xt.model.introductor.collections;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.springmodules.xt.model.introductor.DynamicIntroductor;
import org.apache.commons.collections.map.ReferenceIdentityMap;
/**
* {@link java.util.Set} decorator, which makes all the contained objects implementing a given array of interfaces, leaving
* the original target set unmodified.<br>
* This builds on top of {@link DynamicIntroductor} implementations.<br>
* <b>Important</b>: Changes to this decorated collection are reflected in the original set, and vice-versa.
*
* @author Sergio Bossa
*/
public class IntroductorSet extends AbstractSet {
private Set target;
private Class[] introducedInterfaces;
private Class[] targetObjectsInterfaces;
private DynamicIntroductor introductor;
/**
* Caching original target objects is necessary because when we introduce an object, the next time it is asked for, we MUST retrieve
* the introduced one, in order to preserve introduced values.
* This map contains weak references because entries relative to cached values must be automatically garbage collected when no more strongly referenced,
* and it is an identity map because keys and values must be compared by address identity, not by equals() method, because this could lead
* to consider an object already introduced while it could be not.
*/
private Map cachedValues = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD);
/**/
/**
* Construct the decorated introductor Set, starting from a target Set which will not be modified.
*
* @param target The Set to decorate, whose objects must be introduced.
* @param introducedInterfaces The interfaces to introduce.
* @param targetObjectsInterfaces The interfaces of the objects into the Set.
* @param introductor The concrete {@link DynamicIntroductor} to use for introducing interfaces.
*/
public IntroductorSet(Set target, Class[] introducedInterfaces, Class[] targetObjectsInterfaces, DynamicIntroductor introductor) {
this.target = target;
this.introducedInterfaces = introducedInterfaces;
this.targetObjectsInterfaces = targetObjectsInterfaces;
this.introductor = introductor;
}
/**
* Construct the decorated introductor Set, starting from a target Set which will not be modified.
* @param target The Set to decorate, whose objects must be introduced.
* @param interfaces The interfaces to introduce.
* @param introductor The concrete {@link DynamicIntroductor} to use for introducing interfaces.
*/
public IntroductorSet(Set target, Class[] interfaces, DynamicIntroductor introductor) {
this.target = target;
this.introducedInterfaces = interfaces;
this.introductor = introductor;
}
public int size() {
return this.target.size();
}
public Iterator iterator() {
return new IntroductorIterator(this.target.iterator());
}
public boolean contains(Object o) {
return this.target.contains(o);
}
public boolean containsAll(Collection c) {
return this.target.containsAll(c);
}
public boolean add(Object o) {
return this.target.add(o);
}
public boolean remove(Object o) {
boolean removed = this.target.remove(o);
if (removed) {
this.cachedValues.remove(o);
}
return removed;
}
private Object introduce(Object target) {
if (this.targetObjectsInterfaces != null) {
return this.introductor.introduceInterfaces(
target,
this.introducedInterfaces,
this.targetObjectsInterfaces);
}
else {
return this.introductor.introduceInterfaces(
target,
this.introducedInterfaces);
}
}
private class IntroductorIterator implements Iterator {
private Iterator sourceIt;
private Object currentSourceObject;
public IntroductorIterator(Iterator it) {
this.sourceIt = it;
}
public boolean hasNext() {
return this.sourceIt.hasNext();
}
public Object next() {
this.currentSourceObject = this.sourceIt.next();
Object cachedObject = IntroductorSet.this.cachedValues.get(this.currentSourceObject);
// If an object corresponding to the current one has been already cached, return it:
if (cachedObject != null) {
return cachedObject;
}
// If there's no cached object, make a new introduction:
else {
Object result = IntroductorSet.this.introduce(this.currentSourceObject);
IntroductorSet.this.cachedValues.put(this.currentSourceObject, result);
return result;
}
}
public void remove() {
this.sourceIt.remove();
IntroductorSet.this.cachedValues.remove(this.currentSourceObject);
}
}
}