View Javadoc
1 /*
2 * Fields.java
3 * Created on September 15, 2003
4 *
5 * The Blues Framework - A lightweight application framework
6 * Copyright (C) 2003 Lonnie Pryor
7 * http://blues.lonniepryor.com
8 *
9 * This library is free software; you can redistribute it and/or modify it under the
10 * terms of the GNU Lesser General Public License as published by the Free Software
11 * Foundation; either version 2.1 of the License, or (at your option) any later
12 * version.
13 *
14 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License along
19 * with this library; if not, write to:
20 *
21 * The Free Software Foundation, Inc.
22 * 59 Temple Place, Suite 330
23 * Boston, MA 02111-1307 USA
24 *
25 */
26 package com.lonniepryor.blues.util;
27
28 import java.lang.reflect.Field;
29 import java.lang.reflect.Modifier;
30
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 /***
38 * Specification interface for identifying Fields. Instances of this class may be
39 * logically combined using the AND, OR, and NOT operations. Many common use-cases
40 * are supplied via static accessor methods.
41 *
42 * @author Lonnie Pryor
43 * @version $Revision: 1.1 $
44 */
45 public abstract class Fields {
46 /*** A specification matching public fields. */
47 private static final Fields declaredPublic = new Fields() {
48 public boolean evaluate (Field field) {
49 return Modifier.isPublic(field.getModifiers());
50 }
51 };
52 /*** A specification matching protected fields. */
53 private static final Fields declaredProtected = new Fields() {
54 public boolean evaluate (Field field) {
55 return Modifier.isProtected(field.getModifiers());
56 }
57 };
58 /*** A specification matching private fields. */
59 private static final Fields declaredPrivate = new Fields() {
60 public boolean evaluate (Field field) {
61 return Modifier.isPrivate(field.getModifiers());
62 }
63 };
64 /*** A specification matching package protected fields/ *//package-summary/html">color="#AA0000">* A specification matching package protected fields/ *//package-summary.html">nt color="#AA0000">/*** A specification matching package protected fields/ *//package-summary.html">color="#AA0000">* A specification matching package protected fields. */
65 private static final Fields declaredPackageProtected = declaredPublic.or(
66 declaredProtected).or(declaredPrivate).not();
67 /*** A specification matching final fields. */
68 private static final Fields declaredFinal = new Fields() {
69 public boolean evaluate (Field field) {
70 return Modifier.isFinal(field.getModifiers());
71 }
72 };
73 /*** A specification matching static fields. */
74 private static final Fields declaredStatic = new Fields() {
75 public boolean evaluate (Field field) {
76 return Modifier.isStatic(field.getModifiers());
77 }
78 };
79 /*** A specification matching transient fields. */
80 private static final Fields declaredTransient = new Fields() {
81 public boolean evaluate (Field field) {
82 return Modifier.isTransient(field.getModifiers());
83 }
84 };
85 /*** A specification matching volatile fields. */
86 private static final Fields declaredVolatile = new Fields() {
87 public boolean evaluate (Field field) {
88 return Modifier.isVolatile(field.getModifiers());
89 }
90 };
91 /*** Index of the modifier-based specifications by name. */
92 private static final Map modifiersByName;
93
94 /***
95 * Initalize the modifier name mapping.
96 */
97 static {
98 Map tmp = new HashMap(8);
99 tmp.put("public", declaredPublic);
100 tmp.put("protected", declaredProtected);
101 tmp.put("package", declaredPackageProtected);
102 tmp.put("private", declaredPrivate);
103 tmp.put("static", declaredStatic);
104 tmp.put("final", declaredFinal);
105 tmp.put("transient", declaredTransient);
106 tmp.put("volatile", declaredVolatile);
107 modifiersByName = Collections.unmodifiableMap(tmp);
108 }
109
110 /***
111 * Creates a new Fields object.
112 */
113 protected Fields () {
114 }
115
116 /***
117 * Returns a specification matching public fields.
118 *
119 * @return A specification matching public fields.
120 */
121 public static Fields declaredPublic () {
122 return declaredPublic;
123 }
124
125 /***
126 * Returns a specification matching protected fields.
127 *
128 * @return A specification matching protected fields.
129 */
130 public static Fields declaredProtected () {
131 return declaredProtected;
132 }
133
134 /***
135 * Returns a specification matching private fields.
136 *
137 * @return A specification matching private fields.
138 */
139 public static Fields declaredPrivate () {
140 return declaredPrivate;
141 }
142
143 /***
144 * Returns a specification matching package protected fields.
145 *
146 * @return A specification matching package protected fields.
147 */
148 public static Fields declaredPackageProtected () {
149 return declaredPackageProtected;
150 }
151
152 /***
153 * Returns a specification matching final fields.
154 *
155 * @return A specification matching final fields.
156 */
157 public static Fields declaredFinal () {
158 return declaredFinal;
159 }
160
161 /***
162 * Returns a specification matching static fields.
163 *
164 * @return A specification matching static fields.
165 */
166 public static Fields declaredStatic () {
167 return declaredStatic;
168 }
169
170 /***
171 * Returns a specification matching transient fields.
172 *
173 * @return A specification matching transient fields.
174 */
175 public static Fields declaredTransient () {
176 return declaredTransient;
177 }
178
179 /***
180 * Returns a specification matching volatile fields.
181 *
182 * @return A specification matching volatile fields.
183 */
184 public static Fields declaredVolatile () {
185 return declaredVolatile;
186 }
187
188 /***
189 * Creates a new specification satisfied by Fields equal to the supplied Field.
190 *
191 * @param toTest The Field to be equal to.
192 *
193 * @return A new specification satisfied by Fields equal to the supplied Field.
194 */
195 public static Fields equalTo (final Field toTest) {
196 if (toTest == null)
197 throw new NullPointerException("toTest");
198 return new Fields() {
199 public boolean evaluate (Field field) {
200 return toTest.equals(field);
201 }
202 };
203 }
204
205 /***
206 * Creates a new specification satisfied only by Fields declared on the Types
207 * that satisfy the supplied specification.
208 *
209 * @param specification The specification that Types must match.
210 *
211 * @return A new specification satisfied only by Fields declared on the Types
212 * that satisfy the supplied specification.
213 */
214 public static Fields declaredOn (final Types specification) {
215 if (specification == null)
216 throw new NullPointerException("specification");
217 return new Fields() {
218 public boolean evaluate (Field field) {
219 return specification.isSatisfiedBy(field.getDeclaringClass());
220 }
221 };
222 }
223
224 /***
225 * Creates a new specification satisfied by Fields whose names match the supplied
226 * pattern.
227 *
228 * @param namePattern The pattern names must match.
229 *
230 * @return A new specification satisfied by Fields whose names match the supplied
231 * pattern.
232 *
233 * @throws ParseException if the name pattern is invalid.
234 */
235 public static Fields named (String namePattern) {
236 if (namePattern == null)
237 throw new NullPointerException("namePattern");
238 return named(Strings.parse(namePattern));
239 }
240
241 /***
242 * Creates a new specification satisfied by Fields whose names match the supplied
243 * specification.
244 *
245 * @param specification The specification names must match.
246 *
247 * @return A new specification satisfied by Fields whose names match the supplied
248 * specification.
249 */
250 public static Fields named (final Strings specification) {
251 return new Fields() {
252 public boolean evaluate (Field field) {
253 return specification.isSatisfiedBy(field.getName());
254 }
255 };
256 }
257
258 /***
259 * Creates a new specification satisfied by Fields whose type satisfy the
260 * supplied specification.
261 *
262 * @param specification The specification types must match.
263 *
264 * @return A new specification satisfied by Fields whose type satisfy the
265 * supplied specification.
266 */
267 public static Fields ofType (final Types specification) {
268 return new Fields() {
269 public boolean evaluate (Field field) {
270 return specification.isSatisfiedBy(field.getType());
271 }
272 };
273 }
274
275 /***
276 * Parses a pattern string into a complete Fields specification. Format:
277 * <pre>
278 * fields ::= name
279 * name ::= [ field-type ] [ types | "(" types-expr ")" "." ]
280 * ( strings | "(" strings-expr ")" )
281 * field-type ::= [ modifiers ] ( types | "(" types-expr ")" )
282 * modifiers ::= ( [ "!" ]
283 * ( "public" | "protected" | "package" | "private"
284 * | "static" | "abstract" | "final" ) )...
285 * </pre>
286 * Example:
287 * <pre>
288 * (int | float) com.lonniepryor..*.foo
289 * </pre>
290 * The above matches any int or float field named 'foo', declared on any class in
291 * the 'com.lonniepryor' namespace.
292 *
293 * @param fieldsPattern The pattern definition string.
294 *
295 * @return A specification matching the pattern.
296 *
297 * @throws ParseException if the pattern is invalid.
298 */
299 public static Fields parse (String fieldsPattern) {
300 if (fieldsPattern == null)
301 throw new NullPointerException("fieldPattern");
302 int startIndex = Expression.rtrim(fieldsPattern, 0, fieldsPattern.length());
303 if (startIndex == 0)
304 throw new ParseException("Illegal empty fields pattern");
305 return readName(fieldsPattern, startIndex);
306 }
307
308 /***
309 * Reads a name expression from the pattern.
310 *
311 * @param fieldPattern The pattern definition string.
312 * @param index The effective end of the pattern.
313 *
314 * @return The specification for the field name.
315 *
316 * @throws ParseException if the pattern is invalid.
317 */
318 private static Fields readName (String fieldPattern, int index) {
319 int nameStart = Expression.statementBeginsAt(fieldPattern, 0, index, ".");
320 if (nameStart < 0)
321 throw new ParseException("Unmatched parenthesis in '" + fieldPattern + "'");
322 Fields specification = named(
323 Strings.parseExpression(fieldPattern.substring(nameStart, index)));
324 int nextIndex = Expression.rtrim(fieldPattern, 0, nameStart);
325 if ((nextIndex > 0) && (fieldPattern.charAt(nextIndex - 1) == '.')) {
326 int typeEnd = Expression.rtrim(fieldPattern, 0, nextIndex - 1);
327 if (typeEnd == 0)
328 throw new ParseException(
329 "Declaring type not found in '" + fieldPattern + "'");
330 int typeBegin = Expression.statementBeginsAt(fieldPattern, 0, typeEnd);
331 if (typeBegin < 0)
332 throw new ParseException("Unmatched parenthesis in '" + fieldPattern + "'");
333 specification = specification.and(
334 declaredOn(
335 Types.parseExpression(fieldPattern.substring(typeBegin, typeEnd))));
336 nextIndex = Expression.rtrim(fieldPattern, 0, typeBegin);
337 }
338 if (nextIndex > 0)
339 specification = specification.and(readFieldType(fieldPattern, nextIndex));
340 return specification;
341 }
342
343 /***
344 * Reads a field type expression from the pattern.
345 *
346 * @param fieldPattern The pattern definition string.
347 * @param index The effective end of the pattern.
348 *
349 * @return The specification for the field type.
350 *
351 * @throws ParseException if the pattern is invalid.
352 */
353 private static Fields readFieldType (String fieldPattern, int index) {
354 int typeStart = Expression.statementBeginsAt(fieldPattern, 0, index);
355 if (typeStart < 0)
356 throw new ParseException("Unmatched parenthesis in '" + fieldPattern + "'");
357 Fields specification = ofType(
358 Types.parseExpression(fieldPattern.substring(typeStart, index)));
359 int modsEnd = Expression.rtrim(fieldPattern, 0, typeStart);
360 if (modsEnd == 0)
361 return specification;
362 return specification.and(readModifiers(fieldPattern, modsEnd));
363 }
364
365 /***
366 * Reads a field modifier expression from the pattern.
367 *
368 * @param fieldPattern The pattern definition string.
369 * @param index The effective end of the pattern.
370 *
371 * @return The specification for the field type.
372 *
373 * @throws ParseException if the pattern is invalid.
374 */
375 private static Fields readModifiers (String fieldPattern, int index) {
376 int nextIndex = index;
377 while (
378 (nextIndex > 0)
379 && !Character.isWhitespace(fieldPattern.charAt(nextIndex - 1))
380 && (fieldPattern.charAt(nextIndex - 1) != '!'))
381 --nextIndex;
382 Fields specification = (Fields)modifiersByName.get(
383 fieldPattern.substring(nextIndex, index));
384 if (specification == null)
385 throw new ParseException(
386 "Unknown modifier '" + fieldPattern.substring(nextIndex, index) + "' in '"
387 + fieldPattern + "'");
388 nextIndex = Expression.rtrim(fieldPattern, 0, nextIndex);
389 if ((nextIndex > 0) && (fieldPattern.charAt(nextIndex - 1) == '!')) {
390 specification = specification.not();
391 nextIndex = Expression.rtrim(fieldPattern, 0, nextIndex - 1);
392 }
393 if (nextIndex == 0)
394 return specification;
395 return specification.and(readModifiers(fieldPattern, nextIndex));
396 }
397
398 /***
399 * Returns true if the supplied Field is not null and satisfies this
400 * specification.
401 *
402 * @param field The Field to test.
403 *
404 * @return True if the supplied Field is not null and satisfies this
405 * specification.
406 */
407 public final boolean isSatisfiedBy (Field field) {
408 return (field != null) && evaluate(field);
409 }
410
411 /***
412 * Returns true if the supplied Field satisfies this specification.
413 *
414 * @param attr The Field to test.
415 *
416 * @return True if the supplied Field satisfies this specification.
417 */
418 public abstract boolean evaluate (Field field);
419
420 /***
421 * Returns true if all of the supplied Fields satisfy this specification.
422 *
423 * @param all The array of Fields to test.
424 *
425 * @return True if all of the supplied Fields satisfy this specification.
426 */
427 public final boolean isSatisifiedByAll (Field[] all) {
428 if (all == null)
429 throw new NullPointerException("all");
430 for (int i = 0; i < all.length; ++i)
431 if (!isSatisfiedBy(all[i]))
432 return false;
433 return true;
434 }
435
436 /***
437 * Returns true if any of the supplied Fields satisfy this specification.
438 *
439 * @param any The array of Fields to test.
440 *
441 * @return True if any of the supplied Fields satisfy this specification.
442 */
443 public final boolean isSatisifiedByAny (Field[] any) {
444 if (any == null)
445 throw new NullPointerException("any");
446 for (int i = 0; i < any.length; ++i)
447 if (isSatisfiedBy(any[i]))
448 return true;
449 return false;
450 }
451
452 /***
453 * Selects the first Field that satisfies this specification from the supplied
454 * array.
455 *
456 * @param from The array to select from.
457 *
458 * @return The first Field that satisfies this specification from the supplied
459 * array.
460 */
461 public final Field selectFirst (Field[] from) {
462 if (from == null)
463 throw new NullPointerException("from");
464 for (int i = 0; i < from.length; ++i)
465 if (isSatisfiedBy(from[i]))
466 return from[i];
467 return null;
468 }
469
470 /***
471 * Selects all the Fields that satisfy this specification from the supplied
472 * array.
473 *
474 * @param from The array to select from.
475 *
476 * @return All the Fields that satisfy this specification from the supplied
477 * array.
478 */
479 public final Field[] selectAll (Field[] from) {
480 if (from == null)
481 throw new NullPointerException("from");
482 List results = new ArrayList(from.length);
483 for (int i = 0; i < from.length; ++i)
484 if (isSatisfiedBy(from[i]))
485 results.add(from[i]);
486 return (Field[])results.toArray(new Field[results.size()]);
487 }
488
489 /***
490 * Returns a specification representing a logical AND of this specification on
491 * the left and the supplied specification on the right.
492 *
493 * @param specification The specification to AND with.
494 *
495 * @return A specification representing a logical AND of this specification on
496 * the left and the supplied specification on the right.
497 */
498 public final Fields and (final Fields specification) {
499 if (specification == null)
500 throw new NullPointerException("specification");
501 return new Fields() {
502 public boolean evaluate (Field field) {
503 return Fields.this.evaluate(field) && specification.evaluate(field);
504 }
505 };
506 }
507
508 /***
509 * Returns a specification representing a logical OR of this specification on the
510 * left and the supplied specification on the right.
511 *
512 * @param specification The specification to OR with.
513 *
514 * @return A specification representing a logical OR of this specification on the
515 * left and the supplied specification on the right.
516 */
517 public final Fields or (final Fields specification) {
518 if (specification == null)
519 throw new NullPointerException("specification");
520 return new Fields() {
521 public boolean evaluate (Field field) {
522 return Fields.this.evaluate(field) || specification.evaluate(field);
523 }
524 };
525 }
526
527 /***
528 * Returns a specification representing a logical NOT of this specification.
529 *
530 * @return A specification representing a logical NOT of this specification.
531 */
532 public final Fields not () {
533 return new Fields() {
534 public boolean evaluate (Field field) {
535 return !Fields.this.evaluate(field);
536 }
537 };
538 }
539 }
This page was automatically generated by Maven