001    /*
002      Copyright (C) 2001-2003 Renaud Pawlak <renaud@aopsys.com>, 
003                              Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful,
011      but WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013      GNU Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public License
016      along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
018    
019    package org.objectweb.jac.core.rtti;
020    
021    import java.lang.reflect.InvocationTargetException;
022    import java.util.Collection;
023    import java.util.HashSet;
024    import java.util.Hashtable;
025    import java.util.Map;
026    import java.util.Set;
027    import org.apache.log4j.Logger;
028    import org.objectweb.jac.core.AspectComponent;
029    import org.objectweb.jac.core.Wrappee;
030    import java.lang.reflect.Method;
031    
032    /**
033     * This class defines the rtti aspect.
034     *
035     * <p>It allows the programmer to add some runtime type informations on
036     * the classes of its applications.
037     *
038     * @see ClassItem
039     * @see MethodItem
040     * @see FieldItem
041     * @see CollectionItem
042     *
043     * @author Renaud Pawlak
044     * @author Laurent Martelli
045     */
046    
047    public class RttiAC extends AspectComponent implements RttiConf {
048        static Logger logger = Logger.getLogger("rtti");
049    
050        public static final String OPPOSITE_ROLE = "RttiAC.OPPOSITE_ROLE";
051        public static final String FIELD_TYPE = "RttiAC.FIELD_TYPE";
052        public static final String DYNAMIC_FIELD_TYPE = "RttiAC.DYNAMIC_FIELD_TYPE";
053        public static final String PARAMETER_TYPES = "RttiAC.PARAMETERS_TYPES";
054        public static final String CLONED_FIELDS = "RttiAC.CLONED_FIELDS";
055        public static final String REPOSITORY_NAME = "RttiAC.REPOSITORY_NAME";
056        public static final String REPOSITORY_COLLECTION = "RttiAC.REPOSITORY_COLLECTION";
057        public static final String NULL_ALLOWED_PARAMETERS = "RttiAC.NULL_ALOWED_PARAMETERS";
058        public static final String NULL_ALLOWED = "RttiAC.NULL_ALLOWED"; // Boolean
059        public static final String IS_INDEX = "RttiAC.IS_INDEX"; // Boolean
060        public static final String INDEXED_FIELD = "RttiAC.INDEXED_FIELD"; // FieldItem
061        public static final String AUTHORIZED_VALUES = "RttiAC.AUTHORIZED_VALUES";
062        public static final String FORBIDDEN_VALUES = "RttiAC.FORBIDDEN_VALUES";
063        public static final String CONSTRAINTS = "RttiAC.CONSTRAINTS";
064        public static final String PARAMETERS_FIELDS = "RttiAC.PARAMETERS_FIELDS"; // FieldItem[]
065    
066        public static final String PRIMARY_KEY = "RttiAC.PRIMARY_KEY";
067    
068        public void addWrittenFields(AbstractMethodItem method, 
069                                     String[] writtenFields) {
070            ClassItem cl = method.getClassItem();
071            for( int i=0; i<writtenFields.length; i++ ) {
072                FieldItem fi = cl.getField(writtenFields[i]);
073                method.addWrittenField(fi);
074            }
075        }
076    
077        public void declareCalculatedField(ClassItem cl, String fieldName,
078                                           String getterName) 
079        {
080            MethodItem getter = cl.getMethod(getterName);
081            FieldItem calculatedField; 
082            if (RttiAC.isCollectionType(getter.getType()))
083                calculatedField = new CollectionItem(fieldName,getter,cl);
084            else
085                calculatedField = new FieldItem(fieldName,getter,cl);
086            cl.addField(calculatedField);
087            FieldItem[] fields = getter.getAccessedFields();
088            for (int i=0;i<fields.length;i++) {
089                if (fields[i]!=calculatedField) {
090                    logger.debug(calculatedField.getLongName()+" depends on "+fields[i]);
091                    fields[i].addDependentField(calculatedField);
092                }
093            }
094        }
095    
096        public void setSetter(FieldItem field, String setterName) {
097            MethodItem setter = field.getClassItem().getMethod(setterName);
098            setter.setSetField(field);
099            field.setSetter(setter);
100        }
101    
102        public void setGetter(FieldItem field, String getterName) {
103            MethodItem getter = field.getClassItem().getMethod(getterName);
104            getter.setReturnedField(field);
105            field.setGetter(getter);
106        }
107    
108        public void addDependentField(FieldItem field, String dependentField) {
109            field.getClassItem().getField(dependentField).addDependentField(field);
110        }
111    
112        public void addFieldDependency(FieldItem field, FieldItem dependentField) {
113            field.addDependentField(dependentField);
114        }
115    
116        public void addAdder(CollectionItem collection, String methodName) {
117            MethodItem method = collection.getClassItem().getMethod(methodName);
118            collection.addAddingMethod(method);
119            method.addAddedCollection(collection);
120        }
121    
122        public void setAdder(CollectionItem collection, String methodName) {
123            MethodItem method = collection.getClassItem().getMethod(methodName);
124            collection.setAdder(method);
125            method.addAddedCollection(collection);
126        }
127    
128        public void addRemover(CollectionItem collection,String methodName) {
129            MethodItem method = collection.getClassItem().getMethod(methodName);
130            collection.addRemovingMethod(method);
131            method.addRemovedCollection(collection);
132        }
133    
134        public void setRemover(CollectionItem collection,String methodName) {
135            MethodItem method = collection.getClassItem().getMethod(methodName);
136            collection.setRemover(method);
137            method.addRemovedCollection(collection);
138        }
139    
140        public void addAccessedFields(MethodItem method, 
141                                      String[] accessedFields) {
142            ClassItem cl = method.getClassItem();
143            for(int i=0; i<accessedFields.length; i++) {
144                FieldItem fi = cl.getField(accessedFields[i]);
145                method.addAccessedField(fi);
146            }
147        }
148    
149        public void setFieldType(FieldItem field, String type) 
150        {
151            Object cl = ClassRepository.get().getObject(type);
152    
153            if (cl == null)
154                throw new RuntimeException("no such type "+type);
155    
156            field.setAttribute(FIELD_TYPE, cl);
157        }
158    
159        public void setDynamicFieldType(FieldItem field, MethodItem method) {
160            if (!method.isStatic()) {
161                error("Method must be static");
162            } if (!(method.getType()==String.class 
163                    || method.getType()==Object.class 
164                    || MetaItem.class.isAssignableFrom(method.getType()))) {
165                error("Method must return a String, a MetaItem or an Object");
166            } else {
167                field.setAttribute(DYNAMIC_FIELD_TYPE, method);
168            }
169        }
170    
171        public void setComponentType(CollectionItem collection, String type) {
172            collection.setComponentType(currentImports.getClass(type));
173        }
174    
175        /**
176         * Gets the type of a field. May return a ClassItem, a
177         * VirtualClassItem or a MethodItem.
178         * @param field a field
179         */
180        public static MetaItem getFieldType(FieldItem field) {
181            return (MetaItem)field.getAttribute(FIELD_TYPE);
182        }
183    
184        /**
185         * Gets the type of a field for a given object. May return a
186         * ClassItem, a VirtualClassItem.
187         * @param field a field 
188         * @param substance the object holding the field
189         */
190        public static MetaItem getFieldType(FieldItem field, Object substance) {
191            MethodItem dynType = (MethodItem)field.getAttribute(DYNAMIC_FIELD_TYPE);
192            if (dynType!=null) {
193                Object type = dynType.invokeStatic(new Object[] {field,substance});
194                if (type instanceof String)
195                    return cr.getVirtualClass((String)type);
196                else
197                    return (MetaItem)type;
198            } else {
199                return getFieldType(field);
200            }
201        }
202    
203        public void setParametersType(AbstractMethodItem method, 
204                                      String[] types) 
205        {
206            ClassRepository cr = ClassRepository.get();
207            MetaItem[] metaItems = new MetaItem[types.length];
208            for (int i=0; i<types.length;i++) {
209                metaItems[i] = (MetaItem)cr.getObject(types[i]);
210            }
211            method.setAttribute(PARAMETER_TYPES,metaItems);
212        }
213    
214        public void newVirtualClass(String className, ClassItem actualType)
215        {
216            logger.info("newVirtualClass("+className+")");
217            ClassRepository.get().register(className,
218                                           new VirtualClassItem(className,actualType));
219        }
220    
221        public void defineRepository(ClassItem type, 
222                                     String repositoryName,
223                                     CollectionItem repositoryCollection) 
224        {
225            type.setAttribute(REPOSITORY_NAME, repositoryName);
226            type.setAttribute(REPOSITORY_COLLECTION, repositoryCollection);
227        }
228    
229        public void setClonedFields(String className, String[] fields) {
230            ClassRepository.get().getClass(className)
231                .setAttribute(CLONED_FIELDS,fields);
232        }
233    
234        public void whenClone(Wrappee cloned, Wrappee clone) {
235          
236            ClassItem cli = ClassRepository.get().getClass(cloned.getClass());
237            String[] clonedFields = (String[])cli.getAttribute(CLONED_FIELDS);
238          
239            if( clonedFields != null ) {
240                for( int i=0; i<clonedFields.length; i++ ) {
241                    FieldItem fi = cli.getField( clonedFields[i] );
242                    logger.debug("cloning field "+clonedFields[i]);
243                    /*
244                      try {
245                      fi.set(clone,((Wrappee)fi.get(cloned)).clone());
246                      } catch( Exception e ) {}
247                    */
248                }
249            }
250        }
251    
252        public void ignoreFields(String packageExpr) {
253            logger.info("ignoreFields"+packageExpr);
254            ClassRepository.get().ignoreFields(packageExpr);
255        }
256    
257        void setItemClass(MetaItem item, String className, ClassItem actualType) {
258            MetaItem virtualClass;
259            ClassRepository cr = ClassRepository.get();
260            try {
261                virtualClass = cr.getVirtualClass(className);
262            } catch (NoSuchClassException e) {
263                virtualClass = new VirtualClassItem(className,actualType);
264                cr.register(className,virtualClass);
265            }
266            item.setItemClass(virtualClass);
267        }
268    
269        public void setClass(MemberItem member, String className) {
270            setItemClass(member,className,member.getTypeItem());
271        }
272    
273        public void setClass(ClassItem cli, String className) {
274            setItemClass(cli,className,cli);
275        }
276    
277        /*
278        public void introduce(ClassItem target,ClassItem roleType,
279                              String memeberType,String memberName) {
280        }
281        */
282    
283        public void setParametersFields(AbstractMethodItem method, 
284                                        FieldItem[] fields) {
285            method.setAttribute(PARAMETERS_FIELDS, fields);
286        }
287    
288        public void setNullAllowed(FieldItem field) {
289            setNullAllowed(field,true);
290        }
291    
292        public void setNullAllowed(FieldItem field, boolean allowed) {
293            field.setAttribute(NULL_ALLOWED, allowed ? Boolean.TRUE : Boolean.FALSE);
294            logger.info("setNullAllowed("+field.getName()+")");
295        }
296    
297        public static boolean isNullAllowed(FieldItem field) {
298            Boolean result = (Boolean) field.getAttribute(NULL_ALLOWED);
299            if (result == null)
300                return false;
301            return result.booleanValue();
302        }
303    
304        public void setNullAllowedParameters(AbstractMethodItem method,
305                                             boolean[] nulls) {
306            method.setAttribute(NULL_ALLOWED_PARAMETERS, nulls);
307        }
308    
309        public static boolean isNullAllowedParameter(AbstractMethodItem method,
310                                                     int i) {
311            //System.out.println("is null allowed "+method+" "+i);
312            boolean[] nulls=(boolean[])method.getAttribute(NULL_ALLOWED_PARAMETERS);
313            if(nulls==null) return false;
314            //System.out.println("=> "+nulls[i]);
315            return nulls[i];
316        }
317    
318        public void setAggregation(FieldItem field, boolean isAggregation) {
319            field.setAggregation(isAggregation);
320        }
321    
322        public void setIndexedField(CollectionItem collection, 
323                                    FieldItem indexedField) {
324            setIsIndex(collection,true);
325            collection.setAttribute(INDEXED_FIELD, indexedField);
326        }
327    
328        public void setIsIndex(CollectionItem collection, 
329                               boolean isIndex) {
330            collection.setAttribute(IS_INDEX, isIndex?Boolean.TRUE:Boolean.FALSE);
331        }
332    
333        public static FieldItem getIndexFied(CollectionItem collection) {
334            return (FieldItem)collection.getAttribute(INDEXED_FIELD);
335        }
336    
337        public static boolean isIndex(CollectionItem collection) {
338            Boolean result = (Boolean) collection.getAttribute(IS_INDEX);
339            if (result == null)
340                return false;
341            return result.booleanValue();      
342        }
343    
344        public String[] getDefaultConfigs() {
345            return new String[] {"org/objectweb/jac/core/rtti/rtti.acc",
346                                 "org/objectweb/jac/aspects/user/rtti.acc"};
347        }
348    
349        public void definePrimaryKey(CollectionItem collection,
350                                     String[] fields) {
351            collection.setAttribute(PRIMARY_KEY, fields);
352        }
353    
354        /**
355         * Tells wether a given type represents a collection
356         */
357        public static boolean isCollectionType(Class type) {
358            return Collection.class.isAssignableFrom(type) || 
359                Map.class.isAssignableFrom(type) || 
360                (type.isArray() && type.getComponentType()!=byte.class);
361        }
362    
363        static Hashtable allowedCasts = new Hashtable();
364        public void addAllowedCast(ClassItem src, ClassItem dest) {
365            Set casts = (Set)allowedCasts.get(src);
366            if (casts == null) {
367                casts = new HashSet();
368                allowedCasts.put(src,casts);
369            }
370            casts.add(dest);
371        }
372    
373        public static boolean isCastAllowed(ClassItem src, ClassItem dest) {
374            Set casts = (Set)allowedCasts.get(src);
375            return casts!=null && casts.contains(dest);
376        }
377    
378        public static boolean isCastAllowed(Class src, Class dest) {
379            ClassRepository cr = ClassRepository.get();
380            return isCastAllowed(cr.getClass(src),cr.getClass(dest));
381        }
382    
383            HashSet classesWithAssociations = new HashSet();
384        public Set getClassesWithAssociations() {
385            return classesWithAssociations;
386        }
387    
388        public void setOppositeRole(FieldItem field, FieldItem oppositeRole) {
389            field.setOppositeRole(oppositeRole);
390                    classesWithAssociations.add(field.getClassItem());
391        }
392        
393        public void declareAssociation(FieldItem roleA, FieldItem roleB) {
394            roleA.setOppositeRole(roleB);
395            roleB.setOppositeRole(roleA);
396                    classesWithAssociations.add(roleA.getClassItem());
397                    classesWithAssociations.add(roleB.getClassItem());
398        }
399    
400        /**
401         * Tries to convert an object into a given type 
402         *
403         * <p>If type is
404         * String, toString() is called on value. If type Integer or Long
405         * (or int or long), and the value is numeric type
406         * (float,Float,double or Double) an Integer or Long is
407         * returned. Otherwise, isCastAllowed() is called, and if it
408         * returns true, we try to invoke a constructor take value as a
409         * parameter.</p>
410         *
411         * @param value the value to convert
412         * @param type the type to convert the value into
413         * @return an instance of type built from value, or value 
414         * @see #isCastAllowed(Class,Class)
415         */
416        public static Object convert(Object value, Class type) 
417            throws InstantiationException, IllegalAccessException, 
418                   InvocationTargetException, java.lang.NoSuchMethodException 
419        {
420            Class valueType = value.getClass();
421            if ((type==int.class || type==Integer.class) 
422                && (valueType==float.class || valueType==Float.class 
423                    || valueType==double.class || valueType==Double.class)) {
424                return new Integer(((Number)value).intValue());
425            } else if ((type==long.class || type==Long.class) 
426                       && (valueType==float.class || valueType==Float.class 
427                           || valueType==double.class || valueType==Double.class)) {
428                return new Long(((Number)value).longValue());
429            } else if (type==String.class) {
430                return value.toString();
431            } else {
432                if (RttiAC.isCastAllowed(valueType,type)) {
433                    return 
434                        type.getConstructor(new Class[] {valueType})
435                        .newInstance(new Object[] {value});
436                }
437            }
438            return value;
439        }
440    
441        public void addMixinMethod(ClassItem cli, MethodItem method) 
442            throws InvalidDelegateException 
443        {
444            cli.addMethod(new MixinMethodItem((Method)method.getDelegate(),cli));
445        }
446    
447    }