001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.utils;
018
019import static java.lang.Character.*;
020import static org.apache.juneau.collections.JsonMap.*;
021import static org.apache.juneau.common.utils.Utils.*;
022import static org.apache.juneau.internal.CollectionUtils.*;
023
024import java.lang.reflect.*;
025import java.util.*;
026import java.util.function.*;
027
028import org.apache.juneau.*;
029import org.apache.juneau.common.utils.*;
030
031/**
032 * Allows arbitrary objects to be mapped to classes and methods base on class/method name keys.
033 *
034 * <p>
035 * The valid pattern matches are:
036 * <ul class='spaced-list'>
037 *  <li>Classes:
038 *       <ul>
039 *          <li>Fully qualified:
040 *             <ul>
041 *                <li><js>"com.foo.MyClass"</js>
042 *             </ul>
043 *          <li>Fully qualified inner class:
044 *             <ul>
045 *                <li><js>"com.foo.MyClass$Inner1$Inner2"</js>
046 *             </ul>
047 *          <li>Simple:
048 *             <ul>
049 *                <li><js>"MyClass"</js>
050 *             </ul>
051 *          <li>Simple inner:
052 *             <ul>
053 *                <li><js>"MyClass$Inner1$Inner2"</js>
054 *                <li><js>"Inner1$Inner2"</js>
055 *                <li><js>"Inner2"</js>
056 *             </ul>
057 *       </ul>
058 *    <li>Methods:
059 *       <ul>
060 *          <li>Fully qualified with args:
061 *             <ul>
062 *                <li><js>"com.foo.MyClass.myMethod(String,int)"</js>
063 *                <li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js>
064 *                <li><js>"com.foo.MyClass.myMethod()"</js>
065 *             </ul>
066 *          <li>Fully qualified:
067 *             <ul>
068 *                <li><js>"com.foo.MyClass.myMethod"</js>
069 *             </ul>
070 *          <li>Simple with args:
071 *             <ul>
072 *                <li><js>"MyClass.myMethod(String,int)"</js>
073 *                <li><js>"MyClass.myMethod(java.lang.String,int)"</js>
074 *                <li><js>"MyClass.myMethod()"</js>
075 *             </ul>
076 *          <li>Simple:
077 *             <ul>
078 *                <li><js>"MyClass.myMethod"</js>
079 *             </ul>
080 *          <li>Simple inner class:
081 *             <ul>
082 *                <li><js>"MyClass$Inner1$Inner2.myMethod"</js>
083 *                <li><js>"Inner1$Inner2.myMethod"</js>
084 *                <li><js>"Inner2.myMethod"</js>
085 *             </ul>
086 *       </ul>
087 *    <li>Fields:
088 *       <ul>
089 *          <li>Fully qualified:
090 *             <ul>
091 *                <li><js>"com.foo.MyClass.myField"</js>
092 *             </ul>
093 *          <li>Simple:
094 *             <ul>
095 *                <li><js>"MyClass.myField"</js>
096 *             </ul>
097 *          <li>Simple inner class:
098 *             <ul>
099 *                <li><js>"MyClass$Inner1$Inner2.myField"</js>
100 *                <li><js>"Inner1$Inner2.myField"</js>
101 *                <li><js>"Inner2.myField"</js>
102 *             </ul>
103 *       </ul>
104 *    <li>Constructors:
105 *       <ul>
106 *          <li>Fully qualified with args:
107 *             <ul>
108 *                <li><js>"com.foo.MyClass(String,int)"</js>
109 *                <li><js>"com.foo.MyClass(java.lang.String,int)"</js>
110 *                <li><js>"com.foo.MyClass()"</js>
111 *             </ul>
112 *          <li>Simple with args:
113 *             <ul>
114 *                <li><js>"MyClass(String,int)"</js>
115 *                <li><js>"MyClass(java.lang.String,int)"</js>
116 *                <li><js>"MyClass()"</js>
117 *             </ul>
118 *          <li>Simple inner class:
119 *             <ul>
120 *                <li><js>"MyClass$Inner1$Inner2()"</js>
121 *                <li><js>"Inner1$Inner2()"</js>
122 *                <li><js>"Inner2()"</js>
123 *             </ul>
124 *       </ul>
125 *    <li>A comma-delimited list of anything on this list.
126 * </ul>
127 *
128 * <h5 class='section'>See Also:</h5><ul>
129 * </ul>
130 *
131 * @param <V> The type of object in this map.
132 */
133public class ReflectionMap<V> {
134
135   //-----------------------------------------------------------------------------------------------------------------
136   // Static
137   //-----------------------------------------------------------------------------------------------------------------
138
139   /**
140    * Static builder creator.
141    *
142    * @param <V> The type of object in this map.
143    * @param c The type of object in this map.
144    * @return A new instance of this object.
145    */
146   public static <V> Builder<V> create(Class<V> c) {
147      return new Builder<>();
148   }
149
150   //-----------------------------------------------------------------------------------------------------------------
151   // Builder
152   //-----------------------------------------------------------------------------------------------------------------
153
154   /**
155    * Builder class.
156    * @param <V> The type of object in this map.
157    */
158   public static class Builder<V> {
159      final List<ClassEntry<V>> classEntries;
160      final List<MethodEntry<V>> methodEntries;
161      final List<FieldEntry<V>> fieldEntries;
162      final List<ConstructorEntry<V>> constructorEntries;
163
164      /**
165       * Constructor.
166       */
167      protected Builder() {
168         classEntries = list();
169         methodEntries = list();
170         fieldEntries = list();
171         constructorEntries = list();
172      }
173
174      /**
175       * Copy constructor.
176       *
177       * @param copyFrom The builder being copied.
178       */
179      protected Builder(Builder<V> copyFrom) {
180         classEntries = copyOf(copyFrom.classEntries);
181         methodEntries = copyOf(copyFrom.methodEntries);
182         fieldEntries = copyOf(copyFrom.fieldEntries);
183         constructorEntries = copyOf(copyFrom.constructorEntries);
184      }
185
186      /**
187       * Adds a mapping to this builder.
188       *
189       * @param key
190       *    The mapping key.
191       *    <br>Can be any of the following:
192       *    <ul>
193       *       <li>Full class name (e.g. <js>"com.foo.MyClass"</js>).
194       *       <li>Simple class name (e.g. <js>"MyClass"</js>).
195       *       <li>All classes (e.g. <js>"*"</js>).
196       *       <li>Full method name (e.g. <js>"com.foo.MyClass.myMethod"</js>).
197       *       <li>Simple method name (e.g. <js>"MyClass.myMethod"</js>).
198       *       <li>A comma-delimited list of anything on this list.
199       *    </ul>
200       * @param value The value for this mapping.
201       * @return This object.
202       */
203      public Builder<V> append(String key, V value) {
204         if (Utils.isEmpty(key))
205            throw new BasicRuntimeException("Invalid reflection signature: [{0}]", key);
206         try {
207            splitNames(key, k -> {
208               if (k.endsWith(")")) {
209                  int i = k.substring(0, k.indexOf('(')).lastIndexOf('.');
210                  if (i == -1 || isUpperCase(k.charAt(i+1))) {
211                     constructorEntries.add(new ConstructorEntry<>(k, value));
212                  } else {
213                     methodEntries.add(new MethodEntry<>(k, value));
214                  }
215               } else {
216                  int i = k.lastIndexOf('.');
217                  if (i == -1) {
218                     classEntries.add(new ClassEntry<>(k, value));
219                  } else if (isUpperCase(k.charAt(i+1))) {
220                     classEntries.add(new ClassEntry<>(k, value));
221                     fieldEntries.add(new FieldEntry<>(k, value));
222                  } else {
223                     methodEntries.add(new MethodEntry<>(k, value));
224                     fieldEntries.add(new FieldEntry<>(k, value));
225                  }
226               }
227            });
228         } catch (IndexOutOfBoundsException e) {
229            throw new BasicRuntimeException("Invalid reflection signature: [{0}]", key);
230         }
231
232         return this;
233      }
234
235      /**
236       * Create new instance of {@link ReflectionMap} based on the contents of this builder.
237       *
238       * @return A new {@link ReflectionMap} object.
239       */
240      public ReflectionMap<V> build() {
241         return new ReflectionMap<>(this);
242      }
243
244      /**
245       * Creates a copy of this builder.
246       *
247       * @return A copy of this builder.
248       */
249      public Builder<V> copy() {
250         return new Builder<>(this);
251      }
252   }
253
254   //-----------------------------------------------------------------------------------------------------------------
255   // Instance
256   //-----------------------------------------------------------------------------------------------------------------
257
258   final ClassEntry<V>[] classEntries;
259   final MethodEntry<V>[] methodEntries;
260   final FieldEntry<V>[] fieldEntries;
261   final ConstructorEntry<V>[] constructorEntries;
262
263   /**
264    * Constructor.
265    *
266    * @param b Initializer object.
267    */
268   protected ReflectionMap(Builder<V> b) {
269      this.classEntries = b.classEntries.toArray(new ClassEntry[b.classEntries.size()]);
270      this.methodEntries = b.methodEntries.toArray(new MethodEntry[b.methodEntries.size()]);
271      this.fieldEntries = b.fieldEntries.toArray(new FieldEntry[b.fieldEntries.size()]);
272      this.constructorEntries = b.constructorEntries.toArray(new ConstructorEntry[b.constructorEntries.size()]);
273   }
274
275   static void splitNames(String key, Consumer<String> consumer) {
276      if (key.indexOf(',') == -1) {
277         consumer.accept(key);
278      } else {
279         int m = 0;
280         boolean escaped = false;
281         for (int i = 0; i < key.length(); i++) {
282            char c = key.charAt(i);
283            if (c == '(')
284               escaped = true;
285            else if (c == ')')
286               escaped = false;
287            else if (c == ',' && ! escaped) {
288               consumer.accept(key.substring(m, i).trim());
289               m = i+1;
290            }
291         }
292         consumer.accept(key.substring(m).trim());
293      }
294   }
295
296   /**
297    * Finds first value in this map that matches the specified class.
298    *
299    * @param c The class to test for.
300    * @param ofType Only return objects of the specified type.
301    * @return The matching object.  Never <jk>null</jk>.
302    */
303   public Optional<V> find(Class<?> c, Class<? extends V> ofType) {
304      for (ClassEntry<V> e : classEntries)
305         if (e.matches(c))
306            if (ofType == null || ofType.isInstance(e.value))
307               return Utils.opt(e.value);
308      return Utils.opte();
309   }
310
311   /**
312    * Finds first value in this map that matches the specified class.
313    *
314    * @param c The class to test for.
315    * @return The matching object.  Never <jk>null</jk>.
316    */
317   public Optional<V> find(Class<?> c) {
318      return find(c, null);
319   }
320
321   /**
322    * Finds all values in this map that matches the specified class.
323    *
324    * @param c The class to test for.
325    * @param ofType Only return objects of the specified type.
326    * @return A modifiable list of matching values.  Never <jk>null</jk>.
327    */
328   public List<V> findAll(Class<?> c, Class<? extends V> ofType) {
329      List<V> list = null;
330      for (ClassEntry<V> e : classEntries)
331         if (e.matches(c) && e.value != null)
332            if (ofType == null || ofType.isInstance(e.value))
333               list = lazyAdd(list, e.value);
334      return lazyList(list);
335   }
336
337   /**
338    * Finds all values in this map that matches the specified class.
339    *
340    * @param c The class to test for.
341    * @return A modifiable list of matching values.  Never <jk>null</jk>.
342    */
343   public List<V> findAll(Class<?> c) {
344      List<V> list = null;
345      for (ClassEntry<V> e : classEntries)
346         if (e.matches(c) && e.value != null)
347            list = lazyAdd(list, e.value);
348      return lazyList(list);
349   }
350
351   /**
352    * Finds all values in this map that matches the specified class.
353    *
354    * @param c The class to test for.
355    * @param ofType Only return objects of the specified type.
356    * @param array The array to append values to.
357    * @return The same list passed in or a new modifiable list if <jk>null</jk>.
358    */
359   public V[] appendAll(Class<?> c, Class<? extends V> ofType, V[] array) {
360      List<V> list = null;
361      for (ClassEntry<V> e : classEntries)
362         if (e.matches(c) && e.value != null)
363            if (ofType == null || ofType.isInstance(e.value))
364               list = lazyAdd(array, list, e.value);
365      return lazyArray(array, list);
366   }
367
368   private static <V> List<V> lazyAdd(List<V> list, V v) {
369      if (list == null)
370         list = list();
371      list.add(v);
372      return list;
373   }
374
375   /**
376    * Finds first value in this map that matches the specified method.
377    *
378    * @param m The method to test for.
379    * @param ofType Only return objects of the specified type.
380    * @return The matching object.  Never <jk>null</jk>.
381    */
382   public Optional<V> find(Method m, Class<? extends V> ofType) {
383      for (MethodEntry<V> e : methodEntries)
384         if (e.matches(m))
385            if (ofType == null || ofType.isInstance(e.value))
386               return Utils.opt(e.value);
387      return Utils.opte();
388   }
389
390   /**
391    * Finds first value in this map that matches the specified method.
392    *
393    * @param m The method to test for.
394    * @return The matching object.  Never <jk>null</jk>.
395    */
396   public Optional<V> find(Method m) {
397      return find(m, null);
398   }
399
400   /**
401    * Finds all values in this map that matches the specified method.
402    *
403    * @param m The method to test for.
404    * @param ofType Only return objects of the specified type.
405    * @return A modifiable list of matching values.  Never <jk>null</jk>.
406    */
407   public List<V> findAll(Method m, Class<? extends V> ofType) {
408      List<V> list = null;
409      for (MethodEntry<V> e : methodEntries)
410         if (e.matches(m) && e.value != null)
411            if (ofType == null || ofType.isInstance(e.value))
412               list = lazyAdd(list, e.value);
413      return lazyList(list);
414   }
415
416   /**
417    * Finds all values in this map that matches the specified method.
418    *
419    * @param m The method to test for.
420    * @return A modifiable list of matching values.  Never <jk>null</jk>.
421    */
422   public List<V> findAll(Method m) {
423      List<V> list = null;
424      for (MethodEntry<V> e : methodEntries)
425         if (e.matches(m) && e.value != null)
426            list = lazyAdd(list, e.value);
427      return lazyList(list);
428   }
429
430   /**
431    * Finds all values in this map that matches the specified method.
432    *
433    * @param m The method to test for.
434    * @param ofType Only return objects of the specified type.
435    * @param array The array to append values to.
436    * @return The same list passed in or a new modifiable list if <jk>null</jk>.
437    */
438   public V[] appendAll(Method m, Class<? extends V> ofType, V[] array) {
439      List<V> list = null;
440      for (MethodEntry<V> e : methodEntries)
441         if (e.matches(m) && e.value != null)
442            if (ofType == null || ofType.isInstance(e.value))
443               list = lazyAdd(array, list, e.value);
444      return lazyArray(array, list);
445   }
446
447   /**
448    * Finds first value in this map that matches the specified field.
449    *
450    * @param f The field to test for.
451    * @param ofType Only return objects of the specified type.
452    * @return The matching object.  Never <jk>null</jk>.
453    */
454   public Optional<V> find(Field f, Class<? extends V> ofType) {
455      for (FieldEntry<V> e : fieldEntries)
456         if (e.matches(f))
457            if (ofType == null || ofType.isInstance(e.value))
458               return Utils.opt(e.value);
459      return Utils.opte();
460   }
461
462   /**
463    * Finds first value in this map that matches the specified field.
464    *
465    * @param f The field to test for.
466    * @return The matching object.  Never <jk>null</jk>.
467    */
468   public Optional<V> find(Field f) {
469      return find(f, null);
470   }
471
472   /**
473    * Finds all values in this map that matches the specified field.
474    *
475    * @param f The field to test for.
476    * @param ofType Only return objects of the specified type.
477    * @return A modifiable list of matching values.  Never <jk>null</jk>.
478    */
479   public List<V> findAll(Field f, Class<? extends V> ofType) {
480      List<V> list = null;
481      for (FieldEntry<V> e : fieldEntries)
482         if (e.matches(f) && e.value != null)
483            if (ofType == null || ofType.isInstance(e.value))
484               list = lazyAdd(list, e.value);
485      return lazyList(list);
486   }
487
488   /**
489    * Finds all values in this map that matches the specified field.
490    *
491    * @param f The field to test for.
492    * @return A modifiable list of matching values.  Never <jk>null</jk>.
493    */
494   public List<V> findAll(Field f) {
495      List<V> list = null;
496      for (FieldEntry<V> e : fieldEntries)
497         if (e.matches(f) && e.value != null)
498            list = lazyAdd(list, e.value);
499      return lazyList(list);
500   }
501
502   /**
503    * Finds all values in this map that matches the specified field.
504    *
505    * @param f The field to test for.
506    * @param ofType Only return objects of the specified type.
507    * @param array The array to append values to.
508    * @return The same list passed in or a new modifiable list if <jk>null</jk>.
509    */
510   public V[] appendAll(Field f, Class<? extends V> ofType, V[] array) {
511      List<V> list = null;
512      for (FieldEntry<V> e : fieldEntries)
513         if (e.matches(f) && e.value != null)
514            if (ofType == null || ofType.isInstance(e.value))
515               list = lazyAdd(array, list, e.value);
516      return lazyArray(array, list);
517   }
518
519   /**
520    * Finds first value in this map that matches the specified constructor.
521    *
522    * @param c The constructor to test for.
523    * @param ofType Only return objects of the specified type.
524    * @return The matching object.  Never <jk>null</jk>.
525    */
526   public Optional<V> find(Constructor<?> c, Class<? extends V> ofType) {
527      for (ConstructorEntry<V> e : constructorEntries)
528         if (e.matches(c))
529            if (ofType == null || ofType.isInstance(e.value))
530               return Utils.opt(e.value);
531      return Utils.opte();
532   }
533
534   /**
535    * Finds first value in this map that matches the specified constructor.
536    *
537    * @param c The constructor to test for.
538    * @return The matching object.  Never <jk>null</jk>.
539    */
540   public Optional<V> find(Constructor<?> c) {
541      return find(c, null);
542   }
543
544   /**
545    * Finds all values in this map that matches the specified constructor.
546    *
547    * @param c The constructor to test for.
548    * @param ofType Only return objects of the specified type.
549    * @return A modifiable list of matching values.  Never <jk>null</jk>.
550    */
551   public List<V> findAll(Constructor<?> c, Class<? extends V> ofType) {
552      List<V> list = null;
553      for (ConstructorEntry<V> e : constructorEntries)
554         if (e.matches(c) && e.value != null)
555            if (ofType == null || ofType.isInstance(e.value))
556               list = lazyAdd(list, e.value);
557      return lazyList(list);
558   }
559
560   /**
561    * Finds all values in this map that matches the specified constructor.
562    *
563    * @param c The constructor to test for.
564    * @return A modifiable list of matching values.  Never <jk>null</jk>.
565    */
566   public List<V> findAll(Constructor<?> c) {
567      List<V> list = null;
568      for (ConstructorEntry<V> e : constructorEntries)
569         if (e.matches(c) && e.value != null)
570            list = lazyAdd(list, e.value);
571      return lazyList(list);
572   }
573
574   /**
575    * Finds all values in this map that matches the specified constructor.
576    *
577    * @param c The constructor to test for.
578    * @param ofType Only return objects of the specified type.
579    * @param array The array to append values to.
580    * @return The same list passed in or a new modifiable list if <jk>null</jk>.
581    */
582   public V[] appendAll(Constructor<?> c, Class<? extends V> ofType, V[] array) {
583      List<V> list = null;
584      for (ConstructorEntry<V> e : constructorEntries)
585         if (e.matches(c) && e.value != null)
586            if (ofType == null || ofType.isInstance(e.value))
587               list = lazyAdd(array, list, e.value);
588      return lazyArray(array, list);
589   }
590
591   static class ClassEntry<V> {
592      final String simpleName, fullName;
593      final V value;
594
595      ClassEntry(String name, V value) {
596         this.simpleName = simpleClassName(name);
597         this.fullName = name;
598         this.value = value;
599      }
600
601      public boolean matches(Class<?> c) {
602         if (c == null)
603            return false;
604         return classMatches(simpleName, fullName, c);
605      }
606
607      @Override
608      public String toString() {
609         return filteredMap()
610            .append("simpleName", simpleName)
611            .append("fullName", fullName)
612            .append("value", value)
613            .asString();
614      }
615   }
616
617   static class MethodEntry<V> {
618      String simpleClassName, fullClassName, methodName, args[];
619      V value;
620
621      MethodEntry(String name, V value) {
622         int i = name.indexOf('(');
623         this.args = i == -1 ? null : Utils.splitMethodArgs(name.substring(i+1, name.length()-1));
624         if (args != null) {
625            for (int j = 0; j < args.length; j++) {
626
627               // Strip off generic parameters.
628               int k = args[j].indexOf('<');
629               if (k > 0)
630                  args[j] = args[j].substring(0, k);
631
632               // Convert from xxx[][] to [[Lxxx; notation.
633               if (args[j].endsWith("[]")) {
634                  int l = 0;
635                  while (args[j].endsWith("[]")) {
636                     l++;
637                     args[j] = args[j].substring(0, args[j].length()-2);
638                  }
639                  StringBuilder sb = new StringBuilder(args[j].length() + l + 2);
640                  for (int m = 0; m < l; m++)
641                     sb.append('[');
642                  sb.append('L').append(args[j]).append(';');
643                  args[j] = sb.toString();
644               }
645            }
646         }
647         name = i == -1 ? name : name.substring(0, i);
648         i = name.lastIndexOf('.');
649         String s1 = name.substring(0, i).trim(), s2 = name.substring(i+1).trim();
650         this.simpleClassName = simpleClassName(s1);
651         this.fullClassName = s1;
652         this.methodName = s2;
653         this.value = value;
654      }
655
656      public boolean matches(Method m) {
657         if (m == null)
658            return false;
659         Class<?> c = m.getDeclaringClass();
660         return
661            classMatches(simpleClassName, fullClassName, c)
662            && (Utils.eq(m.getName(), methodName))
663            && (argsMatch(args, m.getParameterTypes()));
664      }
665
666      @Override
667      public String toString() {
668         return filteredMap()
669            .append("simpleClassName", simpleClassName)
670            .append("fullClassName", fullClassName)
671            .append("methodName", methodName)
672            .append("args", args)
673            .append("value", value)
674            .asString();
675      }
676   }
677
678   static class ConstructorEntry<V> {
679      String simpleClassName, fullClassName, args[];
680      V value;
681
682      ConstructorEntry(String name, V value) {
683         int i = name.indexOf('(');
684         this.args = splita(name.substring(i+1, name.length()-1));
685         name = name.substring(0, i).trim();
686         this.simpleClassName = simpleClassName(name);
687         this.fullClassName = name;
688         this.value = value;
689      }
690
691      public boolean matches(Constructor<?> m) {
692         if (m == null)
693            return false;
694         Class<?> c = m.getDeclaringClass();
695         return
696            classMatches(simpleClassName, fullClassName, c)
697            && (argsMatch(args, m.getParameterTypes()));
698      }
699
700      @Override
701      public String toString() {
702         return filteredMap()
703            .append("simpleClassName", simpleClassName)
704            .append("fullClassName", fullClassName)
705            .append("args", args)
706            .append("value", value)
707            .asString();
708      }
709   }
710
711   static class FieldEntry<V> {
712      String simpleClassName, fullClassName, fieldName;
713      V value;
714
715      FieldEntry(String name, V value) {
716         int i = name.lastIndexOf('.');
717         String s1 = name.substring(0, i), s2 = name.substring(i+1);
718         this.simpleClassName = simpleClassName(s1);
719         this.fullClassName = s1;
720         this.fieldName = s2;
721         this.value = value;
722      }
723
724      public boolean matches(Field f) {
725         if (f == null)
726            return false;
727         Class<?> c = f.getDeclaringClass();
728         return
729            classMatches(simpleClassName, fullClassName, c)
730            && (Utils.eq(f.getName(), fieldName));
731      }
732
733      @Override
734      public String toString() {
735         return filteredMap()
736            .append("simpleClassName", simpleClassName)
737            .append("fullClassName", fullClassName)
738            .append("fieldName", fieldName)
739            .append("value", value)
740            .asString();
741      }
742   }
743
744   static boolean argsMatch(String[] names, Class<?>[] args) {
745      if (names == null)
746         return true;
747      if (names.length != args.length)
748         return false;
749      for (int i = 0; i < args.length; i++) {
750         String n = names[i];
751         Class<?> a = args[i];
752         if (! (Utils.eq(n, a.getSimpleName()) || Utils.eq(n, a.getName())))
753            return false;
754      }
755      return true;
756   }
757
758   static String simpleClassName(String name) {
759      int i = name.indexOf('.');
760      if (i == -1)
761         return name;
762      return null;
763   }
764
765   static boolean classMatches(String simpleName, String fullName, Class<?> c) {
766      // For class org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder
767      // c.getSimpleName() == "Builder"
768      // c.getFullName() == "org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder"
769      // c.getPackage() == "org.apache.juneau.a.rttests"
770      String cSimple = c.getSimpleName(), cFull = c.getName();
771      if (Utils.eq(simpleName, cSimple) || Utils.eq(fullName, cFull) || "*".equals(simpleName))
772         return true;
773      if (cFull.indexOf('$') != -1) {
774         Package p = c.getPackage();
775         if (p != null)
776            cFull = cFull.substring(p.getName().length() + 1);
777         if (Utils.eq(simpleName, cFull))
778            return true;
779         int i = cFull.indexOf('$');
780         while (i != -1) {
781            cFull = cFull.substring(i+1);
782            if (Utils.eq(simpleName, cFull))
783               return true;
784            i = cFull.indexOf('$');
785         }
786      }
787      return false;
788   }
789
790   @Override /* Object */
791   public String toString() {
792      return filteredMap()
793         .append("classEntries", classEntries)
794         .append("methodEntries", methodEntries)
795         .append("fieldEntries", fieldEntries)
796         .append("constructorEntries", constructorEntries)
797         .asString();
798   }
799
800   //-----------------------------------------------------------------------------------------------------------------
801   // Utility methods
802   //-----------------------------------------------------------------------------------------------------------------
803
804   private static <V> List<V> lazyList(List<V> list) {
805      return list == null ? Collections.emptyList() : list;
806   }
807
808   private static <V> List<V> lazyAdd(V[] array, List<V> list, V v) {
809      if (list == null)
810         list = list(array);
811      list.add(v);
812      return list;
813   }
814
815   @SuppressWarnings("unchecked")
816   private static <V> V[] lazyArray(V[] array, List<V> list) {
817      if (list == null)
818         return array;
819      array = (V[])Array.newInstance(array.getClass().getComponentType(), list.size());
820      list.toArray(array);
821      return array;
822   }
823}