/* * 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.AbstractList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.springmodules.xt.model.introductor.DynamicIntroductor; import org.apache.commons.collections.map.ReferenceIdentityMap; /** * {@link java.util.List} decorator, which makes all the contained objects implementing a given array of interfaces, while leaving * the original target list unmodified.<br> * This builds on top of {@link DynamicIntroductor} implementations.<br> * <b>Important</b>: This decorated list is <b>immutable</b>. * * @author Sergio Bossa */ public class IntroductorList extends AbstractList { private List 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 list, starting from a target list which will not be modified. * @param target The list to decorate, whose objects must be introduced. * @param introducedInterfaces The interfaces to introduce. * @param tatargetObjectsInterfaceshe interfaces of the objects into the list. * @param introductor The concrete {@link DynamicIntroductor} to use for introducing interfaces. */ public IntroductorList(List target, Class[] introducedInterfaces, Class[] targetObjectsInterfaces, DynamicIntroductor introductor) { this.target = target; this.introducedInterfaces = introducedInterfaces; this.targetObjectsInterfaces = targetObjectsInterfaces; this.introductor = introductor; } /** * Construct the decorated introductor List, starting from a target list which will not be modified. * @param target The list 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 IntroductorList(List target, Class[] interfaces, DynamicIntroductor introductor) { this.target = target; this.introducedInterfaces = interfaces; this.introductor = introductor; } public Object get(int index) { Object o = this.target.get(index); return this._get(o); } public int size() { return this.target.size(); } public boolean contains(Object o) { return this.target.contains(o); } public boolean containsAll(Collection c) { return this.target.containsAll(c); } public Iterator iterator() { return new IntroductorIterator(this.target.listIterator()); } public ListIterator listIterator() { return new IntroductorIterator(this.target.listIterator()); } public void add(int index, Object element) { this.target.add(index, element); } public boolean add(Object element) { return this.target.add(element); } public Object set(int index, Object element) { return this.target.set(index, element); } public boolean remove(Object o) { boolean removed = this.target.remove(o); if (removed) { this.cachedValues.remove(o); } return removed; } public Object remove(int index) { Object removed = this.target.remove(index); if (removed != null) { this.cachedValues.remove(removed); } 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 Object _get(Object currentSourceObject) { Object cachedObject = this.cachedValues.get(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 = this.introduce(currentSourceObject); this.cachedValues.put(currentSourceObject, result); return result; } } private class IntroductorIterator implements ListIterator { private ListIterator sourceIt; private Object currentSourceObject; private int index = 0; public IntroductorIterator(ListIterator it) { this.sourceIt = it; } public boolean hasPrevious() { return this.sourceIt.hasPrevious(); } public boolean hasNext() { return this.sourceIt.hasNext(); } public int previousIndex() { return this.index-1; } public int nextIndex() { return this.index; } public Object previous() { this.currentSourceObject = this.sourceIt.previous(); this.index--; return IntroductorList.this._get(currentSourceObject); } public Object next() { this.currentSourceObject = this.sourceIt.next(); this.index++; return IntroductorList.this._get(currentSourceObject); } public void add(Object o) { this.sourceIt.add(o); } public void remove() { this.sourceIt.remove(); IntroductorList.this.cachedValues.remove(this.currentSourceObject); } public void set(Object o) { this.sourceIt.set(o); } } }