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}