source: java/main/src/main/java/com/framsticks/params/ReflectionAccess.java @ 87

Last change on this file since 87 was 87, checked in by psniegowski, 11 years ago

HIGHLIGHTS:

  • FramsClass? and contained Param are now immutable classes (like String),

which allows to refer to them concurrently without synchronization
(which for example in turn simplifies GUI management)

  • also make Path immutable (which was earlier only assumed)
  • add global cache for FramsClasses? created solely and automatically

on base of Java classes.

representations basing on given FramsClass?

  • above changes greatly improved GUI responsivness during browsing
  • furtherly improve Param class hierarchy
  • allow to inject actions on state changes into MultiParamLoader?
  • add more tests

CHANGELOG:

Add StatusListener? to MultiParamLoader?.

Minor refactorization in MultiParamLoader?.

First step with auto append.

Add SchemaTest?.

Improve Registry.

Clean up in Registry.

Work out Registry.

Use annotations for Param.

Fix ListChange?.

Improve fluent interface of the FramsClassBuilder?.

Done caching of ReflectionAccess?.Backend

Fix hashCode of Pair.

A step on a way to cache ReflectionAccess?.Backend

Make SimpleAbstractAccess?.framsClass a final field.

Add static cache for FramsClasses? based on java.

Only classes created strictly and automatically
based on java classes are using this cache.

Make all Params immutable.

Many improvement to make Param immutable.

Make PrimitiveParam? generic type.

Several changes to make Param immutable.

Make FramsClass? immutable.

Another improvement to Path immutability.

Several improvements to Path.

Improve PathTest?.

Configurarable MutabilityDetector?.

File size: 10.2 KB
Line 
1package com.framsticks.params;
2
3import java.lang.reflect.Field;
4import java.lang.reflect.InvocationTargetException;
5import java.lang.reflect.Method;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.Map;
9
10import javax.annotation.concurrent.Immutable;
11
12import org.apache.log4j.Logger;
13
14import com.framsticks.params.annotations.AutoAppendAnnotation;
15import com.framsticks.util.FramsticksException;
16import com.framsticks.util.lang.Pair;
17
18import static com.framsticks.util.lang.Containers.*;
19
20/**
21 * The Class ReflectionAccess. Stores data in provided object using reflection.
22 *
23 * @author Mateusz Jarus <name.surname@gmail.com> (please replace name and
24 *         surname with my personal data)
25 *
26 * @author Piotr Sniegowski
27 */
28public class ReflectionAccess extends SimpleAbstractAccess {
29        private final static Logger log = Logger.getLogger(ReflectionAccess.class.getName());
30
31        protected final Class<?> reflectedClass;
32        protected final Backend backend;
33
34        private Object object;
35
36        @Immutable
37        public static class Backend {
38
39                protected static Map<Pair<Class<?>, FramsClass>, Backend> synchronizedCache = Collections.synchronizedMap(new HashMap<Pair<Class<?>, FramsClass>, Backend>());
40
41                public static class ReflectedValueParam {
42                        public ReflectedSetter setter;
43                        public ReflectedGetter getter;
44                }
45
46                public interface ReflectedSetter {
47                        public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
48                }
49
50                public interface ReflectedGetter {
51                        public abstract <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
52                }
53
54                protected final Map<ValueParam, ReflectedValueParam> params;
55                protected final Map<Class<?>, Method> autoAppendMethods;
56
57                /**
58                 * @param params
59                 */
60                public Backend(Map<ValueParam, ReflectedValueParam> params, Map<Class<?>, Method> autoAppendMethods) {
61                        // this.params = Collections.unmodifiableMap(params);
62                        this.params = params;
63                        this.autoAppendMethods = autoAppendMethods;
64                }
65
66                public static Backend getOrCreateFor(Class<?> reflectedClass, FramsClass framsClass) {
67
68                        Pair<Class<?>, FramsClass> id = new Pair<Class<?>, FramsClass>(reflectedClass, framsClass);
69                        Backend backend = synchronizedCache.get(id);
70                        if (backend != null) {
71                                return backend;
72                        }
73
74                        log.debug("constructing backend for " + id);
75                        final Map<ValueParam, ReflectedValueParam> params = new HashMap<>();
76
77                        Map<String, ParamCandidate> candidates = ParamCandidate.getAllCandidates(reflectedClass);
78
79                        try {
80                                for (final ValueParam vp : filterInstanceof(framsClass.getParamEntries(), ValueParam.class)) {
81                                        if (!candidates.containsKey(vp.getId())) {
82                                                throw new ConstructionException().msg("missing candidate for param").arg("param", vp);
83                                        }
84                                        ParamCandidate pc = candidates.get(vp.getId());
85                                        if (pc.isReadOnly() && !vp.hasFlag(Flags.READONLY)) {
86                                                throw new ConstructionException().msg("readonly state conflict").arg("param", vp);
87                                        }
88                                        if (!typeMatch(pc.getRawType(), vp.getStorageType())) {
89                                                throw new ConstructionException().msg("types mismatch for param").arg("param", vp).arg("candidate", pc.getType()).arg("storage", vp.getStorageType());
90                                        }
91
92                                        ReflectedValueParam rvp = new ReflectedValueParam();
93                                        params.put(vp, rvp);
94                                        final boolean primitive = pc.isPrimitive();
95                                        if (pc.getField() != null) {
96                                                final Field f = pc.getField();
97                                                rvp.getter = new ReflectedGetter() {
98                                                        @Override
99                                                        public <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException {
100                                                                return type.cast(f.get(object));
101                                                        }
102                                                };
103                                                if (!pc.isFinal()) {
104                                                        rvp.setter = new ReflectedSetter() {
105                                                                @Override
106                                                                public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException {
107                                                                        if (value == null && primitive) {
108                                                                                throw new FramsticksException().msg("setting null to primitive value");
109                                                                        }
110                                                                        f.set(object, value);
111                                                                }
112                                                        };
113                                                }
114                                        } else {
115                                                final Method g = pc.getGetter();
116
117                                                rvp.getter = new ReflectedGetter() {
118                                                        @Override
119                                                        public <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
120                                                                return type.cast(g.invoke(object));
121                                                        }
122                                                };
123
124                                                if (!pc.isFinal()) {
125                                                        final Method s = pc.getSetter();
126                                                        rvp.setter = new ReflectedSetter() {
127                                                                @Override
128                                                                public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
129                                                                        if (value == null && primitive) {
130                                                                                throw new FramsticksException().msg("setting null to primitive value");
131                                                                        }
132                                                                        s.invoke(object, value);
133                                                                }
134                                                        };
135                                                }
136                                        }
137                                }
138                        } catch (ConstructionException e) {
139                                throw e.arg("java class", reflectedClass).arg("framsClass", framsClass);
140                        }
141
142                        Map<Class<?>, Method> autoAppendMethods = new HashMap<>();
143
144                        Class<?> javaClass = reflectedClass;
145                        while (javaClass != null) {
146
147                                for (Method m : javaClass.getDeclaredMethods()) {
148                                        AutoAppendAnnotation a = m.getAnnotation(AutoAppendAnnotation.class);
149                                        if (a == null) {
150                                                continue;
151                                        }
152                                        Class<?>[] args = m.getParameterTypes();
153                                        if (args.length != 1) {
154                                                throw new ConstructionException().msg("invalid number of arguments in AutoAppend marked method").arg("method", m).arg("arguments", args.length);
155                                        }
156                                        autoAppendMethods.put(args[0], m);
157                                }
158
159                                javaClass = javaClass.getSuperclass();
160                        }
161
162                        backend = new Backend(params, autoAppendMethods);
163                        synchronizedCache.put(id, backend);
164                        return backend;
165                }
166
167        }
168
169        public ReflectionAccess(Class<?> reflectedClass) throws ConstructionException {
170                this(reflectedClass, FramsClass.build().forClass(reflectedClass));
171        }
172
173        public static boolean typeMatch(Class<?> a, Class<?> b) {
174                assert !b.isPrimitive();
175                if (!a.isPrimitive()) {
176                        return a.equals(b);
177                }
178
179                if (a.equals(int.class)) {
180                        return b.equals(Integer.class);
181                }
182                if (a.equals(double.class)) {
183                        return b.equals(Double.class);
184                }
185                if (a.equals(boolean.class)) {
186                        return b.equals(Boolean.class);
187                }
188                assert false;
189                return false;
190        }
191
192        public ReflectionAccess(Class<?> reflectedClass, FramsClass framsClass) throws ConstructionException {
193                super(framsClass);
194                this.reflectedClass = reflectedClass;
195                this.backend = Backend.getOrCreateFor(reflectedClass, framsClass);
196                // log.info("created ReflectionAccess " + this);
197        }
198
199        // private static String accessorName(boolean get, String id) {
200        //      return (get ? "get" : "set") + id.substring(0, 1).toUpperCase() + id.substring(1);
201        // }
202
203        @Override
204        public <T> T get(ValueParam param, Class<T> type) {
205                try {
206                        try {
207                                if (object == null) {
208                                        throw new FramsticksException().msg("no object set");
209                                }
210
211                                return backend.params.get(param).getter.get(object, type);
212                        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
213                                throw new FramsticksException().msg("failed to get").cause(e);
214                        }
215                } catch (FramsticksException e) {
216                        throw e.arg("param", param).arg("type", type).arg("access", this);
217                }
218        }
219
220        @Override
221        protected <T> void internalSet(ValueParam param, T value) {
222                setValue(param, value);
223        }
224
225        private <T> void setValue(ValueParam param, T value) {
226                try {
227                        try {
228                                if (object == null) {
229                                        throw new FramsticksException().msg("no object set");
230                                }
231                                Backend.ReflectedSetter s = backend.params.get(param).setter;
232                                if (s == null) {
233                                        throw new FramsticksException().msg("trying to set final");
234                                }
235                                s.set(object, value);
236                        } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
237                                throw new FramsticksException().msg("failed to set").cause(e);
238                        }
239                } catch (FramsticksException e) {
240                        throw e.arg("param", param).arg("value", value).arg("access", this);
241                }
242        }
243
244        void resetErrors() {
245                //TODO this replaces returnedObject.resetErrors();
246        }
247
248        @Override
249        public void clearValues() {
250                if (object == null) {
251                        return;
252                }
253
254                resetErrors();
255
256                try {
257                        for (ValueParam p : filterInstanceof(framsClass.getParamEntries(), ValueParam.class)) {
258                                setValue(p, p.getDef(Object.class));
259                        }
260                } catch (IllegalArgumentException ex) {
261                        ex.printStackTrace();
262                }
263        }
264
265        /**
266         * Sets the new object to operate on.
267         *
268         * @param object
269         *            new object to operate on
270         */
271        @Override
272        public ReflectionAccess select(Object object) {
273                assert object == null || reflectedClass.isInstance(object);
274                this.object = object;
275                return this;
276        }
277
278        @Override
279        public Object getSelected() {
280                return object;
281        }
282
283        // TODO: find a better place for it
284        public static String objectToString(Object object) {
285                StringBuilder b = new StringBuilder();
286                for (Field f : object.getClass().getFields()) {
287                        b.append(f.getName());
288                        b.append(":");
289                        try {
290                                Object value = f.get(object);
291                                b.append((value != null) ? value.toString() : "<null>");
292                        } catch (IllegalAccessException e) {
293                                e.printStackTrace();
294                        }
295                        b.append("\n");
296                }
297                return b.toString();
298        }
299
300        @Override
301        public ReflectionAccess cloneAccess() throws ConstructionException {
302                return new ReflectionAccess(reflectedClass, framsClass);
303        }
304
305        @Override
306        public Object createAccessee() {
307                try {
308                        return reflectedClass.newInstance();
309                } catch (InstantiationException | IllegalAccessException e) {
310                        e.printStackTrace();
311                }
312                log.fatal("failed to create reflected object of class " + reflectedClass.getCanonicalName() + " for frams type " + framsClass.getId());
313                return null;
314        }
315
316        @Override
317        public String toString() {
318                StringBuilder b = new StringBuilder();
319                b.append(framsClass);
320                if (object != null) {
321                        b.append("(").append(object).append(")");
322                }
323                return b.toString();
324        }
325
326        @Override
327        public boolean tryAutoAppend(Object value) {
328                assert object != null;
329                for (Map.Entry<Class<?>, Method> a : backend.autoAppendMethods.entrySet()) {
330                        if (a.getKey().isAssignableFrom(value.getClass())) {
331                                try {
332                                        a.getValue().invoke(object, value);
333                                        return true;
334                                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
335                                        throw new FramsticksException().msg("failed to auto append").cause(e).arg("value", value).arg("into object", object).arg("with method", a.getValue());
336                                }
337                        }
338                }
339                return false;
340        }
341}
342
Note: See TracBrowser for help on using the repository browser.