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.xbean.recipe;
018
019import java.lang.reflect.Field;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.lang.reflect.Modifier;
023import java.lang.reflect.Type;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.EnumSet;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import org.apache.xbean.recipe.ReflectionUtil.*;
033
034/**
035 * @version $Rev: 6688 $ $Date: 2005-12-29T02:08:29.200064Z $
036 */
037public class ObjectRecipe extends AbstractRecipe {
038    private String typeName;
039    private Class typeClass;
040    private String factoryMethod;
041    private List<String> constructorArgNames;
042    private List<Class<?>> constructorArgTypes;
043    private final LinkedHashMap<Property,Object> properties = new LinkedHashMap<Property,Object>();
044    private final EnumSet<Option> options = EnumSet.of(Option.FIELD_INJECTION);
045    private final Map<String,Object> unsetProperties = new LinkedHashMap<String,Object>();
046
047    public ObjectRecipe(Class typeClass) {
048        this(typeClass, null, null, null, null);
049    }
050
051    public ObjectRecipe(Class typeClass, String factoryMethod) {
052        this(typeClass, factoryMethod, null, null, null);
053    }
054
055    public ObjectRecipe(Class typeClass, Map<String,Object> properties) {
056        this(typeClass, null, null, null, properties);
057    }
058
059    public ObjectRecipe(Class typeClass, String[] constructorArgNames) {
060        this(typeClass, null, constructorArgNames, null, null);
061    }
062
063    public ObjectRecipe(Class typeClass, String[] constructorArgNames, Class[] constructorArgTypes) {
064        this(typeClass, null, constructorArgNames, constructorArgTypes, null);
065    }
066
067    public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames) {
068        this(type, factoryMethod, constructorArgNames, null, null);
069    }
070
071    public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
072        this(type, factoryMethod, constructorArgNames, constructorArgTypes, null);
073    }
074
075    public ObjectRecipe(Class typeClass, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
076        this.typeClass = typeClass;
077        this.factoryMethod = factoryMethod;
078        this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
079        this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
080        if (properties != null) {
081            setAllProperties(properties);
082        }
083    }
084
085    public ObjectRecipe(String typeName) {
086        this(typeName, null, null, null, null);
087    }
088
089    public ObjectRecipe(String typeName, String factoryMethod) {
090        this(typeName, factoryMethod, null, null, null);
091    }
092
093    public ObjectRecipe(String typeName, Map<String,Object> properties) {
094        this(typeName, null, null, null, properties);
095    }
096
097    public ObjectRecipe(String typeName, String[] constructorArgNames) {
098        this(typeName, null, constructorArgNames, null, null);
099    }
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}