001    package com.hammurapi.eventbus;
002    
003    import java.io.File;
004    import java.io.FileOutputStream;
005    import java.io.FileWriter;
006    import java.io.IOException;
007    import java.io.ObjectOutputStream;
008    import java.io.StringWriter;
009    import java.io.Writer;
010    import java.lang.reflect.Method;
011    import java.lang.reflect.ParameterizedType;
012    import java.lang.reflect.Type;
013    import java.lang.reflect.TypeVariable;
014    import java.util.ArrayList;
015    import java.util.Arrays;
016    import java.util.Collection;
017    import java.util.Collections;
018    import java.util.Comparator;
019    import java.util.HashMap;
020    import java.util.HashSet;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Locale;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.lang.StringEscapeUtils;
028    import org.onemind.jxp.JxpContext;
029    import org.onemind.jxp.JxpPageSource;
030    import org.onemind.jxp.JxpProcessor;
031    import org.onemind.jxp.ResourceStreamPageSource;
032    
033    import com.hammurapi.common.Context;
034    import com.hammurapi.common.TokenExpander;
035    import com.hammurapi.common.TokenExpander.TokenSource;
036    import com.hammurapi.convert.AbstractReflectiveAtomicConvertersBundle;
037    import com.hammurapi.convert.ConverterMethod;
038    import com.hammurapi.convert.ConvertingService;
039    import com.hammurapi.eventbus.IntrospectorBase.IntrospectionResult;
040    import com.hammurapi.extract.BinaryExtractor;
041    import com.hammurapi.extract.ComparisonResult;
042    import com.hammurapi.extract.CompositePredicate;
043    import com.hammurapi.extract.Constant;
044    import com.hammurapi.extract.Equal;
045    import com.hammurapi.extract.Extractor;
046    import com.hammurapi.extract.False;
047    import com.hammurapi.extract.IndexedExtractor;
048    import com.hammurapi.extract.InstanceOfPredicate;
049    import com.hammurapi.extract.MappedExtractor;
050    import com.hammurapi.extract.Not;
051    import com.hammurapi.extract.NotEqual;
052    import com.hammurapi.extract.Predicate;
053    import com.hammurapi.extract.True;
054    import com.hammurapi.extract.java.JavaExtractor;
055    import com.hammurapi.render.RenderingException;
056    import com.hammurapi.render.WriterRenderer;
057    
058    /**
059     * This class generates source code for Java binding class.
060     * By default binder classes are put to the same package as handler classes and have postfix ''JavaBinder''. 
061     * This behavior can be changed by subclassing the compiler. The class keeps track of generated predicate
062     * classes and attempts to reuse them.
063     *  
064     * @author Pavel Vlasov
065     *
066     */
067    public class JavaBinderCompiler {
068            
069            protected static final String BINDER_CLASS_PACKAGE = "binderClassPackage";
070            private static final String JAVA_INLINE = "java_inline";
071            private static final String JAVA = "java";
072            private static final String BIND_HELPER = "bindHelper";
073            
074            private class EventDispatchContextDescriptor {
075                    
076                    private Class<EventDispatchContext> contextClass;
077                    private _Type[] contextTypeParameters;
078    
079                    public EventDispatchContextDescriptor(Class<EventDispatchContext> contextClass, _Type[] eventBusTypeParameters) {
080                            this.contextClass = contextClass;
081                            
082                            TypeVariable<Class<EventDispatchContext>>[] cctp = EventDispatchContext.class.getTypeParameters();
083                            contextTypeParameters = new _Type[cctp.length];
084                            for (int i=0; i< cctp.length; ++i) {
085                                    contextTypeParameters[i] = _Type.createType(cctp[i]);
086                            }
087                            
088                            List<List<PathEntry>> iPaths = inheritancePaths(contextClass, EventDispatchContext.class);
089                            Collections.sort(iPaths, new Comparator<List<PathEntry>>() {
090    
091                                    @Override
092                                    public int compare(List<PathEntry> o1, List<PathEntry> o2) {
093                                            int sizeDelta =  o1.size() - o2.size();
094                                            if (sizeDelta!=0) {
095                                                    return sizeDelta;
096                                            }
097                                            return o1.hashCode() - o2.hashCode();
098                                    }
099                            });
100                    for (List<PathEntry> path: iPaths) {
101                            for (PathEntry pe: path) {
102                                    pe.bind(contextTypeParameters);
103                            }
104                        break;
105                    }
106    
107                    for (int i=0; i<contextTypeParameters.length; ++i) {
108                            bind(contextTypeParameters, i, new _TypeVariable("E"), eventBusTypeParameters[0]);
109                            bind(contextTypeParameters, i, new _TypeVariable("P"), eventBusTypeParameters[1]);
110                            bind(contextTypeParameters, i, new _TypeVariable("C"), eventBusTypeParameters[2]);                              
111                            bind(contextTypeParameters, i, new _TypeVariable("K"), eventBusTypeParameters[3]);
112                            bind(contextTypeParameters, i, new _TypeVariable("H"), eventBusTypeParameters[4]);
113                            bind(contextTypeParameters, i, new _TypeVariable("S"), eventBusTypeParameters[5]);
114                    }
115                    
116                    }                               
117    
118                    private void bind(_Type[] contextTypeParameters, int i, _TypeVariable from, _Type to) { 
119                            if (from.equals(contextTypeParameters[i])) {
120                                    contextTypeParameters[i] = to;
121                            } else {
122                                    contextTypeParameters[i].bind(from, to);
123                            }                       
124                    }
125                                    
126                    public String getType() {
127                            _Class cls = new _Class(contextClass);
128                            cls.bind(new _TypeVariable("E"), contextTypeParameters[0]);
129                            cls.bind(new _TypeVariable("P"), contextTypeParameters[1]);
130                            cls.bind(new _TypeVariable("C"), contextTypeParameters[2]);                             
131                            cls.bind(new _TypeVariable("H"), contextTypeParameters[3]);
132                            cls.bind(new _TypeVariable("S"), contextTypeParameters[4]);
133                            
134                            return cls.toJavaDeclaration();
135                    }
136                    
137                    public String getEventType() {
138                            return contextTypeParameters[0].toJavaDeclaration();
139                    }
140                    
141                    public String getStoreType() {
142                            return contextTypeParameters[4].toJavaDeclaration();
143                    }
144                    
145                    public boolean isJoin() {
146                            return EventDispatchJoinContext.class.isAssignableFrom(contextClass);
147                    }
148            }
149            
150            private class BindMethod<E, HC> {
151                    
152                    private IntrospectionResult<E, HC> ir;
153                    private String name;
154                    private EventDispatchContextDescriptor eventDispatchContextDescriptor;
155                    private Class<E> busEventType;
156                    private Map<String, Object> renderingEnvironment;
157    
158                    @SuppressWarnings({ "unchecked", "rawtypes" })
159                    BindMethod(String name, IntrospectionResult<E, HC> ir, Class<E> busEventType, _Type[] typeParameters, Map<String, Object> renderingEnvironment) {
160                            this.ir = ir;
161                            this.name = name;
162                            Class<?> firstParameterType = ir.getMethod().getParameterTypes()[0];
163                            if (ir.getOffset()==1 
164                                            && EventDispatchContext.class.isAssignableFrom(firstParameterType) 
165                                            && !(EventDispatchContext.class.equals(firstParameterType)) || EventDispatchJoinContext.class.equals(firstParameterType)) {
166                                    
167                                    eventDispatchContextDescriptor = new EventDispatchContextDescriptor((Class<EventDispatchContext>) firstParameterType, typeParameters);
168                            }
169                            this.busEventType = busEventType;
170                            this.renderingEnvironment = renderingEnvironment;
171                    }
172                    
173                    public String getName() {
174                            return name;
175                    }
176                    
177                    public String getParameterTypeCast(int pIdx) {                  
178                            Class<E> parameterType = ir.getParameterTypes()[pIdx];
179                            if (parameterType.isAssignableFrom(busEventType)) {
180                                    return "";
181                            }
182                            
183                            if (parameterType.isPrimitive()) {
184                                    if (boolean.class.equals(parameterType)) {
185                                            return "("+Boolean.class.getName()+")";
186                                    } else if (char.class.equals(parameterType)) {
187                                            return "("+Character.class.getName()+")";
188                                    } else if (byte.class.equals(parameterType)) {
189                                            return "("+Byte.class.getName()+")";
190                                    } else if (short.class.equals(parameterType)) {
191                                            return "("+Short.class.getName()+")";
192                                    } else if (int.class.equals(parameterType)) {
193                                            return "("+Integer.class.getName()+")";
194                                    } else if (long.class.equals(parameterType)) {
195                                            return "("+Long.class.getName()+")";
196                                    } else if (float.class.equals(parameterType)) {
197                                            return "("+Float.class.getName()+")";
198                                    } else if (double.class.equals(parameterType)) {
199                                            return "("+Double.class.getName()+")";
200                                    } else if (void.class.equals(parameterType)) {
201                                            throw new IllegalArgumentException("Parameter type cannot be void");
202                                    }
203                            } 
204                            return "("+parameterType.getName()+")";
205                    }
206    
207                    public Method getMethod() {
208                            return ir.getMethod();
209                    }
210                    
211                    public boolean isVoid() {
212                            return void.class.equals(getMethod().getReturnType());
213                    }
214    
215                    public Predicate<E, HC>[] getPredicates() {
216                            return ir.getPredicates();
217                    }
218                    
219                    public String inlinePredicate(int pIdx) throws Exception {
220                            WriterRenderer renderer = ConvertingService.convert(ir.getPredicates()[pIdx], WriterRenderer.class);
221                            if (renderer==null) {
222                                    throw new EventBusException("Cannot compile predicate "+ir.getPredicates()[pIdx].getClass().getName());
223                            }
224                            
225                            StringWriter sw = new StringWriter();
226                            if (renderer.render(sw, renderingEnvironment, Context.INSTANCE, JAVA_INLINE, null, outputDir)) {
227                                    sw.close();
228                                    return sw.toString();
229                            }
230                            throw new EventBusException("Cannot compile predicate "+ir.getPredicates()[pIdx].getClass().getName());
231                    }
232    
233                    public int getOffset() {
234                            return ir.getOffset();
235                    }
236    
237                    public Class<E>[] getParameterTypes() {
238                            return ir.getParameterTypes();
239                    }
240    
241                    public Handler getHandlerAnnotation() {
242                            return ir.getHandlerAnnotation();
243                    }
244    
245                    public EventDispatchContextDescriptor getEventDispatchContext() {
246                            return eventDispatchContextDescriptor;
247                    }
248                    
249            }
250            
251            public static class JavaExtractorEntry {
252                    
253                    private JavaExtractor javaExtractor;
254                    private String packageName;
255                    private String className;
256                    
257                    JavaExtractorEntry(JavaExtractor javaExtractor, String packageName, String className) {
258                            super();
259                            this.javaExtractor = javaExtractor;
260                            this.packageName = packageName;
261                            this.className = className;
262                    }
263    
264                    JavaExtractor getJavaExtractor() {
265                            return javaExtractor;
266                    }
267    
268                    String getPackageName() {
269                            return packageName;
270                    }
271    
272                    String getClassName() {
273                            return className;
274                    }                                       
275                    
276            }
277            
278            private class BindHelper<E, HC> {
279                    
280                    private Collection<BindMethod<E,HC>> bindMethods = new ArrayList<JavaBinderCompiler.BindMethod<E,HC>>(); 
281                    private Collection<JavaExtractorEntry> javaExtractorEntries;
282                    private Class<?> contextType;
283                                    
284                    BindHelper(
285                                    Collection<IntrospectionResult<E, HC>> irc, 
286                                    Class<E> busEventType, 
287                                    _Type[] typeParameters, 
288                                    Collection<JavaExtractorEntry> javaExtractorEntries, 
289                                    String binderClassPackage) {
290                            
291                            Set<String> usedNames = new HashSet<String>();
292                            for (IntrospectionResult<E, HC> ir: irc) {
293                                    String name = ir.getMethod().getName();
294                                    for (int i=0; usedNames.contains(name); ++i) {
295                                            name = ir.getMethod().getName()+i;
296                                    }
297                                    usedNames.add(name);
298                                    Map<String, Object> renderingEnvironment = new HashMap<String, Object>();
299                                    renderingEnvironment.put(BIND_HELPER, this);
300                                    renderingEnvironment.put(BINDER_CLASS_PACKAGE, binderClassPackage);
301                                    bindMethods.add(new BindMethod<E, HC>(name, ir, busEventType, typeParameters, renderingEnvironment));
302                            }
303                            this.javaExtractorEntries = javaExtractorEntries;
304                            this.contextType = ((_Class) typeParameters[2]).clazz;
305                    }
306    
307                    public Collection<BindMethod<E,HC>> getBindMethods() {
308                            return bindMethods;
309                    }
310                    
311                    public Collection<JavaExtractorEntry> getJavaExtractorEntries() {
312                            return javaExtractorEntries;
313                    }
314    
315                    public Class<?> getContextType() {
316                            return contextType;
317                    }
318                    
319            }
320            
321            private File outputDir;
322            private ArrayList<JavaExtractorEntry> javaExtractorEntries = new ArrayList<JavaExtractorEntry>();
323            
324            public JavaBinderCompiler(File outputDir) {
325                    this.outputDir = outputDir;
326            }
327            
328            public <E, C, HC extends C, BC extends EventBus<E, ?, C, ?, ?, ?>> void compileJavaBinder(Class<HC> handlerClass, Class<BC> busClass, ClassLoader classLoader, TokenExpander tokenExpander) {           
329            
330                    try {
331                            Map<String, Object> environment = new HashMap<String, Object>();
332                            environment.put(BINDER_CLASS_PACKAGE, getBinderClassPackage(handlerClass));
333                            environment.put("binderClassName", getBinderClassName(handlerClass));
334                            environment.put("binderClassComment", "Binds handler class "+handlerClass.getName()+" to event bus "+busClass.getName());
335    
336                            TypeVariable<Class<EventBus>>[] ebtp = EventBus.class.getTypeParameters();
337                            _Type[] eventBusTypeParameters = new _Type[ebtp.length];
338                            for (int i=0; i< ebtp.length; ++i) {
339                                    eventBusTypeParameters[i] = _Type.createType(ebtp[i]);
340                            }
341                            
342                            List<List<PathEntry>> iPaths = inheritancePaths(busClass, EventBus.class);
343                            Collections.sort(iPaths, new Comparator<List<PathEntry>>() {
344    
345                                    @Override
346                                    public int compare(List<PathEntry> o1, List<PathEntry> o2) {
347                                            int sizeDelta =  o1.size() - o2.size();
348                                            if (sizeDelta!=0) {
349                                                    return sizeDelta;
350                                            }
351                                            return o1.hashCode() - o2.hashCode();
352                                    }
353                            });
354                    for (List<PathEntry> path: iPaths) {
355                            for (PathEntry pe: path) {
356                                    pe.bind(eventBusTypeParameters);
357                            }
358                        break;
359                    }
360    
361                    _Type pType = eventBusTypeParameters[1];
362                    if (pType instanceof _TypeVariable) {
363                            _Class to = new _Class(Integer.class);
364                            for (int i=0; i<eventBusTypeParameters.length; ++i) {
365                                    if (pType.equals(eventBusTypeParameters[i])) {
366                                            eventBusTypeParameters[i] = to;
367                                    } else {
368                                            eventBusTypeParameters[i].bind((_TypeVariable) pType, to);
369                                    }
370                            }
371                    }
372                            
373                            Class<E> busEventType;
374                            if (eventBusTypeParameters[0] instanceof _TypeVariable) {
375                                    busEventType = (Class<E>) Object.class;
376                            _Type eType = eventBusTypeParameters[0];
377                            _Class to = new _Class(busEventType);
378                                for (int i=0; i<eventBusTypeParameters.length; ++i) {
379                                    if (eType.equals(eventBusTypeParameters[i])) {
380                                            eventBusTypeParameters[i] = to;
381                                    } else {
382                                            eventBusTypeParameters[i].bind((_TypeVariable) eType, to);
383                                    }
384                            }
385                            } else if (eventBusTypeParameters[0] instanceof _Class) {
386                                    busEventType = (Class<E>) ((_Class) eventBusTypeParameters[0]).clazz;
387                            } else if (eventBusTypeParameters[0] instanceof _ParameterizedType) {
388                                    busEventType = (Class<E>) ((_ParameterizedType) eventBusTypeParameters[0]).rawType;
389                            } else {
390                                    throw new EventBusException("Unexpected type: "+eventBusTypeParameters[0]);
391                            }
392                            
393                            if (eventBusTypeParameters[2] instanceof _TypeVariable) {
394                            _Type eType = eventBusTypeParameters[2];
395                            _Class to = new _Class(Object.class);
396                                for (int i=0; i<eventBusTypeParameters.length; ++i) {
397                                    if (eType.equals(eventBusTypeParameters[i])) {
398                                            eventBusTypeParameters[i] = to;
399                                    } else {
400                                            eventBusTypeParameters[i].bind((_TypeVariable) eType, to);
401                                    }
402                            }
403                            }
404                            
405                            environment.put("E", eventBusTypeParameters[0].toJavaDeclaration());                    
406                            environment.put("P", eventBusTypeParameters[1].toJavaDeclaration());
407                            environment.put("C", eventBusTypeParameters[2].toJavaDeclaration());                            
408                            environment.put("K", eventBusTypeParameters[3].toJavaDeclaration());
409                            environment.put("H", eventBusTypeParameters[4].toJavaDeclaration());
410                            environment.put("S", eventBusTypeParameters[5].toJavaDeclaration());
411                            
412                    List<String> genericParameters = new ArrayList<String>();
413                    for (int i=0; i<eventBusTypeParameters.length; ++i) {
414                            if (eventBusTypeParameters[i] instanceof _TypeVariable) {
415                                    genericParameters.add(eventBusTypeParameters[i].toJavaDeclaration());
416                            }
417                    }
418                    
419                    if (genericParameters.isEmpty()) {
420                            environment.put("genericParameters", "");
421                    } else {
422                            StringBuilder sb = new StringBuilder("<");
423                            Iterator<String> gpit = genericParameters.iterator();
424                            while (gpit.hasNext()) {
425                                    sb.append(gpit.next());
426                                    if (gpit.hasNext()) {
427                                            sb.append(", ");
428                                    }
429                            }
430                            sb.append(">");
431                            environment.put("genericParameters", sb.toString());
432                    }                       
433                                                            
434                            environment.put("B", toJavaDeclaration(busClass, eventBusTypeParameters));
435                            environment.put("I", toJavaDeclaration(handlerClass, eventBusTypeParameters));
436                            
437                            IntrospectorBase<E,HC> introspector = new IntrospectorBase<E, HC>(classLoader, tokenExpander);
438                                                    
439                            environment.put("bindHelper", new BindHelper<E, HC>(introspector.introspect(handlerClass, busEventType), busEventType, eventBusTypeParameters, javaExtractorEntries, getBinderClassPackage(handlerClass)));
440                            
441                            String prefix = getClass().getPackage().getName().replace('.', '/');
442                            JxpPageSource pageSource = new ResourceStreamPageSource("/"+prefix);            
443                            JxpContext context = new JxpContext(pageSource);
444                            JxpProcessor processor = new JxpProcessor(context);
445                            
446                            File packageDir = new File(outputDir, getBinderClassPackage(handlerClass).replace('.', File.separatorChar));
447                            packageDir.mkdirs();
448                            File outputFile = new File(packageDir, getBinderClassName(handlerClass)+".java");                       
449                            Writer writer = new FileWriter(outputFile);
450                            try {
451                                    processor.process("JavaBinder.jxp", writer, environment);
452                            } finally {
453                                    writer.close();
454                            }
455                    } catch (Exception e) {
456                            throw new EventBusException(e);
457                    } 
458            }
459            
460            private String toJavaDeclaration(Class<?> clazz, _Type[] typeParameters) {
461                    _Class cls = new _Class(clazz);
462                    cls.bind(new _TypeVariable("E"), typeParameters[0]);
463                    cls.bind(new _TypeVariable("P"), typeParameters[1]);
464                    cls.bind(new _TypeVariable("C"), typeParameters[2]);                            
465                    cls.bind(new _TypeVariable("K"), typeParameters[3]);
466                    cls.bind(new _TypeVariable("H"), typeParameters[4]);
467                    cls.bind(new _TypeVariable("S"), typeParameters[5]);
468                    
469                    return cls.toJavaDeclaration();
470            }
471            
472            protected String getBinderClassPackage(Class<?> handlerClass) {
473                    return handlerClass.getPackage().getName();
474            }
475    
476            protected String getBinderClassName(Class<?> handlerClass) {
477                    int idx = handlerClass.getName().lastIndexOf('.');
478                    return (idx==-1 ? handlerClass.getName() : handlerClass.getName().substring(idx+1))+"JavaBinder";
479            }
480            
481            private static abstract class _Type {
482                    
483                    abstract void bind(_TypeVariable from, _Type to);
484                    
485                    abstract String toJavaDeclaration();
486                    
487                    static _Type createType(Type rType) {
488                            if (rType instanceof TypeVariable<?>) {
489                                    return new _TypeVariable((TypeVariable<?>) rType);
490                            }
491                            
492                            if (rType instanceof ParameterizedType) {
493                                    return new _ParameterizedType((ParameterizedType) rType);
494                            }
495    
496                            if (rType instanceof Class) {
497                                    return new _Class((Class) rType);
498                            }
499                            throw new UnsupportedOperationException();
500                    }
501            }
502            
503            private static class _Class extends _Type {
504                    
505                    private Class<?> clazz;
506    
507                    public _Class(Class<?> clazz) {
508                            this.clazz = clazz;
509                            TypeVariable<?>[] ctp = clazz.getTypeParameters();
510                            this.typeParameters = new _Type[ctp.length];
511                            for (int i=0; i<ctp.length; ++i) {
512                                    typeParameters[i] = _Type.createType(ctp[i]);
513                            }
514                    }
515    
516                    @Override
517                    public String toString() {
518                            return "_Class [clazz=" + clazz + "]";
519                    }
520    
521                    _Type[] typeParameters;
522                    
523                    @Override
524                    void bind(_TypeVariable from, _Type to) {
525                            for (int i=0; i<typeParameters.length; ++i) {
526                                    if (from.equals(typeParameters[i])) {
527                                            typeParameters[i] = to;
528                                    } else {
529                                            typeParameters[i].bind(from, to);
530                                    }
531                            }
532                    }
533    
534                    @Override
535                    String toJavaDeclaration() {
536                            StringBuilder sb = new StringBuilder(((Class) clazz).getCanonicalName());
537                            if (typeParameters.length>0) {
538                                    sb.append("<");
539                                    for (int i=0; i<typeParameters.length; ++i) {
540                                            if (i>0) {
541                                                    sb.append(", ");
542                                            }
543                                            sb.append(typeParameters[i].toJavaDeclaration());
544                                    }
545                                    sb.append(">");
546                            }
547                            return sb.toString();
548                    }               
549            }
550    
551            private static class _TypeVariable extends _Type {
552                    private String name;
553    
554                    public _TypeVariable(TypeVariable<?> rTypeVariable) {
555                            super();
556                            this.name = rTypeVariable.getName();
557                    }
558    
559                    public _TypeVariable(String name) {
560                            super();
561                            this.name = name;
562                    }
563                    
564                    @Override
565                    public int hashCode() {
566                            final int prime = 31;
567                            int result = 1;
568                            result = prime * result + ((name == null) ? 0 : name.hashCode());
569                            return result;
570                    }
571    
572                    @Override
573                    public String toString() {
574                            return "_TypeVariable [name=" + name + "]";
575                    }
576    
577                    @Override
578                    public boolean equals(Object obj) {
579                            if (this == obj)
580                                    return true;
581                            if (obj == null)
582                                    return false;
583                            if (getClass() != obj.getClass())
584                                    return false;
585                            _TypeVariable other = (_TypeVariable) obj;
586                            if (name == null) {
587                                    if (other.name != null)
588                                            return false;
589                            } else if (!name.equals(other.name))
590                                    return false;
591                            return true;
592                    }
593    
594                    public String getName() {
595                            return name;
596                    }
597    
598                    public void setName(String name) {
599                            this.name = name;
600                    }
601    
602                    @Override
603                    void bind(_TypeVariable from, _Type to) {
604                            if (from.getName().equals(name)) {
605                                    if (to instanceof _TypeVariable) {
606                                            name = ((_TypeVariable) to).name;
607                                    } else {
608                                            throw new UnsupportedOperationException();
609                                    }
610                            }                       
611                    }
612    
613                    @Override
614                    String toJavaDeclaration() {
615                            return name;
616                    }                                               
617            }
618            
619            private static class _ParameterizedType extends _Type {
620                    Type rawType;
621                    _Type[] actualTypeArguments;
622                    
623                    public _ParameterizedType(ParameterizedType pt) {
624                            rawType = pt.getRawType();
625                            Type[] atp = pt.getActualTypeArguments();
626                            actualTypeArguments = new _Type[atp.length];
627                            for (int i=0; i<atp.length; ++i) {
628                                    actualTypeArguments[i]=_Type.createType(atp[i]);
629                            }
630                    }
631    
632                    @Override
633                    void bind(_TypeVariable from, _Type to) {
634                            for (int i=0; i<actualTypeArguments.length; ++i) {
635                                    if (from.equals(actualTypeArguments[i])) {
636                                            actualTypeArguments[i] = to;
637                                    } else {
638                                            actualTypeArguments[i].bind(from, to);
639                                    }
640                            }
641                    }
642    
643                    @Override
644                    public String toString() {
645                            return "_ParameterizedType [rawType=" + rawType
646                                            + ", actualTypeArguments="
647                                            + Arrays.toString(actualTypeArguments) + "]";
648                    }
649    
650                    @Override
651                    String toJavaDeclaration() {
652                            StringBuilder sb = new StringBuilder(((Class) rawType).getCanonicalName());
653                            sb.append("<");
654                            for (int i=0; i<actualTypeArguments.length; ++i) {
655                                    if (i>0) {
656                                            sb.append(", ");
657                                    }
658                                    sb.append(actualTypeArguments[i].toJavaDeclaration());
659                            }
660                            sb.append(">");
661                            return sb.toString();
662                    }               
663                    
664            }
665            
666        private static class PathEntry {
667            Type sType;
668            Class<?> clazz;
669            public PathEntry(Type type, Class<?> clazz) {
670                super();
671                if (clazz==null) {
672                    throw new IllegalArgumentException();
673                }
674                sType = type;
675                this.clazz = clazz;
676            }
677            
678            void bind(_Type[] typeParameters) {
679                    TypeVariable<?>[] ctp = clazz.getTypeParameters();
680                    Type[] atp = ((ParameterizedType) sType).getActualTypeArguments();
681                    for (int i=0; i<ctp.length; ++i) {
682                            _TypeVariable from = (_TypeVariable) _Type.createType(ctp[i]);
683                            _Type to = _Type.createType(atp[i]);
684                            for (int j=0; j<typeParameters.length; ++j) {
685                                    if (from.equals(typeParameters[j])) {
686                                            typeParameters[j] = to;
687                                    } else {
688                                            typeParameters[j].bind(from, to);
689                                    }
690                            }
691                    }
692            }
693     
694            @Override
695            public String toString() {
696                TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
697                return "PathEntry[type = "+sType+", class = "+clazz+", type parameters = "+(typeParameters==null ? "(no type parameters)" : Arrays.toString(typeParameters))+"]";
698            }
699        }
700     
701        private static <S> List<List<PathEntry>> inheritancePaths(Class<? extends S> from, Class<S> to) {
702            List<List<PathEntry>> ret = new ArrayList<List<PathEntry>>();
703            if (from.equals(to)) {
704                ret.add(new ArrayList<PathEntry>());
705            } else if (to.isAssignableFrom(from)) {
706                Class<?> fromSuperClass = from.getSuperclass();
707                if (fromSuperClass!=null) {
708                                    for (List<PathEntry> path: inheritancePaths((Class<? extends S>) fromSuperClass, to))  {
709                            Type gsc = from.getGenericSuperclass();
710                            Class<? extends S> sc = (Class<? extends S>) from.getSuperclass();
711                            if (sc!=null) {
712                                path.add(new PathEntry(gsc, sc));
713                                ret.add(path);
714                            }
715                        }
716                }
717                
718                Type[] gi = from.getGenericInterfaces();
719                Class[] si = from.getInterfaces();
720                for (int i=0; i<gi.length; ++i) {
721                    for (List<PathEntry> path: inheritancePaths((Class<? extends S>) si[i], to))  {
722                        path.add(new PathEntry(gi[i], si[i]));
723                        ret.add(path);
724                    }
725                }
726            }
727     
728            return ret;
729        }
730    
731        public static class ConvertersBundle extends AbstractReflectiveAtomicConvertersBundle {
732            
733                    @ConverterMethod
734            public WriterRenderer toWriterRenderer(final CompositePredicate predicate) {
735                    return new WriterRenderer() {
736                                    
737                                    @Override
738                                    public boolean render(
739                                                    Writer out, 
740                                                    Map<String, Object> environment,
741                                                    Context context, 
742                                                    String profile, 
743                                                    Locale locale, 
744                                                    File outputDir) throws RenderingException {
745                                            
746                                            if (!JAVA_INLINE.equals(profile)) {
747                                                    return false;
748                                            }
749                                            
750                                            try {
751                                                    out.write("new "+predicate.getClass().getCanonicalName()+"(0, null, ");
752                                                    Iterator<Predicate> pit = predicate.getParts().iterator();
753                                                    while (pit.hasNext()) {
754                                                            Predicate part = pit.next();
755                                                            WriterRenderer pwr = ConvertingService.convert(part, WriterRenderer.class);
756                                                            if (pwr==null) {
757                                                                    throw new RenderingException("Cannot render "+part.getClass().getName());
758                                                            }
759                                                            if (!pwr.render(out, environment, context, profile, locale, outputDir)) {
760                                                                    throw new RenderingException("Cannot render "+part.getClass().getName());                                                               
761                                                            }
762                                                            if (pit.hasNext()) {
763                                                                    out.write(", ");
764                                                            }
765                                                    }
766                                                    out.write(")");
767                                            } catch (IOException e) {
768                                                    throw new RenderingException(e);
769                                            }
770                                            
771                                            return true;
772                                    }
773                            };
774            }
775    
776            @ConverterMethod
777            public WriterRenderer toWriterRenderer(final BinaryExtractor extractor) {
778                    return new WriterRenderer() {
779                                    
780                                    @Override
781                                    public boolean render(
782                                                    Writer out, 
783                                                    Map<String, Object> environment,
784                                                    Context context, 
785                                                    String profile, 
786                                                    Locale locale, 
787                                                    File outputDir) throws RenderingException {
788                                            
789                                            if (!JAVA_INLINE.equals(profile)) {
790                                                    return false;
791                                            }
792                                            
793                                            try {
794                                                    out.write("new "+extractor.getClass().getCanonicalName()+"(0, null, ");
795                                                    Extractor leftExtractor = extractor.getLeftExtractor();
796                                                    WriterRenderer lwr = ConvertingService.convert(leftExtractor, WriterRenderer.class);
797                                                    if (lwr==null) {
798                                                            throw new RenderingException("Cannot render "+leftExtractor.getClass().getName());
799                                                    }
800                                                    if (!lwr.render(out, environment, context, profile, locale, outputDir)) {
801                                                            throw new RenderingException("Cannot render "+leftExtractor.getClass().getName());                                                              
802                                                    }
803                                                    out.write(", ");
804                                                    Extractor rightExtractor = extractor.getRightExtractor();
805                                                    WriterRenderer rwr = ConvertingService.convert(rightExtractor, WriterRenderer.class);
806                                                    if (rwr==null) {
807                                                            throw new RenderingException("Cannot render "+rightExtractor.getClass().getName());
808                                                    }
809                                                    if (!rwr.render(out, environment, context, profile, locale, outputDir)) {
810                                                            throw new RenderingException("Cannot render "+rightExtractor.getClass().getName());                                                             
811                                                    }
812                                                    
813                                                    if (extractor instanceof Equal) {
814                                                            out.write(", " + ((Equal) extractor).isIdentity());
815                                                    } else if (extractor instanceof NotEqual) {
816                                                            out.write(", " + ((NotEqual) extractor).isIdentity());
817                                                    }
818                                                    
819                                                    out.write(")");
820                                            } catch (IOException e) {
821                                                    throw new RenderingException(e);
822                                            }
823                                            
824                                            return true;
825                                    }
826                            };
827            }
828    
829            @ConverterMethod
830            public WriterRenderer toWriterRenderer(final Constant extractor) {
831                    return new WriterRenderer() {
832                                    
833                                    @Override
834                                    public boolean render(
835                                                    Writer out, 
836                                                    Map<String, Object> environment,
837                                                    Context context, 
838                                                    String profile, 
839                                                    Locale locale, 
840                                                    File outputDir) throws RenderingException {
841                                            
842                                            if (!JAVA_INLINE.equals(profile)) {
843                                                    return false;
844                                            }
845                                            
846                                            try {
847                                                    out.write("new "+extractor.getClass().getCanonicalName()+"(");
848                                                    if (extractor.getValue() == null) {
849                                                            out.write("null");
850                                                    } else if (extractor.getValue() instanceof String) {
851                                                            out.write("\"");
852                                                            out.write(StringEscapeUtils.escapeJava(extractor.getValue().toString()));
853                                                            out.write("\"");
854                                                    } else if (extractor.getValue() instanceof Character) {
855                                                            out.write("\'");
856                                                            out.write(StringEscapeUtils.escapeJava(extractor.getValue().toString()));
857                                                            out.write("\'");                                                        
858                                                    } else {
859                                                            out.write("("+extractor.getValue().getClass().getCanonicalName()+") ");
860                                                            out.write(String.valueOf(extractor.getValue()));                                                        
861                                                    }                                               
862                                                    out.write(")");
863                                            } catch (IOException e) {
864                                                    throw new RenderingException(e);
865                                            }
866                                            
867                                            return true;
868                                    }
869                            };
870            }
871            
872            @ConverterMethod
873            public WriterRenderer toWriterRenderer(final False extractor) {
874                    return new WriterRenderer() {
875                                    
876                                    @Override
877                                    public boolean render(
878                                                    Writer out, 
879                                                    Map<String, Object> environment,
880                                                    Context context, 
881                                                    String profile, 
882                                                    Locale locale, 
883                                                    File outputDir) throws RenderingException {
884                                            
885                                            if (!JAVA_INLINE.equals(profile)) {
886                                                    return false;
887                                            }
888                                            
889                                            try {
890                                                    out.write(False.class.getCanonicalName()+".getInstance()");
891                                            } catch (IOException e) {
892                                                    throw new RenderingException(e);
893                                            }
894                                            
895                                            return true;
896                                    }
897                            };
898            }
899            
900            @ConverterMethod
901            public WriterRenderer toWriterRenderer(final IndexedExtractor extractor) {
902                    return new WriterRenderer() {
903                                    
904                                    @Override
905                                    public boolean render(
906                                                    Writer out, 
907                                                    Map<String, Object> environment,
908                                                    Context context, 
909                                                    String profile, 
910                                                    Locale locale, 
911                                                    File outputDir) throws RenderingException {
912                                            
913                                            if (!JAVA_INLINE.equals(profile)) {
914                                                    return false;
915                                            }
916                                            
917                                            try {
918                                                    out.write("new "+extractor.getClass().getCanonicalName()+"(");
919                                                    out.write(String.valueOf(extractor.getIndex()));
920                                                    out.write(")");
921                                            } catch (IOException e) {
922                                                    throw new RenderingException(e);
923                                            }
924                                            
925                                            return true;
926                                    }
927                            };
928            }
929            
930            @ConverterMethod
931            public WriterRenderer toWriterRenderer(final InstanceOfPredicate extractor) {
932                    return new WriterRenderer() {
933                                    
934                                    @Override
935                                    public boolean render(
936                                                    Writer out, 
937                                                    Map<String, Object> environment,
938                                                    Context context, 
939                                                    String profile, 
940                                                    Locale locale, 
941                                                    File outputDir) throws RenderingException {
942                                            
943                                            if (!JAVA_INLINE.equals(profile)) {
944                                                    return false;
945                                            }
946                                            
947                                            try {
948                                                    out.write("new "+extractor.getClass().getCanonicalName()+"(");
949                                                    WriterRenderer er = ConvertingService.convert(extractor.getExtractor(), WriterRenderer.class);
950                                                    if (er==null) {
951                                                            throw new RenderingException("Cannot render "+extractor.getExtractor().getClass().getName());
952                                                    }
953                                                    if (!er.render(out, environment, context, profile, locale, outputDir)) {
954                                                            throw new RenderingException("Cannot render "+extractor.getExtractor().getClass().getName());                                                           
955                                                    }
956                                                    out.write(", ");
957                                                    out.write(extractor.getInstanceType().getName()+".class");
958                                                    out.write(")");
959                                            } catch (IOException e) {
960                                                    throw new RenderingException(e);
961                                            }
962                                            
963                                            return true;
964                                    }
965                            };
966            }
967            
968            @ConverterMethod
969            public WriterRenderer toWriterRenderer(final MappedExtractor extractor) {
970                    return new WriterRenderer() {
971                                    
972                                    @Override
973                                    public boolean render(
974                                                    Writer out, 
975                                                    Map<String, Object> environment,
976                                                    Context context, 
977                                                    String profile, 
978                                                    Locale locale, 
979                                                    File outputDir) throws RenderingException {
980                                            
981                                            if (!JAVA_INLINE.equals(profile)) {
982                                                    return false;
983                                            }
984                                            
985                                            try {
986                                                    out.write("new "+extractor.getClass().getCanonicalName()+"(");
987                                                    WriterRenderer ewr = ConvertingService.convert(extractor.getTarget(), WriterRenderer.class);
988                                                    if (ewr==null) {
989                                                            throw new RenderingException("Cannot render "+extractor.getTarget().getClass().getName());
990                                                    }
991                                                    if (!ewr.render(out, environment, context, profile, locale, outputDir)) {
992                                                            throw new RenderingException("Cannot render "+extractor.getTarget().getClass().getName());                                                              
993                                                    }
994                                                    
995                                                    out.write(", new int[] {");
996                                                    int[] map = extractor.getMap();
997                                                    for (int i=0; i<map.length; ++i) {
998                                                            if (i>0) {
999                                                                    out.write(", ");
1000                                                            }
1001                                                            out.write(String.valueOf(map[i]));
1002                                                    }
1003                                                    
1004                                                    out.write("})");
1005                                            } catch (IOException e) {
1006                                                    throw new RenderingException(e);
1007                                            }
1008                                            
1009                                            return true;
1010                                    }
1011                            };
1012            }
1013            
1014    //      @ConverterMethod
1015    //      public WriterRenderer toWriterRenderer(final MethodExtractor extractor) {
1016    //              return new WriterRenderer() {
1017    //                              
1018    //                              @Override
1019    //                              public boolean render(
1020    //                                              Writer out, 
1021    //                                              Map<String, Object> environment,
1022    //                                              Context context, 
1023    //                                              String profile, 
1024    //                                              Locale locale, 
1025    //                                              File outputDir) throws RenderingException {
1026    //                                      
1027    //                                      if (!JAVA_INLINE.equals(profile)) {
1028    //                                              return false;
1029    //                                      }
1030    //                                      
1031    //                                      try {
1032    //                                              out.write("new "+extractor.getClass().getCanonicalName()+"(");
1033    //                                              
1034    //                                              out.write("777~~~Not implemented~~~777");
1035    //                                              
1036    //                                              out.write(")");
1037    //                                      } catch (IOException e) {
1038    //                                              throw new RenderingException(e);
1039    //                                      }
1040    //                                      
1041    //                                      return true;
1042    //                              }
1043    //                      };
1044    //      }
1045            
1046            @ConverterMethod
1047            public WriterRenderer toWriterRenderer(final Not extractor) {
1048                    return new WriterRenderer() {
1049                                    
1050                                    @Override
1051                                    public boolean render(
1052                                                    Writer out, 
1053                                                    Map<String, Object> environment,
1054                                                    Context context, 
1055                                                    String profile, 
1056                                                    Locale locale, 
1057                                                    File outputDir) throws RenderingException {
1058                                            
1059                                            if (!JAVA_INLINE.equals(profile)) {
1060                                                    return false;
1061                                            }
1062                                            
1063                                            try {
1064                                                    out.write("new "+extractor.getClass().getCanonicalName()+"(");
1065                                                    
1066                                                    WriterRenderer ewr = ConvertingService.convert(extractor.getPredicate(), WriterRenderer.class);
1067                                                    if (ewr==null) {
1068                                                            throw new RenderingException("Cannot render "+extractor.getPredicate().getClass().getName());
1069                                                    }
1070                                                    if (!ewr.render(out, environment, context, profile, locale, outputDir)) {
1071                                                            throw new RenderingException("Cannot render "+extractor.getPredicate().getClass().getName());                                                           
1072                                                    }
1073                                                    
1074                                                    out.write(")");
1075                                            } catch (IOException e) {
1076                                                    throw new RenderingException(e);
1077                                            }
1078                                            
1079                                            return true;
1080                                    }
1081                            };
1082            }
1083            
1084    //      @ConverterMethod
1085    //      public WriterRenderer toWriterRenderer(final TimeIntervalPredicate extractor) {
1086    //              return new WriterRenderer() {
1087    //                              
1088    //                              @Override
1089    //                              public boolean render(
1090    //                                              Writer out, 
1091    //                                              Map<String, Object> environment,
1092    //                                              Context context, 
1093    //                                              String profile, 
1094    //                                              Locale locale, 
1095    //                                              File outputDir) throws RenderingException {
1096    //                                      
1097    //                                      if (!JAVA_INLINE.equals(profile)) {
1098    //                                              return false;
1099    //                                      }
1100    //                                      
1101    //                                      try {
1102    //                                              out.write("new "+extractor.getClass().getCanonicalName()+"(");
1103    //                                              
1104    //                                              out.write("777~~~Not implemented~~~777");
1105    //                                              
1106    //                                              out.write(")");
1107    //                                      } catch (IOException e) {
1108    //                                              throw new RenderingException(e);
1109    //                                      }
1110    //                                      
1111    //                                      return true;
1112    //                              }
1113    //                      };
1114    //      }
1115            
1116            @ConverterMethod
1117            public WriterRenderer toWriterRenderer(final True extractor) {
1118                    return new WriterRenderer() {
1119                                    
1120                                    @Override
1121                                    public boolean render(
1122                                                    Writer out, 
1123                                                    Map<String, Object> environment,
1124                                                    Context context, 
1125                                                    String profile, 
1126                                                    Locale locale, 
1127                                                    File outputDir) throws RenderingException {
1128                                            
1129                                            if (!JAVA_INLINE.equals(profile)) {
1130                                                    return false;
1131                                            }
1132                                            
1133                                            try {
1134                                                    out.write(True.class.getCanonicalName()+".getInstance()");
1135                                            } catch (IOException e) {
1136                                                    throw new RenderingException(e);
1137                                            }
1138                                            
1139                                            return true;
1140                                    }
1141                            };
1142            }
1143            
1144            @ConverterMethod
1145            public WriterRenderer toWriterRenderer(final JavaExtractor extractor) {
1146                    return new WriterRenderer() {
1147                                    
1148                                    @Override
1149                                    public boolean render(
1150                                                    Writer out, 
1151                                                    Map<String, Object> environment,
1152                                                    Context context, 
1153                                                    String profile, 
1154                                                    Locale locale, 
1155                                                    File outputDir) throws RenderingException {
1156                                            
1157                                            if (!JAVA_INLINE.equals(profile)) {
1158                                                    return false;
1159                                            }
1160                                            
1161                                            try {
1162                                                    // Try to find existing compiled predicate/extractor
1163                                                    BindHelper bindHelper = (BindHelper) environment.get(BIND_HELPER);
1164                                                    String packageName = (String) environment.get(BINDER_CLASS_PACKAGE);
1165                                                    for (JavaExtractorEntry jee: (Collection<JavaExtractorEntry>) bindHelper.getJavaExtractorEntries()) {
1166                                                            if (packageName.equals(jee.getPackageName())) {
1167                                                                    ComparisonResult cr = jee.getJavaExtractor().compareTo(extractor); // TODO - verify right order.
1168                                                                    if (cr!=null) {
1169                                                                            switch (cr.getType()) {
1170                                                                            case OPPOSITE:
1171                                                                                    out.write("new "+Not.class.getCanonicalName()+"(");
1172                                                                            case EQUAL:
1173                                                                                    out.write("new "+jee.getClassName()+"(");
1174                                                                                    if (cr.getIndexMap()==null) {
1175                                                                                            out.write("null");
1176                                                                                    } else {
1177                                                                                            out.write("new int[] {");
1178                                                                                            for (int i=0; i<cr.getIndexMap().length; ++i) {
1179                                                                                                    if (i>0) {
1180                                                                                                            out.write(", ");
1181                                                                                                    }
1182                                                                                                    out.write(String.valueOf(i));
1183                                                                                            }
1184                                                                                            out.write("}");
1185                                                                                    }
1186                                                                                    out.write(")");
1187    
1188                                                                                    if (ComparisonResult.Type.OPPOSITE.equals(cr.getType())) {
1189                                                                                            out.write(")");
1190                                                                                    }
1191                                                                                    
1192                                                                                    return true;
1193                                                                            }
1194                                                                    }
1195                                                            }
1196                                                    }
1197                                                    
1198                                                    File outputFile;
1199                                                    int i = 0;
1200                                                    do {
1201                                                            outputFile = new File(outputDir, packageName.replace('.', File.separatorChar)+File.separator+prefix(extractor)+(++i)+".java");
1202                                                    } while (outputFile.exists());
1203                                                    
1204                                                    outputFile.getParentFile().mkdirs();
1205                                                    
1206                                                    JavaExtractorEntry newJee = new JavaExtractorEntry(extractor, packageName, prefix(extractor)+i);
1207                                                    bindHelper.getJavaExtractorEntries().add(newJee);
1208                                                    
1209                                                    File identityFile = new File(outputFile.getParentFile(), prefix(extractor)+i+".identity");
1210                                                    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(identityFile));
1211                                                    try {
1212                                                            oos.writeObject(extractor.getIdentity());
1213                                                    } finally {
1214                                                            oos.close();
1215                                                    }
1216                                                    
1217                                                    String prefix = getClass().getPackage().getName().replace('.', '/');
1218                                                    JxpPageSource pageSource = new ResourceStreamPageSource("/"+prefix);            
1219                                                    JxpContext jxpContext = new JxpContext(pageSource);
1220                                                    JxpProcessor processor = new JxpProcessor(jxpContext);
1221                                                    Writer writer = new FileWriter(outputFile);
1222                                                    CompileHelper compileHelper = new CompileHelper(packageName, prefix(extractor)+i, bindHelper.getContextType(), extractor);
1223                                                    environment.put("compileHelper", compileHelper);
1224                                                    processor.process("JavaExtractor.jxp", writer, environment);
1225                                                    writer.close();
1226    
1227                                                    out.write("new "+prefix(extractor)+i+"(null)");
1228                                            } catch (Exception e) {
1229                                                    throw new RenderingException(e);
1230                                            }
1231                                            
1232                                            return true;
1233                                    }
1234                            };
1235            }
1236            
1237            private static String prefix(Extractor extractor) {
1238                    return extractor instanceof Predicate ? "Predicate" : "Extractor";
1239            }
1240            
1241    //      /**
1242    //       * For testing.
1243    //       * @param extractor
1244    //       * @return
1245    //       */
1246    //      @ConverterMethod
1247    //      public WriterRenderer toWriterRenderer(final Extractor extractor) {
1248    //              return new WriterRenderer() {
1249    //                              
1250    //                              @Override
1251    //                              public boolean render(
1252    //                                              Writer out, 
1253    //                                              Map<String, Object> environment,
1254    //                                              Context context, 
1255    //                                              String profile, 
1256    //                                              Locale locale, 
1257    //                                              File outputDir) throws RenderingException {
1258    //                                      
1259    //                                      if (!JAVA_INLINE.equals(profile)) {
1260    //                                              return false;
1261    //                                      }
1262    //                                      
1263    //                                      try {
1264    //                                              out.write(extractor.toString());
1265    //                                      } catch (IOException e) {
1266    //                                              throw new RenderingException(e);
1267    //                                      }
1268    //                                      
1269    //                                      return true;
1270    //                              }
1271    //                      };
1272    //      }
1273            
1274        }
1275        
1276        private static class CompileHelper {
1277            
1278            private String packageName;
1279                    private String className;
1280                    private _Type[] extractorTypeParameters;                
1281                    private JavaExtractor javaExtractor;
1282                    
1283                    public CompileHelper(String packageName, String className, Class<?> contextType, JavaExtractor javaExtractor) {
1284                            this.packageName = packageName;
1285                            this.className = className;
1286                            this.javaExtractor = javaExtractor;
1287                            
1288                            TypeVariable<Class<Extractor>>[] etp = Extractor.class.getTypeParameters();
1289                            extractorTypeParameters = new _Type[etp.length];
1290                            for (int i=0; i< etp.length; ++i) {
1291                                    extractorTypeParameters[i] = _Type.createType(etp[i]);
1292                            }
1293                            
1294                            List<List<PathEntry>> iPaths = inheritancePaths(javaExtractor.getClass(), Extractor.class);
1295                            Collections.sort(iPaths, new Comparator<List<PathEntry>>() {
1296    
1297                                    @Override
1298                                    public int compare(List<PathEntry> o1, List<PathEntry> o2) {
1299                                            int sizeDelta =  o1.size() - o2.size();
1300                                            if (sizeDelta!=0) {
1301                                                    return sizeDelta;
1302                                            }
1303                                            return o1.hashCode() - o2.hashCode();
1304                                    }
1305                            });
1306                            
1307                    for (List<PathEntry> path: iPaths) {
1308                            for (PathEntry pe: path) {
1309                                    pe.bind(extractorTypeParameters);
1310                            }
1311                        break;
1312                    }
1313    
1314                    _Type tType = extractorTypeParameters[0]; // T
1315                    if (tType instanceof _TypeVariable) {
1316                            _Class to = new _Class(Object.class);
1317                            for (int i=0; i<extractorTypeParameters.length; ++i) {
1318                                    if (tType.equals(extractorTypeParameters[i])) {
1319                                            extractorTypeParameters[i] = to;
1320                                    } else {
1321                                            extractorTypeParameters[i].bind((_TypeVariable) tType, to);
1322                                    }
1323                            }
1324                    }
1325                    
1326                    _Type vType = extractorTypeParameters[1]; // V
1327                    if (vType instanceof _TypeVariable) { // redundant
1328                            _Class to = new _Class(javaExtractor instanceof Predicate ? Boolean.class : Object.class);
1329                            for (int i=0; i<extractorTypeParameters.length; ++i) {
1330                                    if (vType.equals(extractorTypeParameters[i])) {
1331                                            extractorTypeParameters[i] = to;
1332                                    } else {
1333                                            extractorTypeParameters[i].bind((_TypeVariable) vType, to);
1334                                    }
1335                            }
1336                    }
1337                            
1338                    _Type cType = extractorTypeParameters[2]; // C
1339                    if (cType instanceof _TypeVariable) {
1340                            _Class to = new _Class(contextType);
1341                            for (int i=0; i<extractorTypeParameters.length; ++i) {
1342                                    if (cType.equals(extractorTypeParameters[i])) {
1343                                            extractorTypeParameters[i] = to;
1344                                    } else {
1345                                            extractorTypeParameters[i].bind((_TypeVariable) cType, to);
1346                                    }
1347                            }
1348                    }                                       
1349                    }
1350            
1351            public String getPackageName() {
1352                    return packageName;
1353            }
1354    
1355            public String getClassName() {
1356                    return className;
1357            }
1358            
1359            public String getT() {
1360                    return extractorTypeParameters[0].toJavaDeclaration();
1361            }
1362            
1363            public String getV() {
1364                    return extractorTypeParameters[1].toJavaDeclaration();                  
1365            }
1366            
1367            public String getC() {
1368                    return extractorTypeParameters[2].toJavaDeclaration();                  
1369            }
1370            
1371            public boolean isPredicate() {
1372                    return javaExtractor instanceof Predicate;
1373            }
1374            
1375            public boolean isContextDependent() {
1376                    return javaExtractor.isContextDependent();
1377            }
1378            
1379            public String getParameterIndicesCSV() {
1380                    StringBuilder ret = new StringBuilder();
1381                    for (Object pi: javaExtractor.parameterIndices()) {
1382                            if (ret.length()>0) {
1383                                    ret.append(", ");
1384                            }
1385                            ret.append(pi);
1386                    }
1387                    return ret.toString();
1388            }
1389    
1390            public Iterable<JavaExtractor.Parameter> getParameters() {
1391                    return javaExtractor.getParameters();
1392            }
1393            
1394            public String maybeCastParameter(JavaExtractor.Parameter parameter) {
1395                    Class<?> pType = parameter.getType();
1396                    Class<?> tType = ((_Class) extractorTypeParameters[0]).clazz;
1397                    if (pType.isAssignableFrom(tType)) {
1398                            return "";
1399                    }
1400                    
1401                            if (pType.isPrimitive()) {
1402                                    if (boolean.class.equals(pType)) {
1403                                            return "("+Boolean.class.getName()+")";
1404                                    } else if (char.class.equals(pType)) {
1405                                            return "("+Character.class.getName()+")";
1406                                    } else if (byte.class.equals(pType)) {
1407                                            return "("+Byte.class.getName()+")";
1408                                    } else if (short.class.equals(pType)) {
1409                                            return "("+Short.class.getName()+")";
1410                                    } else if (int.class.equals(pType)) {
1411                                            return "("+Integer.class.getName()+")";
1412                                    } else if (long.class.equals(pType)) {
1413                                            return "("+Long.class.getName()+")";
1414                                    } else if (float.class.equals(pType)) {
1415                                            return "("+Float.class.getName()+")";
1416                                    } else if (double.class.equals(pType)) {
1417                                            return "("+Double.class.getName()+")";
1418                                    } else if (void.class.equals(pType)) {
1419                                            throw new IllegalArgumentException("Parameter type cannot be void");
1420                                    }
1421                            } 
1422                    
1423                    return "("+pType.getCanonicalName()+")";
1424            }
1425            
1426            public String getExpression() {
1427                    return javaExtractor.getExpression();
1428            }
1429            
1430            public String getEscapedExpression() {
1431                    return StringEscapeUtils.escapeJava(javaExtractor.getExpression());
1432            }
1433        }
1434        
1435        /**
1436         * Compiles bindings for classes specified in the command line. Uses system properties for
1437         * token expansion.
1438         * @param args
1439         * @throws Exception
1440         */
1441        @SuppressWarnings("unchecked")
1442            public static void main(String[] args) throws Exception {
1443            System.out.println("Parameters: <output directory> <bus class> <handler classes>");
1444            JavaBinderCompiler compiler = new JavaBinderCompiler(new File(args[0]));
1445            Class<EventBus> busClass = (Class<EventBus>) Class.forName(args[1]);
1446                    TokenExpander tokenExpander = new TokenExpander(new TokenSource() {
1447    
1448                            @Override
1449                            public String getToken(String name) {
1450                                    return System.getProperty(name);
1451                            }
1452                            
1453                    });
1454            for (int i=2; i<args.length; ++i) {
1455                            Class<?> handlerClass = Class.forName(args[i]); 
1456                            System.out.println("Compiling Java binder for "+handlerClass.getName());
1457                            compiler.compileJavaBinder(handlerClass, busClass, null, tokenExpander);
1458            }
1459        }
1460    }