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