View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.xbean.recipe;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.lang.reflect.Type;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.EnumSet;
28  import java.util.LinkedHashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import org.apache.xbean.recipe.ReflectionUtil.*;
33  
34  /**
35   * @version $Rev: 6688 $ $Date: 2005-12-29T02:08:29.200064Z $
36   */
37  public class ObjectRecipe extends AbstractRecipe {
38      private String typeName;
39      private Class typeClass;
40      private String factoryMethod;
41      private List<String> constructorArgNames;
42      private List<Class<?>> constructorArgTypes;
43      private final LinkedHashMap<Property,Object> properties = new LinkedHashMap<Property,Object>();
44      private final EnumSet<Option> options = EnumSet.of(Option.FIELD_INJECTION);
45      private final Map<String,Object> unsetProperties = new LinkedHashMap<String,Object>();
46  
47      public ObjectRecipe(Class typeClass) {
48          this(typeClass, null, null, null, null);
49      }
50  
51      public ObjectRecipe(Class typeClass, String factoryMethod) {
52          this(typeClass, factoryMethod, null, null, null);
53      }
54  
55      public ObjectRecipe(Class typeClass, Map<String,Object> properties) {
56          this(typeClass, null, null, null, properties);
57      }
58  
59      public ObjectRecipe(Class typeClass, String[] constructorArgNames) {
60          this(typeClass, null, constructorArgNames, null, null);
61      }
62  
63      public ObjectRecipe(Class typeClass, String[] constructorArgNames, Class[] constructorArgTypes) {
64          this(typeClass, null, constructorArgNames, constructorArgTypes, null);
65      }
66  
67      public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames) {
68          this(type, factoryMethod, constructorArgNames, null, null);
69      }
70  
71      public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
72          this(type, factoryMethod, constructorArgNames, constructorArgTypes, null);
73      }
74  
75      public ObjectRecipe(Class typeClass, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
76          this.typeClass = typeClass;
77          this.factoryMethod = factoryMethod;
78          this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
79          this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
80          if (properties != null) {
81              setAllProperties(properties);
82          }
83      }
84  
85      public ObjectRecipe(String typeName) {
86          this(typeName, null, null, null, null);
87      }
88  
89      public ObjectRecipe(String typeName, String factoryMethod) {
90          this(typeName, factoryMethod, null, null, null);
91      }
92  
93      public ObjectRecipe(String typeName, Map<String,Object> properties) {
94          this(typeName, null, null, null, properties);
95      }
96  
97      public ObjectRecipe(String typeName, String[] constructorArgNames) {
98          this(typeName, null, constructorArgNames, null, null);
99      }
100 
101     public ObjectRecipe(String typeName, String[] constructorArgNames, Class[] constructorArgTypes) {
102         this(typeName, null, constructorArgNames, constructorArgTypes, null);
103     }
104 
105     public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames) {
106         this(typeName, factoryMethod, constructorArgNames, null, null);
107     }
108 
109     public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
110         this(typeName, factoryMethod, constructorArgNames, constructorArgTypes, null);
111     }
112 
113     public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
114         this.typeName = typeName;
115         this.factoryMethod = factoryMethod;
116         this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
117         this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
118         if (properties != null) {
119             setAllProperties(properties);
120         }
121     }
122 
123     public void allow(Option option){
124         options.add(option);
125     }
126 
127     public void disallow(Option option){
128         options.remove(option);
129     }
130 
131     public Set<Option> getOptions() {
132         return Collections.unmodifiableSet(options);
133     }
134 
135     public List<String> getConstructorArgNames() {
136         return constructorArgNames;
137     }
138 
139     public void setConstructorArgNames(String[] constructorArgNames) {
140         this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
141     }
142 
143     public void setConstructorArgNames(List<String> constructorArgNames) {
144         this.constructorArgNames = constructorArgNames;
145     }
146 
147     public List<Class<?>> getConstructorArgTypes() {
148         return constructorArgTypes;
149     }
150 
151     public void setConstructorArgTypes(Class[] constructorArgTypes) {
152         this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
153     }
154 
155     public void setConstructorArgTypes(List<? extends Class<?>> constructorArgTypes) {
156         this.constructorArgTypes = new ArrayList<Class<?>>(constructorArgTypes);
157     }
158 
159     public String getFactoryMethod() {
160         return factoryMethod;
161     }
162 
163     public void setFactoryMethod(String factoryMethod) {
164         this.factoryMethod = factoryMethod;
165     }
166 
167     public Object getProperty(String name) {
168         Object value = properties.get(new Property(name));
169         return value;
170     }
171 
172     public Map<String, Object> getProperties() {
173         LinkedHashMap<String, Object> properties = new LinkedHashMap<String, Object>();
174         for (Map.Entry<Property, Object> entry : this.properties.entrySet()) {
175             properties.put(entry.getKey().name, entry.getValue());
176         }
177         return properties;
178     }
179 
180     public void setProperty(String name, Object value) {
181         setProperty(new Property(name), value);
182     }
183 
184     public void setFieldProperty(String name, Object value){
185         setProperty(new FieldProperty(name), value);
186         options.add(Option.FIELD_INJECTION);
187     }
188 
189     public void setMethodProperty(String name, Object value){
190         setProperty(new SetterProperty(name), value);
191     }
192 
193     public void setAutoMatchProperty(String type, Object value){
194         setProperty(new AutoMatchProperty(type), value);
195     }
196 
197     public void setCompoundProperty(String name, Object value) {
198         setProperty(new CompoundProperty(name), value);
199     }
200     
201     private void setProperty(Property key, Object value) {
202         if (value instanceof UnsetPropertiesRecipe) {
203             allow(Option.IGNORE_MISSING_PROPERTIES);
204         }
205         properties.put(key, value);
206     }
207 
208 
209     public void setAllProperties(Map<?,?> map) {
210         if (map == null) throw new NullPointerException("map is null");
211         for (Map.Entry<?, ?> entry : map.entrySet()) {
212             String name = (String) entry.getKey();
213             Object value = entry.getValue();
214             setProperty(name, value);
215         }
216     }
217 
218     public Map<String,Object> getUnsetProperties() {
219         return unsetProperties;
220     }
221 
222     public List<Recipe> getNestedRecipes() {
223         List<Recipe> nestedRecipes = new ArrayList<Recipe>(properties.size());
224         for (Object o : properties.values()) {
225             if (o instanceof Recipe) {
226                 Recipe recipe = (Recipe) o;
227                 nestedRecipes.add(recipe);
228             }
229         }
230         return nestedRecipes;
231     }
232 
233     public List<Recipe> getConstructorRecipes() {
234         // find the factory that will be used to create the class instance
235         Factory factory = findFactory(Object.class);
236 
237         // if we are NOT using an instance factory to create the object
238         // (we have a factory method and it is not a static factory method)
239         if (factoryMethod != null && !(factory instanceof StaticFactory)) {
240             // only include recipes used in the construcor args
241             List<String> parameterNames = factory.getParameterNames();
242             List<Recipe> nestedRecipes = new ArrayList<Recipe>(parameterNames.size());
243             for (Map.Entry<Property, Object> entry : properties.entrySet()) {
244                 if (parameterNames.contains(entry.getKey().name) && entry.getValue() instanceof Recipe) {
245                     Recipe recipe = (Recipe) entry.getValue();
246                     nestedRecipes.add(recipe);
247                 }
248             }
249             return nestedRecipes;
250         } else {
251             // when there is an instance factory all nested recipes are used in the constructor
252             return getNestedRecipes();
253         }
254     }
255 
256     public boolean canCreate(Type type) {
257         Class myType = getType();
258         return RecipeHelper.isAssignable(type, myType) || RecipeHelper.isAssignable(type, myType);
259     }
260 
261     protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
262         unsetProperties.clear();
263 
264         //
265         // load the type class
266         Class typeClass = getType();
267 
268         //
269         // clone the properties so they can be used again
270         Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
271 
272         //
273         // create the instance
274         Factory factory = findFactory(expectedType);
275         Object[] parameters = extractConstructorArgs(propertyValues, factory);
276         Object instance = factory.create(parameters);
277 
278         //
279         // add to execution context if name is specified
280         if (getName() != null) {
281             ExecutionContext.getContext().addObject(getName(), instance);
282         }
283 
284         //
285         // set the properties
286         setProperties(propertyValues, instance, instance.getClass());
287 
288         //
289         // call instance factory method
290 
291         // if we have a factory method name and did not find a static factory,
292         // then we have an instance factory
293         if (factoryMethod != null && !(factory instanceof StaticFactory)) {
294             // find the instance factory method
295             Method instanceFactory = ReflectionUtil.findInstanceFactory(instance.getClass(), factoryMethod, null);
296 
297             try {
298                 instance = instanceFactory.invoke(instance);
299             } catch (Exception e) {
300                 Throwable t = e;
301                 if (e instanceof InvocationTargetException) {
302                     InvocationTargetException invocationTargetException = (InvocationTargetException) e;
303                     if (invocationTargetException.getCause() != null) {
304                         t = invocationTargetException.getCause();
305                     }
306                 }
307                 throw new ConstructionException("Error calling instance factory method: " + instanceFactory, t);
308             }
309         }
310 
311         return instance;
312     }
313 
314     public void setProperties(Object instance) throws ConstructionException {
315         unsetProperties.clear();
316 
317         // clone the properties so they can be used again
318         Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
319 
320         setProperties(propertyValues, instance, instance.getClass());
321     }
322 
323     public Class setStaticProperties() throws ConstructionException {
324         unsetProperties.clear();
325 
326         // load the type class
327         Class typeClass = getType();
328 
329         // verify that it is a class we can construct
330         if (!Modifier.isPublic(typeClass.getModifiers())) {
331             throw new ConstructionException("Class is not public: " + typeClass.getName());
332         }
333         if (Modifier.isInterface(typeClass.getModifiers())) {
334             throw new ConstructionException("Class is an interface: " + typeClass.getName());
335         }
336         if (Modifier.isAbstract(typeClass.getModifiers())) {
337             throw new ConstructionException("Class is abstract: " + typeClass.getName());
338         }
339 
340         // clone the properties so they can be used again
341         Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
342 
343         setProperties(propertyValues, null, typeClass);
344 
345         return typeClass;
346     }
347 
348     public Class getType() {
349         if (typeClass != null || typeName != null) {
350             Class type = typeClass;
351             if (type == null) {
352                 try {
353                     type = RecipeHelper.loadClass(typeName);
354                 } catch (ClassNotFoundException e) {
355                     throw new ConstructionException("Type class could not be found: " + typeName);
356                 }
357             }
358 
359             return type;
360         }
361 
362         return null;
363     }
364 
365     private void setProperties(Map<Property, Object> propertyValues, Object instance, Class clazz) {
366         // set remaining properties
367         for (Map.Entry<Property, Object> entry : RecipeHelper.prioritizeProperties(propertyValues)) {
368             Property propertyName = entry.getKey();
369             Object propertyValue = entry.getValue();
370 
371             setProperty(instance, clazz, propertyName, propertyValue);
372         }
373 
374     }
375 
376     private void setProperty(Object instance, Class clazz, Property propertyName, Object propertyValue) {
377 
378         List<Member> members = new ArrayList<Member>();
379         try {
380             if (propertyName instanceof SetterProperty){
381                 List<Method> setters = ReflectionUtil.findAllSetters(clazz, propertyName.name, propertyValue, options);
382                 for (Method setter : setters) {
383                     MethodMember member = new MethodMember(setter);
384                     members.add(member);
385                 }
386             } else if (propertyName instanceof FieldProperty){
387                 FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
388                 members.add(member);
389             } else if (propertyName instanceof AutoMatchProperty){
390                 MissingAccessorException noField = null;
391                 if (options.contains(Option.FIELD_INJECTION)) {
392                     List<Field> fieldsByType = null;
393                     try {
394                         fieldsByType = ReflectionUtil.findAllFieldsByType(clazz, propertyValue, options);
395                         FieldMember member = new FieldMember(fieldsByType.iterator().next());
396                         members.add(member);
397                     } catch (MissingAccessorException e) {
398                         noField = e;
399                     }
400 
401                     // if we got more then one matching field, that is an immidate error
402                     if (fieldsByType != null && fieldsByType.size() > 1) {
403                         List<String> matches = new ArrayList<String>();
404                         for (Field field : fieldsByType) {
405                             matches.add(field.getName());
406                         }
407                         throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one field: " + matches, 0);
408                     }
409                 }
410 
411                 // if we didn't find any fields, try the setters
412                 if (members.isEmpty()) {
413                     List<Method> settersByType;
414                     try {
415                         settersByType = ReflectionUtil.findAllSettersByType(clazz, propertyValue, options);
416                         MethodMember member = new MethodMember(settersByType.iterator().next());
417                         members.add(member);
418                     } catch (MissingAccessorException noSetter) {
419                         throw (noField == null || noSetter.getMatchLevel() > noField.getMatchLevel())? noSetter: noField;
420                     }
421 
422                     // if we got more then one matching field, that is an immidate error
423                     if (settersByType != null && settersByType.size() > 1) {
424                         List<String> matches = new ArrayList<String>();
425                         for (Method setter : settersByType) {
426                             matches.add(setter.getName());
427                         }
428                         throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one setter: " + matches, 0);
429                     }
430                 }
431             } else if (propertyName instanceof CompoundProperty) {
432                 String[] names = propertyName.name.split("\\.");
433                 for (int i = 0; i < names.length - 1; i++) {
434                     Method getter = ReflectionUtil.findGetter(clazz, names[i], options);
435                     if (getter != null) {
436                         try {
437                             instance = getter.invoke(instance);
438                             clazz = instance.getClass();
439                         } catch (Exception e) {
440                             Throwable t = e;
441                             if (e instanceof InvocationTargetException) {
442                                 InvocationTargetException invocationTargetException = (InvocationTargetException) e;
443                                 if (invocationTargetException.getCause() != null) {
444                                     t = invocationTargetException.getCause();
445                                 }
446                             }
447                             throw new ConstructionException("Error setting property: " + names[i], t);                            
448                         } 
449                     } else {
450                         throw new ConstructionException("No getter for " + names[i] + " property");
451                     }
452                 }
453                 List<Method> setters = ReflectionUtil.findAllSetters(clazz, names[names.length - 1], propertyValue, options);
454                 for (Method setter : setters) {
455                     MethodMember member = new MethodMember(setter);
456                     members.add(member);
457                 }
458             } else {
459                 // add setter members
460                 MissingAccessorException noSetter = null;
461                 try {
462                     List<Method> setters = ReflectionUtil.findAllSetters(clazz, propertyName.name, propertyValue, options);
463                     for (Method setter : setters) {
464                         MethodMember member = new MethodMember(setter);
465                         members.add(member);
466                     }
467                 } catch (MissingAccessorException e) {
468                     noSetter = e;
469                     if (!options.contains(Option.FIELD_INJECTION)) {
470                         throw noSetter;
471                     }
472                 }
473 
474                 if (options.contains(Option.FIELD_INJECTION)) {
475                     try {
476                         FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
477                         members.add(member);
478                     } catch (MissingAccessorException noField) {
479                         if (members.isEmpty()) {
480                             throw (noSetter == null || noField.getMatchLevel() > noSetter.getMatchLevel())? noField: noSetter;
481                         }
482                     }
483                 }
484             }
485         } catch (MissingAccessorException e) {
486             if (options.contains(Option.IGNORE_MISSING_PROPERTIES)) {
487                 unsetProperties.put(propertyName.name, propertyValue);
488                 return;
489             }
490             throw e;
491         }
492 
493         ConstructionException conversionException = null;
494         for (Member member : members) {
495             // convert the value to type of setter/field
496             try {
497                 propertyValue = RecipeHelper.convert(member.getType(), propertyValue, false);
498             } catch (Exception e) {
499                 // save off first conversion exception, in case setting failed
500                 if (conversionException == null) {
501                     String valueType = propertyValue == null ? "null" : propertyValue.getClass().getName();
502                     String memberType = member.getType() instanceof Class ? ((Class) member.getType()).getName() : member.getType().toString();
503                     conversionException = new ConstructionException("Unable to convert property value" +
504                             " from " + valueType +
505                             " to " + memberType +
506                             " for injection " + member, e);
507                 }
508                 continue;
509             }
510             try {
511                 // set value
512                 member.setValue(instance, propertyValue);
513             } catch (Exception e) {
514                 Throwable t = e;
515                 if (e instanceof InvocationTargetException) {
516                     InvocationTargetException invocationTargetException = (InvocationTargetException) e;
517                     if (invocationTargetException.getCause() != null) {
518                         t = invocationTargetException.getCause();
519                     }
520                 }
521                 throw new ConstructionException("Error setting property: " + member, t);
522             }
523 
524             // value set successfully
525             return;
526         }
527 
528         throw conversionException;
529     }
530 
531     private Factory findFactory(Type expectedType) {
532         Class type = getType();
533 
534         //
535         // attempt to find a static factory
536         if (factoryMethod != null) {
537             try {
538                 StaticFactory staticFactory = ReflectionUtil.findStaticFactory(
539                         type,
540                         factoryMethod,
541                         constructorArgNames,
542                         constructorArgTypes,
543                         getProperties().keySet(),
544                         options);
545                 return staticFactory;
546             } catch (MissingFactoryMethodException ignored) {
547             }
548 
549         }
550 
551         //
552         // factory was not found, look for a constuctor
553 
554         // if expectedType is a subclass of the assigned type, we create
555         // the sub class instead
556         Class consturctorClass;
557         if (RecipeHelper.isAssignable(type, expectedType)) {
558             consturctorClass = RecipeHelper.toClass(expectedType);
559         } else {
560             consturctorClass = type;
561         }
562 
563         ConstructorFactory constructor = ReflectionUtil.findConstructor(
564                 consturctorClass,
565                 constructorArgNames,
566                 constructorArgTypes,
567                 getProperties().keySet(),
568                 options);
569 
570         return constructor;
571     }
572 
573     private Object[] extractConstructorArgs(Map propertyValues, Factory factory) {
574         List<String> parameterNames = factory.getParameterNames();
575         List<Type> parameterTypes = factory.getParameterTypes();
576 
577         Object[] parameters = new Object[parameterNames.size()];
578         for (int i = 0; i < parameterNames.size(); i++) {
579             Property name = new Property(parameterNames.get(i));
580             Type type = parameterTypes.get(i);
581 
582             Object value;
583             if (propertyValues.containsKey(name)) {
584                 value = propertyValues.remove(name);
585                 if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value)) {
586                     throw new ConstructionException("Invalid and non-convertable constructor parameter type: " +
587                             "name=" + name + ", " +
588                             "index=" + i + ", " +
589                             "expected=" + RecipeHelper.toClass(type).getName() + ", " +
590                             "actual=" + (value == null ? "null" : value.getClass().getName()));
591                 }
592                 value = RecipeHelper.convert(type, value, false);
593             } else {
594                 value = getDefaultValue(RecipeHelper.toClass(type));
595             }
596 
597 
598             parameters[i] = value;
599         }
600         return parameters;
601     }
602 
603     private static Object getDefaultValue(Class type) {
604         if (type.equals(Boolean.TYPE)) {
605             return Boolean.FALSE;
606         } else if (type.equals(Character.TYPE)) {
607             return (char) 0;
608         } else if (type.equals(Byte.TYPE)) {
609             return (byte) 0;
610         } else if (type.equals(Short.TYPE)) {
611             return (short) 0;
612         } else if (type.equals(Integer.TYPE)) {
613             return 0;
614         } else if (type.equals(Long.TYPE)) {
615             return (long) 0;
616         } else if (type.equals(Float.TYPE)) {
617             return (float) 0;
618         } else if (type.equals(Double.TYPE)) {
619             return (double) 0;
620         }
621         return null;
622     }
623 
624     public static interface Member {
625         Type getType();
626         void setValue(Object instance, Object value) throws Exception;
627     }
628 
629     public static class MethodMember implements Member {
630         private final Method setter;
631 
632         public MethodMember(Method method) {
633             this.setter = method;
634         }
635 
636         public Type getType() {
637             return setter.getGenericParameterTypes()[0];
638         }
639 
640         public void setValue(Object instance, Object value) throws Exception {
641             setter.invoke(instance, value);
642         }
643 
644         public String toString() {
645             return setter.toString();
646         }
647     }
648 
649     public static class FieldMember implements Member {
650         private final Field field;
651 
652         public FieldMember(Field field) {
653             this.field = field;
654         }
655 
656         public Type getType() {
657             return field.getGenericType();
658         }
659 
660         public void setValue(Object instance, Object value) throws Exception {
661             field.set(instance, value);
662         }
663 
664         public String toString() {
665             return field.toString();
666         }
667     }
668 
669     public static class Property {
670         private final String name;
671 
672         public Property(String name) {
673             if (name == null) throw new NullPointerException("name is null");
674             this.name = name;
675         }
676 
677         public boolean equals(Object o) {
678             if (this == o) return true;
679             if (o == null) return false;
680             if (o instanceof String){
681                 return this.name.equals(o);
682             }
683             if (o instanceof Property) {
684                 Property property = (Property) o;
685                 return this.name.equals(property.name);
686             }
687             return false;
688         }
689 
690         public int hashCode() {
691             return name.hashCode();
692         }
693 
694         public String toString() {
695             return name;
696         }
697     }
698 
699     public static class SetterProperty extends Property {
700         public SetterProperty(String name) {
701             super(name);
702         }
703         public int hashCode() {
704             return super.hashCode()+2;
705         }
706         public String toString() {
707             return "[setter] "+super.toString();
708         }
709 
710     }
711 
712     public static class FieldProperty extends Property {
713         public FieldProperty(String name) {
714             super(name);
715         }
716 
717         public int hashCode() {
718             return super.hashCode()+1;
719         }
720         public String toString() {
721             return "[field] "+ super.toString();
722         }
723     }
724 
725     public static class AutoMatchProperty extends Property {
726         public AutoMatchProperty(String type) {
727             super(type);
728         }
729 
730         public int hashCode() {
731             return super.hashCode()+1;
732         }
733         public String toString() {
734             return "[auto-match] "+ super.toString();
735         }
736     }
737     
738     public static class CompoundProperty extends Property {
739         public CompoundProperty(String type) {
740             super(type);
741         }
742 
743         public int hashCode() {
744             return super.hashCode()+1;
745         }
746         public String toString() {
747             return "[compound] "+ super.toString();
748         }
749     }
750 }