001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022package org.biojava.bio.seq.projection; 023 024import java.io.InputStream; 025import java.lang.reflect.Constructor; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Set; 030 031import org.biojava.bio.BioError; 032import org.biojava.bio.seq.Feature; 033import org.biojava.utils.AssertionFailure; 034import org.biojava.utils.ClassTools; 035import org.biojava.utils.bytecode.ByteCode; 036import org.biojava.utils.bytecode.CodeClass; 037import org.biojava.utils.bytecode.CodeException; 038import org.biojava.utils.bytecode.CodeField; 039import org.biojava.utils.bytecode.CodeMethod; 040import org.biojava.utils.bytecode.CodeUtils; 041import org.biojava.utils.bytecode.GeneratedClassLoader; 042import org.biojava.utils.bytecode.GeneratedCodeClass; 043import org.biojava.utils.bytecode.GeneratedCodeMethod; 044import org.biojava.utils.bytecode.InstructionVector; 045import org.biojava.utils.bytecode.IntrospectedCodeClass; 046import org.biojava.utils.bytecode.LocalVariable; 047 048/** 049 * Factory for proxy objects which project BioJava features 050 * into alternate coordinate systems. This class binds 051 * together a feature and a <code>ProjectionContext</code> 052 * object, and returns the resulting projected feature. 053 * New feature-projection wrapper classes are generated 054 * automatically as they are required. 055 * 056 * Don't use this class directly. This class contains deep voodoo code. Run 057 * away while you still can. 058 * 059 * You may find that for some bizaare reason you need to manually project a feature 060 * through a ProjectionContext. You should be using @link 061 * ProjectionContext.projectFeature(). If this is not practical for some reason, 062 * use this class. However, this probably indicates that you are doing something 063 * mad. 064 * 065 * Projected feature classes will be named as 066 * org.biojava.bio.seq.projection.{$ctxt}.{$feat} where $ctxt and $feat are the 067 * full class names of the context and feature respectively with each "." 068 * character replaced with "_". 069 * 070 * Delegate into this from your ProjectionContext to do the dirty work of 071 * actually projecting features from the underlying data. This factory 072 * generate a new class that is unique to the combination of your 073 * projection context and the Feature interface that the projected feature 074 * implements. Then, it will instantiate that class with your context and the 075 * underlying feature. The returned feature will currently be an implementation 076 * of the package-private class org.biojava.bio.seq.projection.ProjectedFeature, 077 * but this is implementation detail and should not be relied upon. 078 * 079 * @author Thomas Down 080 * @since 1.2 081 */ 082 083public class ProjectionEngine { 084 /** 085 * The standard projection engine object. 086 */ 087 088 public static final ProjectionEngine DEFAULT; 089 090 static { 091 DEFAULT = new ProjectionEngine(); 092 } 093 094 private ProjectionEngine() { 095 super(); 096 } 097 098 private final Map _projectionClasses; 099 private final PEClassLoader loader; 100 private final Instantiator instantiator; 101 102 { 103 loader = new PEClassLoader(ClassTools.getClassLoader(ProjectionEngine.class)); 104 _projectionClasses = new HashMap(); 105 try { 106 instantiator = new InstantiatorImpl(); 107 } catch (Exception ex) { 108 throw new AssertionFailure( 109 "Assertion failure: can't initialize projection system", ex); 110 } 111 } 112 113 private Class searchForClass(Class origClass, Class ctxtClass) { 114 Map pc = (Map) _projectionClasses.get(ctxtClass); 115 116 if(pc == null) { 117 return null; 118 } 119 120 return (Class) pc.get(origClass); 121 } 122 123 private void registerClass(Class origClass, 124 Class ctxtClass, 125 Class projClass) 126 { 127 Map pc = (Map) _projectionClasses.get(ctxtClass); 128 129 if(pc == null) { 130 pc = new HashMap(); 131 pc.put(null, ProjectedFeature.class); 132 _projectionClasses.put(ctxtClass, pc); 133 } 134 135 pc.put(origClass, projClass); 136 } 137 138 /** 139 * Return a projection of Feature <code>f</code> into the system 140 * defined by a given ProjectionContext. The returned object 141 * will implement the same Feature interface (sub-interface of 142 * <code>Feature</code> as the underlying feature, and will also 143 * implement the <code>Projection</code> interface. 144 */ 145 146 public Feature projectFeature(Feature f, ProjectionContext ctx) { 147 //System.err.println("Searching with : " + f + " : " + ctx); 148 Class featureClass = f.getClass(); 149 Class[] fcInterfaces = featureClass.getInterfaces(); 150 Class featureInterface = Feature.class; 151 for (int i = fcInterfaces.length - 1; i >= 0; --i) { 152 if (Feature.class.isAssignableFrom(fcInterfaces[i])) { 153 featureInterface = fcInterfaces[i]; 154 break; 155 } 156 } 157 158 Class projectionClass = getFeatureProjectionClass(featureInterface, 159 ctx.getClass()); 160 //System.err.println("Got projectionClass: " + projectionClass + " for " + featureInterface + " : " + ctx.getClass()); 161 Class[] sig = new Class[2]; 162 sig[0] = featureInterface; 163 sig[1] = ProjectionContext.class; 164 try { 165 Constructor ct = projectionClass.getConstructor(sig); 166 Object[] args = new Object[2]; 167 args[0] = f; 168 args[1] = ctx; 169 return (Feature) instantiator.newInstance(ct, args); 170 } catch (Exception ex) { 171 throw new AssertionFailure( 172 "Assertion failed: Couldn't instantiate proxy " + 173 projectionClass.getName(), 174 ex); 175 } 176 } 177 178 private synchronized Class getFeatureProjectionClass(Class face, Class ctxtClass) { 179 Class projection = searchForClass(face, ctxtClass); 180 if (projection == null) { 181 try { 182 Class baseClass = ProjectedFeature.class; 183 184 String faceName = face.getName() 185 .replaceAll("\\.", "_").replaceAll("\\$", "__"); 186 String ctxtName = ctxtClass.getName() 187 .replaceAll("\\$", "_"); 188 189 CodeClass baseClassC = IntrospectedCodeClass.forClass(baseClass); 190 CodeClass faceClassC = IntrospectedCodeClass.forClass(face); 191 192 GeneratedCodeClass pclass = new GeneratedCodeClass( 193 ctxtName + "__" + faceName, 194 baseClassC, 195 new CodeClass[]{faceClassC}, 196 CodeUtils.ACC_PUBLIC | CodeUtils.ACC_SUPER 197 ); 198 //System.err.println("Generating proxy class: " + pclass.getName()); 199 200 CodeClass[] baseInitArgsList = new CodeClass[] { 201 IntrospectedCodeClass.forClass(Feature.class), 202 IntrospectedCodeClass.forClass(ProjectionContext.class) 203 }; 204 205 CodeClass voidC 206 = IntrospectedCodeClass.forClass(Void.TYPE); 207 CodeClass projectionContextC 208 = IntrospectedCodeClass.forClass(ProjectionContext.class); 209 CodeClass ourContextC 210 = IntrospectedCodeClass.forClass(ctxtClass); 211 212 CodeMethod m_ourBase_init = baseClassC.getConstructor(baseInitArgsList); 213 214 CodeMethod m_ourBase_getViewedFeature = baseClassC.getMethod( 215 "getViewedFeature", 216 CodeUtils.EMPTY_LIST); 217 218 CodeMethod m_ourBase_getProjectionContext = baseClassC.getMethod( 219 "getProjectionContext", 220 CodeUtils.EMPTY_LIST); 221 222 GeneratedCodeMethod init = pclass.createMethod( 223 "<init>", 224 voidC, 225 new CodeClass[]{ 226 faceClassC, 227 projectionContextC 228 }, 229 CodeUtils.ACC_PUBLIC); 230 231 InstructionVector initIV = new InstructionVector(); 232 initIV.add(ByteCode.make_aload(init.getThis())); 233 initIV.add(ByteCode.make_aload(init.getVariable(0))); 234 initIV.add(ByteCode.make_aload(init.getVariable(1))); 235 initIV.add(ByteCode.make_invokespecial(m_ourBase_init)); 236 initIV.add(ByteCode.make_return()); 237 pclass.setCodeGenerator(init, initIV); 238 239 METHOD_MAKER: 240 for (Iterator methIt = faceClassC.getMethods().iterator(); methIt.hasNext();) { 241 CodeMethod faceMethod = (CodeMethod) methIt.next(); 242 Set baseMethods = baseClassC.getMethodsByName(faceMethod.getName()); 243 244 if (baseClassC.getMethodsByName(faceMethod.getName()).size() > 0) { 245 for(Iterator i = baseMethods.iterator(); i.hasNext(); ) { 246 CodeMethod meth = (CodeMethod) i.next(); 247 if( (meth.getModifiers() & CodeUtils.ACC_ABSTRACT) == 0) { 248 //System.err.println("Skipping defined method: " + faceMethod.getName()); 249 continue METHOD_MAKER; 250 } 251 } 252 } 253 254 //System.err.println("Looking at method: " + faceMethod.getName()); 255 if (faceMethod.getName().startsWith("get") && 256 faceMethod.numParameters() == 0) { 257 //System.err.println("Getter: " + faceMethod.getName()); 258 String propName = faceMethod.getName().substring("get".length()); 259 260 // we will make a proxy 261 GeneratedCodeMethod getterMethod = pclass.createMethod( 262 faceMethod.getName(), 263 faceMethod.getReturnType(), 264 CodeUtils.EMPTY_LIST, 265 CodeUtils.ACC_PUBLIC); 266 267 // search for project* 268 //System.err.println("Searching for methods: project" + propName + " : " + ourContextC.getMethodsByName("project" + propName)); 269 CodeMethod projMeth = null; 270 for(Iterator i = ourContextC.getMethodsByName( 271 "project" + propName).iterator(); 272 i.hasNext();) { 273 CodeMethod cm = (CodeMethod) i.next(); 274 //System.err.println("Evaluating context method: " + cm.getName() + " return: " + cm.getReturnType().getName() + " params: " + cm.numParameters()); 275 if(cm.numParameters() == 1 && 276 cm.getReturnType().equals(cm.getParameterType(0)) && 277 cm.getReturnType().equals(faceMethod.getReturnType()) 278 ) { 279 //System.err.println("A match"); 280 projMeth = cm; 281 break; 282 } 283 } 284 285 if(projMeth == null) { 286 // direct proxy 287 // 288 // equivalent to: 289 // Foo ProjFeat.getFoo() { 290 // return getViewedFeature().getFoo(); 291 // } 292 293 InstructionVector proxyIV = new InstructionVector(); 294 proxyIV.add(ByteCode.make_aload(getterMethod.getThis())); 295 proxyIV.add(ByteCode.make_invokevirtual(m_ourBase_getViewedFeature)); 296 proxyIV.add(ByteCode.make_invokeinterface(faceMethod)); 297 proxyIV.add(ByteCode.make_return(getterMethod)); 298 pclass.setCodeGenerator(getterMethod, proxyIV); 299 //System.err.println("Direct proxy: " + getterMethod); 300 } else { 301 // context proxy 302 // 303 // equivalent to: 304 // Foo projFeat.getFoo() { 305 // return getContext().projectFoo( 306 // getViewedFeature().getFoo()); 307 // } 308 309 InstructionVector proxyIV = new InstructionVector(); 310 proxyIV.add(ByteCode.make_aload(getterMethod.getThis())); 311 proxyIV.add(ByteCode.make_invokevirtual(m_ourBase_getProjectionContext)); 312 proxyIV.add(ByteCode.make_checkcast(ourContextC)); 313 proxyIV.add(ByteCode.make_aload(getterMethod.getThis())); 314 proxyIV.add(ByteCode.make_invokevirtual(m_ourBase_getViewedFeature)); 315 proxyIV.add(ByteCode.make_invokeinterface(faceMethod)); 316 proxyIV.add(ByteCode.make_invokevirtual(projMeth)); 317 proxyIV.add(ByteCode.make_return(getterMethod)); 318 pclass.setCodeGenerator(getterMethod, proxyIV); 319 //System.err.println("TargetContext proxy: " + getterMethod + " " + projMeth); 320 } 321 } 322 323 if (faceMethod.getName().startsWith("set") && 324 faceMethod.numParameters() == 0) { 325 // System.err.println("Setter: " + faceMethod.getName()); 326 String propName = faceMethod.getName().substring("set".length()); 327 328 // we will make a proxy 329 GeneratedCodeMethod setterMethod = pclass.createMethod( 330 faceMethod.getName(), 331 voidC, 332 new CodeClass[] { faceMethod.getParameterType(0) }, 333 CodeUtils.ACC_PUBLIC); 334 335 // search for revert* 336 CodeMethod revertMeth = null; 337 for(Iterator i = ourContextC.getMethodsByName( 338 "revert" + propName).iterator(); 339 i.hasNext();) { 340 CodeMethod cm = (CodeMethod) i.next(); 341 if(cm.numParameters() == 1 && 342 cm.getReturnType().equals(cm.getParameterType(0)) && 343 cm.getReturnType().equals(faceMethod.getReturnType()) 344 ) { 345 revertMeth = cm; 346 break; 347 } 348 349 if(revertMeth == null) { 350 // direct proxy 351 // 352 // equivalent to: 353 // void ProjFeat.setFoo(Foo foo) { 354 // getViewedFeature().setFoo(foo); 355 // } 356 357 InstructionVector proxyIV = new InstructionVector(); 358 proxyIV.add(ByteCode.make_aload(setterMethod.getThis())); 359 proxyIV.add(ByteCode.make_invokevirtual(m_ourBase_getViewedFeature)); 360 proxyIV.add(ByteCode.make_lload(setterMethod.getVariable(0))); 361 proxyIV.add(ByteCode.make_invokeinterface(faceMethod)); 362 pclass.setCodeGenerator(setterMethod, proxyIV); 363 //System.err.println("Direct proxy: " + setterMethod); 364 } else { 365 // context proxy 366 // 367 // equivalent to: 368 // void ProjFeat.setFoo(Foo foo) { 369 // getViewedFeature().setFoo( 370 // getProjectionContext().revertFoo(foo)); 371 // } 372 InstructionVector proxyIV = new InstructionVector(); 373 proxyIV.add(ByteCode.make_aload(setterMethod.getThis())); 374 proxyIV.add(ByteCode.make_invokevirtual(m_ourBase_getViewedFeature)); 375 proxyIV.add(ByteCode.make_aload(setterMethod.getThis())); 376 proxyIV.add(ByteCode.make_invokevirtual(m_ourBase_getProjectionContext)); 377 proxyIV.add(ByteCode.make_checkcast(ourContextC) ); 378 proxyIV.add(ByteCode.make_lload(setterMethod.getVariable(0))); 379 proxyIV.add(ByteCode.make_invokevirtual(revertMeth)); 380 proxyIV.add(ByteCode.make_invokeinterface(faceMethod)); 381 pclass.setCodeGenerator(setterMethod, proxyIV); 382 //System.err.println("TargetContext proxy: " + setterMethod + " " + revertMeth); 383 } 384 } 385 } 386 } 387 388 //System.err.println("Creating class: " + pclass); 389 projection = loader.defineClass(pclass); 390 registerClass(face, ctxtClass, projection); 391 } catch (CodeException ex) { 392 throw new BioError(ex); 393 } catch (NoSuchMethodException nsme) { 394 throw new BioError(nsme); 395 } 396 397 } 398 return projection; 399 } 400 401 /** 402 * Revert a template so that it can be used on the original feature-space. 403 * 404 * <p> 405 * This will use the revertFoo methods defined in the context to revert 406 * all of the transformed properties. 407 * </p> 408 * 409 * @param templ the template to revert 410 * @param ctxt the context defining the reversion 411 * @return a new template of the same type as templ, with reverted fields 412 */ 413 public Feature.Template revertTemplate(Feature.Template templ, ProjectionContext ctxt) { 414 Class templateClass = templ.getClass(); 415 Class projClass = getTemplateProjectorClass(templateClass, ctxt.getClass()); 416 TemplateProjector proj = null; 417 try { 418 proj = (TemplateProjector) projClass.newInstance(); 419 } catch (InstantiationException ie) { 420 throw new AssertionFailure( 421 "Assertion failed: Couldn't instantiate template projector" + 422 projClass.getName(), 423 ie); 424 } catch (IllegalAccessException iae) { 425 throw new AssertionFailure( 426 "Assertion Failed: Couldn't instantiate template projector" + 427 projClass.getName(), 428 iae); 429 } 430 431 try { 432 return proj.revertTemplate(ctxt, templ); 433 } catch (CloneNotSupportedException cnse) { 434 throw new AssertionFailure(cnse); 435 } 436 } 437 438 private Class getTemplateProjectorClass(Class templateClass, Class ctxtClass) { 439 Class projection = searchForClass(templateClass, ctxtClass); 440 if(projection == null) { 441 try { 442 String tpltName = templateClass.getName() 443 .replaceAll("\\.", "_").replaceAll("\\$", "__"); 444 String ctxtName = ctxtClass.getName() 445 .replaceAll("\\$", "_"); 446 447 CodeClass c_baseClass = CodeUtils.TYPE_OBJECT; 448 CodeClass c_tpClass = IntrospectedCodeClass.forClass(TemplateProjector.class); 449 450 GeneratedCodeClass pclass = new GeneratedCodeClass( 451 ctxtName + "__" + tpltName, 452 c_baseClass, 453 new CodeClass[] { c_tpClass }, 454 CodeUtils.ACC_PUBLIC | CodeUtils.ACC_SUPER); 455 456 // we need a dumb no-args constructor 457 CodeMethod m_Object_init 458 = CodeUtils.TYPE_OBJECT.getConstructor(CodeUtils.EMPTY_LIST); 459 GeneratedCodeMethod init = pclass.createMethod("<init>", 460 CodeUtils.TYPE_VOID, 461 CodeUtils.EMPTY_LIST, 462 CodeUtils.ACC_PUBLIC); 463 InstructionVector initIV = new InstructionVector(); 464 initIV.add(ByteCode.make_aload(init.getThis())); 465 initIV.add(ByteCode.make_invokespecial(m_Object_init)); 466 initIV.add(ByteCode.make_return()); 467 pclass.setCodeGenerator(init, initIV); 468 469 // we need to implement revertTemplate 470 CodeClass c_FeatureTemplate = IntrospectedCodeClass.forClass(Feature.Template.class); 471 CodeClass c_ProjectionContext = IntrospectedCodeClass.forClass(ProjectionContext.class); 472 GeneratedCodeMethod revT = pclass.createMethod("revertTemplate", 473 c_FeatureTemplate, 474 new CodeClass[] { 475 c_ProjectionContext, 476 c_FeatureTemplate }, 477 new String[] { 478 "ctxt", "origTplt" }, 479 CodeUtils.ACC_PUBLIC); 480 revT.addThrownException(IntrospectedCodeClass.forClass(CloneNotSupportedException.class)); 481 InstructionVector revTIV = new InstructionVector(); 482 483 // firstly, revertTempalte must cast the arg to the correct type and 484 // then clone it and store this away in a local variable 485 CodeMethod m_FeatureTemplate_clone = c_FeatureTemplate.getMethod( 486 "clone", 487 CodeUtils.EMPTY_LIST); 488 LocalVariable lv_ctxt = revT.getVariable("ctxt"); 489 LocalVariable lv_origTplt = revT.getVariable("origTplt"); 490 LocalVariable lv_ourTplt = new LocalVariable(c_FeatureTemplate, "ourTemplate"); 491 revTIV.add(ByteCode.make_aload(lv_origTplt)); 492 revTIV.add(ByteCode.make_invokevirtual(m_FeatureTemplate_clone)); 493 revTIV.add(ByteCode.make_checkcast(c_FeatureTemplate)); 494 revTIV.add(ByteCode.make_astore(lv_ourTplt)); 495 496 // This method must assign each field that is mapped using revertFoo. 497 // The pattern will be for each revertable field, call 498 // ((TemplateClass) ourTemplate).foo = 499 // ((TemplateClass) origTemplate).foo 500 CodeClass c_ourContext = IntrospectedCodeClass.forClass(ctxtClass); 501 CodeClass c_ourTemplate = IntrospectedCodeClass.forClass(templateClass); 502 503 for(Iterator fi = c_ourTemplate.getFields().iterator(); 504 fi.hasNext(); ) { 505 CodeField field = (CodeField) fi.next(); 506 String fieldName = field.getName(); 507 String propName = fieldName.substring(0, 1).toUpperCase() + 508 fieldName.substring(1); 509 String revName = "revert" + propName; 510 511 CodeMethod revertMeth = null; 512 for(Iterator mi = c_ourContext.getMethodsByName( 513 revName).iterator(); 514 mi.hasNext(); ) 515 { 516 CodeMethod cm = (CodeMethod) mi.next(); 517 if(cm.numParameters() == 1 && 518 cm.getReturnType().equals(cm.getParameterType(0)) && 519 cm.getReturnType().equals(field.getType())) { 520 revertMeth = cm; 521 break; 522 } 523 } 524 525 if(revertMeth != null) { 526 // we have a revert method. Wire it in using the pattern: 527 // 528 // ((TemplateClass) ourTemplate).foo = 529 // ((OurCtxt) ctxt).revertFoo( 530 // ((TemplateClass) origTemplate).foo) 531 InstructionVector revIV = new InstructionVector(); 532 revIV.add(ByteCode.make_aload(lv_ourTplt)); 533 revIV.add(ByteCode.make_checkcast(c_ourTemplate)); 534 revIV.add(ByteCode.make_aload(lv_ctxt)); 535 revIV.add(ByteCode.make_checkcast(c_ourContext)); 536 revIV.add(ByteCode.make_aload(lv_origTplt)); 537 revIV.add(ByteCode.make_checkcast(c_ourTemplate)); 538 revIV.add(ByteCode.make_getfield(field)); 539 revIV.add(ByteCode.make_invokevirtual(revertMeth)); 540 revIV.add(ByteCode.make_putfield(field)); 541 revTIV.add(revIV); 542 } 543 } 544 revTIV.add(ByteCode.make_aload(lv_ourTplt)); 545 revTIV.add(ByteCode.make_areturn()); 546 547 pclass.setCodeGenerator(revT, revTIV); 548 549 projection = loader.defineClass(pclass); 550 registerClass(templateClass, ctxtClass, projection); 551 } catch (CodeException ce) { 552 throw new AssertionFailure("Unable to create template projector: ", ce); 553 } catch (NoSuchMethodException nsme) { 554 throw new AssertionFailure("Unable to create template projector: ", nsme); 555 } 556 } 557 return projection; 558 } 559 560 private static class PEClassLoader extends GeneratedClassLoader { 561 public PEClassLoader(ClassLoader parent) { 562 super(parent); 563 } 564 565 public Class loadClassMagick(String name) 566 throws ClassNotFoundException { 567 try { 568 String resName = name.replace('.', '/') + ".class"; 569 InputStream is = getResourceAsStream(resName); 570 if (is == null) { 571 throw new ClassNotFoundException( 572 "Could not find class resource: " + resName); 573 } 574 575 byte[] buffer = new byte[10000]; 576 int len = 0; 577 int read = 0; 578 while (read >= 0) { 579 read = is.read(buffer, len, buffer.length - len); 580 if (read > 0) { 581 len += read; 582 } 583 } 584 is.close(); 585 Class c = defineClass(name, buffer, 0, len); 586 resolveClass(c); 587 588 return c; 589 } catch (Exception ex) { 590 throw new ClassNotFoundException( 591 "Could not load class for " + name + ": " + ex.toString()); 592 } 593 594 } 595 } 596 597 /** 598 * Internal helper class. 599 */ 600 601 public static interface Instantiator { 602 public Object newInstance(Constructor c, Object[] args) throws Exception; 603 } 604 605 /** 606 * Internal helper class. 607 */ 608 609 static class InstantiatorImpl implements Instantiator { 610 public Object newInstance(Constructor c, Object[] args) throws Exception { 611 return c.newInstance(args); 612 } 613 } 614 615 /** 616 * This is an interface for things that project feature templates. 617 * 618 * <p><em>Note:</em> This is implementation guts that has to be public to bind 619 * contexts to features. It is not intended for users.</p> 620 * 621 * @author Matthew Pocock 622 * @since 1.4 623 */ 624 public static interface TemplateProjector { 625 public Feature.Template revertTemplate(ProjectionContext ctxt, 626 Feature.Template projTempl) 627 throws CloneNotSupportedException; 628 } 629}