001package org.biojava.bio.seq.projection; 002 003 004 005import java.io.Serializable; 006import java.util.HashMap; 007import java.util.Iterator; 008import java.util.Map; 009 010import org.biojava.bio.BioException; 011import org.biojava.bio.BioRuntimeException; 012import org.biojava.bio.seq.Feature; 013import org.biojava.bio.seq.FeatureFilter; 014import org.biojava.bio.seq.FeatureHolder; 015import org.biojava.bio.seq.FilterUtils; 016import org.biojava.bio.seq.Sequence; 017import org.biojava.utils.ChangeEvent; 018import org.biojava.utils.ChangeListener; 019import org.biojava.utils.ChangeSupport; 020import org.biojava.utils.ChangeType; 021import org.biojava.utils.ChangeVetoException; 022import org.biojava.utils.Unchangeable; 023 024 025 026/** 027 * A good base class to implement ProjectionContext from. 028 * 029 030 * <p> 031 032 * ReparentContext takes care of much of the ugliness of implementing 033 034 * ProjectionContext, such as handling listeners and grafting features onto 035 036 * a new parent. It also sets up a framework for mutating feature filters. 037 038 * Think carefully before overriding methods in this class. 039 040 * </p> 041 042 * 043 044 * @author Matthew Pocock 045 046 * @author Thomas Down 047 048 */ 049 050public class ReparentContext implements ProjectionContext, Serializable { 051 private transient final Map forwardersByFeature = new HashMap(); 052 private final FeatureHolder parent; 053 private final FeatureHolder wrapped; 054 055 056 057 public ReparentContext(FeatureHolder parent, 058 FeatureHolder wrapped) 059 { 060 this.parent = parent; 061 this.wrapped = wrapped; 062 } 063 064 public final FeatureHolder getParent() { 065 return parent; 066 } 067 068 public final FeatureHolder getUnprojectedFeatures() { 069 return wrapped; 070 } 071 072 /** 073 * Create a single projected feature using the rules of this <code>ProjectedFeatureHolder</code>. 074 */ 075 076 public Feature projectFeature(Feature feat) { 077 return ProjectionEngine.DEFAULT.projectFeature(feat, this); 078 } 079 080 public Feature revertFeature(Feature feat) { 081 return ((Projection) feat).getViewedFeature(); 082 } 083 084 public final FeatureFilter projectFilter(FeatureFilter ff) { 085 return FilterUtils.transformFilter(ff, getTransformer()); 086 } 087 088 public final FeatureFilter revertFilter(FeatureFilter ff) { 089 return FilterUtils.transformFilter(ff, getReverter()); 090 } 091 092 protected FilterUtils.FilterTransformer getTransformer() { 093 return new FilterUtils.FilterTransformer() { 094 public FeatureFilter transform(FeatureFilter ff) { 095 // fixme: should we be mapping filters on feature instance or sequence 096 // instance? 097 098 return ff; 099 } 100 }; 101 } 102 103 protected FilterUtils.FilterTransformer getReverter() { 104 return new FilterUtils.FilterTransformer() { 105 public FeatureFilter transform(FeatureFilter ff) { 106 // fixme: should we be mapping filters on feature instance or sequence 107 // instance? 108 109 return ff; 110 } 111 }; 112 } 113 114 public final FeatureHolder getParent(Feature f) { 115 FeatureHolder oldP = f.getParent(); 116 if (oldP instanceof Feature) { 117 if (wrapped.containsFeature(f)) { 118 return parent; 119 } else { 120 return projectFeature((Feature) oldP); 121 } 122 } else { 123 return parent; 124 } 125 } 126 127 public final Sequence getSequence(Feature f) { 128 FeatureHolder fh = getParent(); 129 while (fh instanceof Feature) { 130 fh = ((Feature) fh).getParent(); 131 } 132 133 if (! (fh instanceof Sequence)) { 134 throw new BioRuntimeException("Chasing up parents to get sequence: actually hit: " + fh.toString()); 135 } 136 137 return (Sequence) fh; 138 } 139 140 public FeatureHolder projectChildFeatures(Feature f, FeatureHolder parent) { 141 return new ProjectionSet(f); 142 } 143 144 public final Feature createFeature(Feature.Template projTempl) 145 throws BioException, ChangeVetoException 146 { 147 148 Feature f = null; 149 150 synchronized(wrapped){ 151 f = wrapped.createFeature( 152 ProjectionEngine.DEFAULT.revertTemplate(projTempl, this)); 153 } 154 155 f = projectFeature(f); 156 157 158 159 160 161 return f; 162 163 164 } 165 public final void removeFeature(Feature dyingChild) 166 throws BioException, ChangeVetoException 167 { 168 wrapped.removeFeature(revertFeature(dyingChild)); 169 } 170 171 public final Feature createFeature(Feature f, Feature.Template projTempl) 172 throws BioException, ChangeVetoException 173 { 174 175 176 177 Feature f1 = null; 178 synchronized (f) { 179 f1 = revertFeature(f); 180 } 181 182 Feature f2 = null; 183 synchronized(f1){ 184 f2 = f1.createFeature(ProjectionEngine.DEFAULT.revertTemplate(projTempl, this)); 185 } 186 187 Feature newF = null; 188 189 synchronized (f2) { 190 newF = projectFeature(f2); 191 } 192 193 return newF; 194 195 196 } 197 198 public final void removeFeature(Feature f, Feature f2) 199 throws ChangeVetoException, BioException 200 { 201 revertFeature(f).removeFeature(revertFeature(f2)); 202 } 203 204 public final FeatureFilter getSchema(Feature f) { 205 return projectFilter(f.getSchema()); 206 } 207 208 209 210 public final void addChangeListener(Feature f, ChangeListener cl, ChangeType ct) { 211 if (!f.isUnchanging(ct)) { 212 PFChangeForwarder forwarder = (PFChangeForwarder) forwardersByFeature.get(f); 213 if (forwarder == null) { 214 forwarder = new PFChangeForwarder(f); 215 forwardersByFeature.put(f, forwarder); 216 f.addChangeListener(forwarder, ChangeType.UNKNOWN); 217 } 218 219 forwarder.addChangeListener(cl, ct); 220 } 221 } 222 223 public final void removeChangeListener(Feature f, ChangeListener cl, ChangeType ct) { 224 PFChangeForwarder forwarder = (PFChangeForwarder) forwardersByFeature.get(f); 225 if (forwarder != null) { 226 forwarder.removeChangeListener(cl, ct); 227 if (!forwarder.hasListeners()) { 228 forwardersByFeature.remove(f); 229 f.removeChangeListener(forwarder, ChangeType.UNKNOWN); 230 } 231 } 232 } 233 234 public final FeatureHolder projectFeatures(FeatureHolder fh) { 235 return new ProjectionSet(fh); 236 } 237 238 // 239 // Dumb set of features to which we delegate everything except the 240 // ChangeEvent stuff. 241 // 242 243 private class ProjectionSet 244 extends Unchangeable 245 implements FeatureHolder, Serializable 246 { 247 private final FeatureHolder baseSet; 248 249 ProjectionSet(FeatureHolder baseSet) { 250 this.baseSet = baseSet; 251 } 252 253 public int countFeatures() { 254 return baseSet.countFeatures(); 255 } 256 257 public Iterator features() { 258 final Iterator wrappedIterator = baseSet.features(); 259 260 return new Iterator() { 261 public boolean hasNext() { 262 return wrappedIterator.hasNext(); 263 } 264 265 public Object next() { 266 return projectFeature((Feature) wrappedIterator.next()); 267 } 268 269 public void remove() { 270 throw new UnsupportedOperationException(); 271 } 272 }; 273 } 274 275 public boolean containsFeature(Feature f) { 276 if (! (f instanceof Projection)) { 277 return false; 278 } else { 279 Projection p = (Projection) f; 280 return p.getProjectionContext() == ReparentContext.this && baseSet.containsFeature(p.getViewedFeature()); 281 } 282 } 283 284 public FeatureHolder filter(FeatureFilter ff) { 285 return filter(ff, true); // bit of a hack for now. 286 } 287 288 public FeatureHolder filter(FeatureFilter ff, boolean recurse) { 289 ff = revertFilter(ff); 290 FeatureHolder toProject = baseSet.filter(ff, recurse); 291 return new ProjectionSet(toProject); 292 } 293 294 public Feature createFeature(Feature.Template templ) 295 throws ChangeVetoException, BioException 296 { 297 throw new ChangeVetoException("Can't create features in this projection"); 298 } 299 300 public void removeFeature(Feature toDie) 301 throws ChangeVetoException, BioException 302 { 303 throw new ChangeVetoException("Can't remove features in this projection"); 304 } 305 306 public FeatureFilter getSchema() { 307 return projectFilter(baseSet.getSchema()); 308 } 309 } 310 311 private class PFChangeForwarder 312 extends ChangeSupport 313 implements ChangeListener 314 { 315 private Feature master; 316 317 public PFChangeForwarder(Feature master) { 318 super(1); 319 this.master = master; 320 } 321 322 public void preChange(ChangeEvent cev) 323 throws ChangeVetoException 324 { 325 ChangeEvent cev2 = forwardFeatureChangeEvent(master, cev); 326 if (cev2 != null) { 327 synchronized (cev2) { 328 firePreChangeEvent(cev2); 329 } 330 331 } 332 } 333 334 public void postChange(ChangeEvent cev) { 335 ChangeEvent cev2 = forwardFeatureChangeEvent(master, cev); 336 if (cev2 != null) { 337 synchronized (cev2) { 338 firePostChangeEvent(cev2); 339 } 340 } 341 } 342 343 /** 344 * Called internally to generate a forwarded version of a ChangeEvent from a ProjectedFeature 345 * 346 * @param f the feature who's projection is due to receive an event. 347 * @return a tranformed event, or <code>null</code> to cancel the event. 348 */ 349 350 protected ChangeEvent forwardFeatureChangeEvent(Feature f, ChangeEvent cev) { 351 return new ChangeEvent(projectFeature(f), 352 cev.getType(), 353 cev.getChange(), 354 cev.getPrevious(), 355 cev); 356 357 } 358 } 359} 360