View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2001-2004 Open World Ltd
4   // Copyright (C) 2005 Kevin Rogers
5   //
6   // This file is part of MillScript.
7   //
8   // MillScript is free software; you can redistribute it and/or modify it under
9   // the terms of the GNU General Public License as published by the Free
10  // Software Foundation; either version 2 of the License, or (at your option)
11  // any later version.
12  //
13  // MillScript is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  // more details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA  02111-1307  USA
21  ////////////////////////////////////////////////////////////////////////////////
22  package org.millscript.millscript.vm;
23  
24  import org.millscript.commons.util.EList;
25  import org.millscript.commons.util.EMap;
26  import org.millscript.commons.util.IList;
27  import org.millscript.commons.util.ListIterator;
28  import org.millscript.commons.util.MapIterator;
29  import org.millscript.commons.util.list.ELinkedList;
30  import org.millscript.commons.util.map.EHashMap;
31  import org.millscript.millscript.alert.Alerts;
32  import org.millscript.millscript.functions.Function;
33  import org.millscript.millscript.functions.JConstructor;
34  import org.millscript.millscript.functions.JMethod;
35  import org.millscript.millscript.functions.JOConstructor;
36  import org.millscript.millscript.functions.JOMethod;
37  import org.millscript.millscript.functions.JRecognizer;
38  
39  import java.lang.reflect.Array;
40  import java.lang.reflect.Constructor;
41  import java.lang.reflect.Method;
42  import java.util.Vector;
43  
44  /**
45   * This class imports all available methods and constructors in a specified
46   * Java class into the specified package. The available methods and
47   * constructors are imported as functions. If there are multiple constructors
48   * or methods that take the same number of parameters, they will not be
49   * imported.
50   * <p>
51   * The imported constructor function will be called by the name of the class
52   * prefixed with "new". A recognizer function for instances of this class can
53   * be imported called by the name of the class prefixed with "is".
54   * </p>
55   */
56  public class ImportJavaClass {
57  
58      /**
59       * The class we intend to import.
60       */
61      private final Class clss;
62  
63      /**
64       * The package we will import into.
65       */
66      private final Package pack;
67  
68      /**
69       * Constructs an instance to import all methods and constructors in the
70       * specified Java class into the specified package.
71       *
72       * @param className the class to import methods and constructors from
73       * @param p the package to define the new functions in
74       */
75      public ImportJavaClass( final String className, final Package p ) {
76          Class c = null;
77          this.pack = p;
78          try {
79              c = Class.forName( className );
80          } catch ( ClassNotFoundException ex ) {
81              throw(
82                  Alerts.fault( "Cannot find this class " + className ).mishap()
83              );
84          }
85          clss = c;   //  *sigh*
86      }
87  
88      /**
89       * Convenience method to import all available constructors and methods.
90       */
91      public void importAll() {
92          importAllConstructors();
93          importAllMethods();
94      }
95  
96      /**
97       * Removes duplicate entries from the vector of lists of
98       * constructors/methods. Specifically, this method removes entries from the
99       * vector where there is more than one item in the list. e.g. this means if
100      * two constructors or methods have the same name they will both be
101      * removed.
102      *
103      * @param v the vector of lists of constructors/methods to check
104      * @param componentClass    the class we are importing from
105      * @return  an array of constructors/methods indexed by the number of
106      * parameters the constructor/method takes.
107      */
108     @SuppressWarnings( "unchecked" )
109     private static < T > T[] pruneDups( final Vector< EList< T > > v, final Class< T > componentClass ) {
110         // This will hold the maximum number of parameters a constructor/method
111         // in this class takes
112         int hi = 0;
113         // The maximum number of parameters a constructor/method in the class
114         // takes, before removing duplicates
115         int siz = v.size();
116         for ( int i = 0; i < siz; i++ ) {
117             // Get the list of constructors that take i parameters
118             EList< T > list = v.get( i );
119             if ( list != null ) {
120                 if ( list.size() > 1 ) {
121                     // There is more than one constructor/method that takes i
122                     // parameters, so remove them all
123                     v.set( i, null );
124                 } else {
125                     // There is just one constructor/method taking i parameters
126                     // so set the maximum number of parameters accordingly
127                     hi = Math.max( hi, i + 1 );
128                 }
129             }
130         }
131 
132         // Make a new array as large as the maximum number of paramters
133         T[] result = (T[]) Array.newInstance( componentClass, hi );
134         // Loop through the constructors/methods and set each position in the
135         // array accordingly
136         for ( int i = 0; i < siz; i++ ) {
137             EList< T > list = v.get( i );
138             if ( list != null ) {
139                 result[ i ] = list.get0( 0 );
140             }
141         }
142         return result;
143     }
144 
145     /**
146      * Returns the number of non-null elements in the array.
147      *
148      * @param objs  the array to count non-null elements in
149      * @return  the number of non-null elements
150      */
151     private int countNonNulls( final Object[] objs ) {
152         int count = 0;
153         for ( int i = 0; i < objs.length; i++ ) {
154             if ( objs[ i ] != null ) {
155                 count += 1;
156             }
157         }
158         return count;
159     }
160 
161     /**
162      * Returns the first non-null item in the array.
163      *
164      * @param objs  the array to search
165      * @return  the first non-null object, or <code>null</code> if all the
166      * objects in the array are <code>null</code>
167      */
168     private Object firstItem( final Object[] objs ) {
169         for ( int i = 0; i < objs.length; i++ ) {
170             if ( objs[ i ] != null ) {
171                 return objs[ i ];
172             }
173         }
174         return null;
175     }
176 
177     /**
178      * Return the name of the specified class without any package prefix. e.g.
179      * when java.lang.Object is specified, this method would return Object.
180      *
181      * @param name  the class name
182      * @return  the short version of the class name
183      */
184     private String shortClassName( final String name ) {
185         int n = name.lastIndexOf( '.' );
186         if ( n == -1 ) {
187             return name;
188         }
189         return name.substring( n + 1 );
190     }
191 
192     /**
193      * Imports a recognizer function for this class.
194      */
195     private void importRecognizer() {
196         String name = "is" + shortClassName( clss.getName() );
197         pack.bindConst(
198             name,
199             new JRecognizer( clss )
200         );
201     }
202 
203     /**
204      * Imports all available constructors in the Java class. This will ignore
205      *constructors which take the same number of parameters.
206      */
207     private void importAllConstructors() {
208         // Get the constructors
209         Constructor[] cons = clss.getConstructors();
210         // Create a vector to store the available constructors in, grouped by
211         // the number of parameters required. e.g. at index 1 will be all the
212         // constructors taking 1 parameter, and so on.
213         Vector< EList< Constructor > > v = new Vector< EList< Constructor > >();
214         for ( int i = 0; i < cons.length; i++ ) {
215             Constructor c = cons[ i ];
216             // How many parameters does this constructor take?
217             int n = c.getParameterTypes().length;
218             // Ensure the vector has a position for this number of paramters
219             v.setSize( n + 1 );
220             // Get the list of constructors taking this number of paramters
221             EList< Constructor > list = v.get( n );
222             if ( list == null ) {
223                 list = new ELinkedList< Constructor >();
224             }
225             // Add this constructor to the list
226             list.addLast( c );
227             // Put the list into the vector
228             v.set( n, list );
229         }
230 
231         // Prune the duplicates and get an array of constructors, indexed by
232         // the number of parameters
233         Constructor[] result = pruneDups( v, Constructor.class );
234 
235         // Count the number of non-null elements. i.e. the number of
236         // constructors we've found
237         int total = countNonNulls( result );
238 
239         // Check we've found at least one constructor
240         if ( total > 0 ) {
241             // Make up a string for the constructor function name
242             String name = "new" + shortClassName( clss.getName() );
243             //  System.out.println( "Defining " + name );
244             // Bind the constructor function name to a Java constructor
245             // function.
246             pack.bindConst(
247                 name,
248                 (
249                     total == 1 ?
250                     (Function)new JConstructor( (Constructor)firstItem( result ) ) :
251                     (Function)new JOConstructor( result )
252                 ).modName( name )
253             );
254         }
255     }
256 
257     /**
258      * Imports all available methods in the Java class. This will ignore
259      * methods which take the same number of parameters.
260      */
261     private void importAllMethods() {
262         // Get the available methods
263         Method[] meths = clss.getMethods();
264         // Create a map to store the available methods in, mapping the method
265         // name to a list of methods with that name
266         EMap< String, EList< Method > > map = new EHashMap< String, EList< Method > >();
267         for ( int i = 0; i < meths.length; i++ ) {
268             Method m = meths[ i ];
269             String name = m.getName();
270             EList< Method > list = map.get( name );
271             if ( list == null ) {
272                 list = new ELinkedList< Method >();
273                 map.insert( name, list );
274             }
275             list.addLast( m );
276         }
277         // Iterate over the available methods and import each one.
278         MapIterator< String, EList< Method > > it = map.iterator( true );
279         while ( it.hasNext() ) {
280             importMethod( it.nextKey(), it.currentValue() );
281         }
282     }
283 
284     /**
285      * Imports the specified list of methods, removing any methods that take
286      * the same number of paramters.
287      *
288      * @param name  the name of the method being imported, which will be the
289      * name bound in the package
290      * @param methods   the list of methods to import
291      */
292     private void importMethod( final String name, final IList< Method > methods ) {
293         // Create a vector to store the available methods in, grouped by
294         // the number of parameters required. e.g. at index 1 will be all the
295         // methods taking 1 parameter, and so on.
296         Vector< EList< Method > > v = new Vector< EList< Method > >();
297         ListIterator< Method > it = methods.iterator( true );
298         while ( it.hasNext() ) {
299             Method m = it.nextValue();
300             // How many parameters does this method take?
301             int n = m.getParameterTypes().length;
302             // Ensure the vector has a position for this number of paramters
303             v.setSize( n + 1 );
304             // Get the list of methods taking this number of paramters
305             EList< Method > list = v.get( n );
306             if ( list == null ) {
307                 list = new ELinkedList< Method >();
308             }
309             // Add this method to the list
310             list.addLast( m );
311             // Put the list into the vector
312             v.set( n, list );
313         }
314 
315         // Prune the duplicates and get an array of methods, indexed by the
316         // number of parameters
317         Method[] result = pruneDups( v, Method.class );
318         // Count the number of non-null elements. i.e. the number of
319         // methods we've found
320         int total = countNonNulls( result );
321         // Check that total is greater than zero? and that the symbol does not
322         // have a pervasive import in this package
323         if ( total > 0 && pack.hasPervasiveImport( name ) == null ) {
324             //  System.out.println( "Assigning " + name );
325             // Bind the method function name to a Java method function.
326             pack.bindConst(
327                 name,
328                 (
329                     total > 0 ?
330                     (Function)JMethod.make( (Method)firstItem( result ) ) :
331                     (Function)new JOMethod( result )
332                 ).modName( name )
333             );
334         }
335     }
336 }