source: java/main/src/main/java/com/framsticks/params/ParamCandidate.java

Last change on this file was 193, checked in by Maciej Komosinski, 10 years ago

Set svn:eol-style native for all textual files

  • Property svn:eol-style set to native
File size: 15.5 KB
Line 
1package com.framsticks.params;
2
3import java.lang.reflect.AnnotatedElement;
4import java.lang.reflect.Array;
5import java.lang.reflect.Field;
6import java.lang.reflect.GenericArrayType;
7import java.lang.reflect.Member;
8import java.lang.reflect.Method;
9import java.lang.reflect.Modifier;
10import java.lang.reflect.ParameterizedType;
11import java.lang.reflect.Type;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Comparator;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.IdentityHashMap;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Map;
23
24import com.framsticks.params.annotations.FramsClassAnnotation;
25import com.framsticks.params.annotations.ParamAnnotation;
26import com.framsticks.params.types.ProcedureParam;
27// import com.framsticks.util.FramsticksException;
28
29public class ParamCandidate {
30
31        public class OneTime<T> {
32                protected final String name;
33                T value;
34
35                /**
36                 * @param name
37                 */
38                public OneTime(String name) {
39                        this.name = name;
40                }
41
42                final void set(T value) {
43                        if (this.value == null) {
44                                this.value = value;
45                                return;
46                        }
47                        if (!this.value.equals(value)) {
48                                throw new ConstructionException().msg("already set")
49                                        .arg("name", name)
50                                        .arg("in", ParamCandidate.this)
51                                        .arg("already", this.value)
52                                        .arg("now", value);
53                        }
54                }
55
56                public final T get() {
57                        return value;
58                }
59
60                public final boolean has() {
61                        return value != null;
62                }
63
64                @Override
65                public String toString() {
66                        return value == null ? "<null>" : value.toString();
67                }
68
69
70        }
71
72        protected final String id;
73        protected final OneTime<String> name = new OneTime<>("name");
74        protected final OneTime<Type> type = new OneTime<>("type");
75        protected final OneTime<Field> field = new OneTime<>("field");
76        protected final OneTime<Method> setter = new OneTime<>("setter");
77        protected final OneTime<Method> getter = new OneTime<>("getter");
78        protected final OneTime<Method> caller = new OneTime<>("caller");
79        protected final OneTime<Method> adder = new OneTime<>("adder");
80        protected final OneTime<Method> remover = new OneTime<>("remover");
81        protected final OneTime<Class<? extends Param>> paramType = new OneTime<>("paramType");
82        protected final OneTime<String> stringType = new OneTime<>("stringType");
83
84        protected int flags = 0;
85
86        protected final List<ParamAnnotation> annotations = new LinkedList<>();
87
88        /**
89         * @param id
90         */
91        public ParamCandidate(String id) {
92                this.id = id;
93        }
94
95        /**
96         * @return the id
97         */
98        public String getId() {
99                return id;
100        }
101
102        /**
103         * @return the name
104         */
105        public String getName() {
106                return name.get();
107        }
108
109        /**
110         * @return the type
111         */
112        public Type getType() {
113                return type.get();
114        }
115
116        public Class<?> getRawType() {
117                return getRawClass(type.get());
118        }
119
120        void setType(Type type) {
121                this.type.set(type);
122        }
123
124
125        /**
126         * @return the field
127         */
128        public Field getField() {
129                return field.get();
130        }
131
132        /**
133         * @return the setter
134         */
135        public Method getSetter() {
136                return setter.get();
137        }
138
139        /**
140         * @return the getter
141         */
142        public Method getGetter() {
143                return getter.get();
144        }
145
146        /**
147         * @return the getter
148         */
149        public Method getCaller() {
150                return caller.get();
151        }
152
153        /**
154         * @return the getter
155         */
156        public Method getAdder() {
157                return adder.get();
158        }
159
160        /**
161         * @return the getter
162         */
163        public Method getRemover() {
164                return remover.get();
165        }
166
167        /**
168         * @return the annotations
169         */
170        public List<ParamAnnotation> getAnnotations() {
171                return Collections.unmodifiableList(annotations);
172        }
173
174        protected final java.util.Set<Class<?>> dependantClasses = new HashSet<>();
175
176        // public void addDependantClass(Class<?> javaClass) {
177        //      dependantClasses.add(javaClass);
178        // }
179
180        /**
181         * @return the dependantClasses
182         */
183        public java.util.Set<Class<?>> getDependantClasses() {
184                return Collections.unmodifiableSet(dependantClasses);
185        }
186
187        void validate() throws ConstructionException {
188                try {
189                        if (adder.has() != remover.has()) {
190                                throw new ConstructionException().msg("only one of event manipulator methods is defined");
191                        }
192                        if (adder.has() && remover.has()) {
193                                return;
194                        }
195                        if (caller.has()) {
196                                if (!isPublic(caller)) {
197                                        throw new ConstructionException().msg("method is not public");
198                                }
199                                if (getter.has() || setter.has()) {
200                                        throw new ConstructionException().msg("getter or setter coexist with caller");
201                                }
202                                return;
203                        }
204                        if (isPublic(field)) {
205                                if (getter.has()) {
206                                        throw new ConstructionException().msg("getter and public field coexist");
207                                }
208                                return;
209                        }
210                        if (isPublic(field)) {
211                                if (setter.has()) {
212                                        throw new ConstructionException().msg("setter and field coexist");
213                                }
214                        }
215
216                        if (!getter.has() && !field.has()) {
217                                throw new ConstructionException().msg("missing getter or field");
218                        }
219                        if (getter.has() || field.has() || setter.has()) {
220                                if (type.get().equals(Void.TYPE)) {
221                                        throw new ConstructionException().msg("type of field is void");
222                                }
223                        }
224                } catch (ConstructionException e) {
225                        throw e.arg("in", this);
226                }
227        }
228
229        boolean isFinal() {
230                if (caller.has()) {
231                        return false;
232                }
233                if (adder.has() || remover.has()) {
234                        return false;
235                }
236                if (field.has()) {
237                        return Modifier.isFinal(field.get().getModifiers());
238                }
239                if (setter.has()) {
240                        return false;
241                }
242                if (Collection.class.isAssignableFrom(getRawType())) {
243                        return false;
244                }
245                return true;
246        }
247
248        boolean isReadOnly() {
249                if (caller.has()) {
250                        return false;
251                }
252                if (adder.has() || remover.has()) {
253                        return false;
254                }
255                if (Collection.class.isAssignableFrom(getRawType())) {
256                        return false;
257                }
258                if (isPublic(setter)) {
259                        return false;
260                }
261                if (isPublic(field)) {
262                        return Modifier.isFinal(field.get().getModifiers());
263                }
264                return true;
265        }
266
267        // public static <T extends Param> boolean isParamAnnorationOfTypeOrUnspecified(ParamAnnotation paramAnnotation, Class<T> paramType) {
268        //      return paramAnnotation.paramType().equals(Param.class) || paramAnnotation.paramType().equals(paramType);
269        // }
270
271        // public static <T extends Param> boolean isParamAnnorationOfType(ParamAnnotation paramAnnotation, Class<T> paramType) {
272        //      return paramAnnotation.paramType().equals(paramType);
273        // }
274
275        void add(ParamAnnotation paramAnnotation, Member member, String name) {
276                this.name.set(name);
277                annotations.add(paramAnnotation);
278                flags |= paramAnnotation.flags();
279                if (!paramAnnotation.paramType().equals(Param.class)) {
280                        paramType.set(paramAnnotation.paramType());
281                }
282                if (!"".equals(paramAnnotation.stringType())) {
283                        stringType.set(paramAnnotation.stringType());
284                }
285                if (member instanceof Field) {
286                        field.set((Field) member);
287                        setType(field.get().getGenericType());
288                        return;
289                }
290                if (member instanceof Method) {
291                        Method m = (Method) member;
292                        if (paramAnnotation.paramType().equals(ProcedureParam.class)) {
293                                caller.set(m);
294                                return;
295                        }
296                        Type[] ps = m.getGenericParameterTypes();
297                        Class<?>[] pts = m.getParameterTypes();
298                        if (ps.length == 0) {
299                                if (m.getReturnType().equals(Void.TYPE)) {
300                                        throw new ConstructionException().msg("failed to add getter of void return type");
301                                }
302                                getter.set(m);
303                                setType(m.getGenericReturnType());
304                                return;
305                        }
306                        if (ps.length == 1) {
307                                if (pts[0].equals(EventListener.class)) {
308                                        if (member.getName().startsWith("add")) {
309                                                adder.set(m);
310                                                setType(ps[0]);
311                                                return;
312                                        }
313                                        if (member.getName().startsWith("remove")) {
314                                                remover.set(m);
315                                                setType(ps[0]);
316                                                return;
317                                        }
318                                        throw new ConstructionException().msg("invalid name of event manipulator").arg("method", m).arg("in", this);
319                                }
320                                setter.set(m);
321                                setType(ps[0]);
322                                return;
323                        }
324                        throw new ConstructionException().msg("invalid number of arguments").arg("method", m).arg("in", this);
325                }
326                throw new ConstructionException().msg("invalid kind of member").arg("member", member).arg("in", this);
327        }
328
329        public boolean isPrimitive() {
330                return getRawType().isPrimitive();
331        }
332
333        public int getFlags() {
334                int f = flags;
335                if (isReadOnly()) {
336                        f |= ParamFlags.READONLY;
337                }
338                return f;
339        }
340
341        @Override
342        public String toString() {
343                return id + "(" + type.toString() + ")";
344        }
345
346        public static boolean isPublic(Member member) {
347                return Modifier.isPublic(member.getModifiers());
348        }
349
350        public static boolean isPublic(OneTime<? extends Member> v) {
351                return v.has() ? isPublic(v.get()) : false;
352        }
353
354        public static <M extends Member & AnnotatedElement> void filterParamsCandidates(Set set, M[] members) {
355                for (M m : members) {
356                        ParamAnnotation pa = m.getAnnotation(ParamAnnotation.class);
357                        if (pa == null) {
358                                continue;
359                        }
360                        String id = FramsClassBuilder.getId(pa, m);
361                        ParamCandidate pc = null;
362                        if (set.getCandidates().containsKey(id)) {
363                                pc = set.getCandidates().get(id);
364                        } else {
365                                pc = new ParamCandidate(id);
366                                set.getCandidates().put(id, pc);
367                                set.getOrder().add(pc);
368                        }
369                        pc.add(pa, m, FramsClassBuilder.getName(pa, m));
370                }
371        }
372
373        public static class Set {
374                protected final Map<String, ParamCandidate> candidates;
375                protected final List<ParamCandidate> order;
376                protected final java.util.Set<Class<?>> dependantClasses = new HashSet<>();
377                protected final java.util.Set<String> dependantClassesFromInfo = new HashSet<>();
378
379                /**
380                 * @param candidates
381                 * @param order
382                 */
383                public Set(Map<String, ParamCandidate> candidates, List<ParamCandidate> order) {
384                        this.candidates = candidates;
385                        this.order = order;
386                }
387
388                /**
389                 * @return the candidates
390                 */
391                public Map<String, ParamCandidate> getCandidates() {
392                        return candidates;
393                }
394
395                /**
396                 * @return the order
397                 */
398                public List<ParamCandidate> getOrder() {
399                        return order;
400                }
401
402                public java.util.Set<Class<?>> getDependentClasses() {
403                        return dependantClasses;
404                }
405
406                public java.util.Set<String> getDependentClassesFromInfo() {
407                        return dependantClassesFromInfo;
408                }
409        }
410
411        protected static final Map<Class<?>, Set> setsCache = Collections.synchronizedMap(new IdentityHashMap<Class<?>, Set>());
412
413        public static Set getAllCandidates(final Class<?> javaClass) throws ConstructionException {
414                Set result = setsCache.get(javaClass);
415                if (result != null) {
416                        return result;
417                }
418
419                List<Class<?>> javaClasses = new LinkedList<>();
420                Class<?> jc = javaClass;
421                while (jc != null) {
422                        javaClasses.add(0, jc);
423                        jc = jc.getSuperclass();
424                }
425
426                result = new Set(new HashMap<String, ParamCandidate>(), new LinkedList<ParamCandidate>());
427
428                for (Class<?> j : javaClasses) {
429                        Set set = new Set(result.getCandidates(), new LinkedList<ParamCandidate>());
430
431                        filterParamsCandidates(set, j.getDeclaredFields());
432                        filterParamsCandidates(set, j.getDeclaredMethods());
433
434                        FramsClassAnnotation fa = j.getAnnotation(FramsClassAnnotation.class);
435                        if (fa != null) {
436
437                                if (j != javaClass) {
438                                        result.dependantClasses.add(j);
439                                }
440                                for (Class<?> r : fa.register()) {
441                                        result.dependantClasses.add(r);
442                                }
443                                for (String i : fa.registerFromInfo()) {
444                                        result.dependantClassesFromInfo.add(i);
445                                }
446
447                                final List<String> order = Arrays.asList(fa.order());
448                                Collections.sort(set.getOrder(), new Comparator<ParamCandidate>() {
449                                        @Override
450                                        public int compare(ParamCandidate pc0, ParamCandidate pc1) {
451                                                int u0 = order.indexOf(pc0.getId());
452                                                int u1 = order.indexOf(pc1.getId());
453                                                if (u0 == -1 || u1 == -1) {
454                                                        return 0;
455                                                }
456                                                return u0 - u1;
457                                        }
458                                });
459                        }
460                        result.getOrder().addAll(0, set.getOrder());
461                }
462
463                for (ParamCandidate pc : result.getOrder()) {
464                        pc.validate();
465                        pc.induceParamType(Param.build());
466                        result.dependantClasses.addAll(pc.getDependantClasses());
467                }
468
469                setsCache.put(javaClass, result);
470
471                return result;
472        }
473
474        public static Class<?> getRawClass(final Type type) {
475                if (type == null) {
476                        throw new IllegalArgumentException();
477                }
478                if (Class.class.isInstance(type)) {
479                        return Class.class.cast(type);
480                }
481                if (ParameterizedType.class.isInstance(type)) {
482                        final ParameterizedType parameterizedType = ParameterizedType.class.cast(type);
483                        return getRawClass(parameterizedType.getRawType());
484                } else if (GenericArrayType.class.isInstance(type)) {
485                        GenericArrayType genericArrayType = GenericArrayType.class.cast(type);
486                        Class<?> c = getRawClass(genericArrayType.getGenericComponentType());
487                        return Array.newInstance(c, 0).getClass();
488                } else {
489                        return null;
490                }
491        }
492
493        protected ParamBuilder induceParamType(ParamBuilder builder, Type type) {
494                // if (type.equals(Void.TYPE)) {
495                //      throw new ConstructionException().msg("void is not a valid type");
496                // }
497
498
499                if (type instanceof ParameterizedType) {
500                        ParameterizedType p = (ParameterizedType) type;
501                        Type rawType = p.getRawType();
502                        Type containedType = null;
503                        boolean map = false;
504                        StringBuilder b = new StringBuilder();
505                        if (rawType.equals(Map.class)) {
506                                containedType = p.getActualTypeArguments()[1];
507                                map = true;
508                                b.append("l");
509                        } else if (rawType.equals(List.class)) {
510                                containedType = p.getActualTypeArguments()[0];
511                                b.append("l");
512                        } else if (rawType.equals(EventListener.class)) {
513                                containedType = p.getActualTypeArguments()[0];
514                                b.append("e");
515                        } else {
516                                return induceParamType(builder, rawType);
517                                // throw new FramsticksException().msg("unknown raw type").arg("raw type", rawType);
518                        }
519                        if (!(containedType instanceof Class)) {
520                                return builder;
521                        }
522                        b.append(" ");
523
524                        Class<?> containedClass = (Class<?>) containedType;
525                        FramsClassAnnotation fca = containedClass.getAnnotation(FramsClassAnnotation.class);
526                        if (fca == null) {
527                                throw new ConstructionException().msg("the contained class is not annotated").arg("class", containedClass);
528                        }
529                        dependantClasses.add(containedClass);
530                        b.append(FramsClassBuilder.getName(fca, containedClass));
531                        if (map) {
532                                b.append(" uid");
533                        }
534
535                        builder.type(b.toString());
536                        return builder;
537                }
538
539                if (type instanceof Class) {
540
541                        Class<?> cl = (Class<?>) type;
542
543                        // this is draft implementation of future support for enum
544                        // if (cl.isEnum()) {
545                        //      Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) cl;
546                        //      Enum<?>[] enums = enumType.getEnumConstants();
547                        //      StringBuilder b = new StringBuilder();
548
549                        //      b.append("d 0 ").append(enums.length - 1).append(" 0 ");
550                        //      for (Enum<?> e : enums) {
551                        //              b.append("~").append(e.name());
552                        //      }
553                        //      return b.toString();
554                        // }
555                        if (cl.equals(Integer.class) || cl.equals(int.class)) {
556                                builder.type("d");
557                                return builder;
558                        }
559                        if (cl.equals(String.class)) {
560                                builder.type("s");
561                                return builder;
562                        }
563                        if (cl.equals(Double.class) || cl.equals(double.class)) {
564                                builder.type("f");
565                                return builder;
566                        }
567                        if (cl.equals(Boolean.class) || cl.equals(boolean.class)) {
568                                builder.type( "d 0 1");
569                                return builder;
570                        }
571                        if (cl.equals(Object.class)) {
572                                builder.type("x");
573                                return builder;
574                        }
575
576
577                        // builder.type("o " + (cl).getCanonicalName());
578                        builder.type("o " + cl.getSimpleName());
579                        dependantClasses.add(cl);
580                        builder.fillStorageType(cl);
581                        return builder;
582                }
583
584                throw new ConstructionException().msg("failed to find framsticks for native type").arg("type", type);
585        }
586
587        public ParamBuilder induceParamType(ParamBuilder builder) {
588
589                if (stringType.has()) {
590                        return builder.type(stringType.get());
591                }
592
593                Method method = getCaller();
594                if (method == null) {
595                        if (paramType.has()) {
596                                return builder.type(paramType.get());
597                        }
598                        return induceParamType(builder, getType());
599                }
600
601                if (!method.getReturnType().equals(Void.TYPE)) {
602                        builder.resultType(induceParamType(Param.build(), method.getGenericReturnType()).finish(ValueParam.class));
603                }
604
605                List<ValueParam> arguments = new ArrayList<>();
606                int number = 0;
607                for (Type arg : method.getGenericParameterTypes()) {
608                        arguments.add(induceParamType(Param.build(), arg).idAndName("arg" + (number++)).finish(ValueParam.class));
609                }
610                builder.argumentsType(arguments);
611
612                return builder;
613        }
614
615};
Note: See TracBrowser for help on using the repository browser.