1 /* Copyright (C) 2004 Vladimir Roubtsov. All rights reserved.
2  *
3  * This program and the accompanying materials are made available under
4  * the terms of the Common Public License v1.0 which accompanies this distribution,
5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
6  *
7  * $Id: ClassDep.java,v 1.1.2.2 2004/07/09 01:42:04 vlad_r Exp $
8  */
9 package com.vladium.tools;
10 
11 import java.io.File;
12 import java.io.FileOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.net.URL;
16 import java.net.URLClassLoader;
17 import java.util.ArrayList;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Properties;
23 import java.util.Set;
24 import java.util.StringTokenizer;
25 
26 import com.vladium.jcd.cls.ClassDef;
27 import com.vladium.jcd.cls.IConstantCollection;
28 import com.vladium.jcd.cls.constant.CONSTANT_Class_info;
29 import com.vladium.jcd.cls.constant.CONSTANT_info;
30 import com.vladium.jcd.parser.ClassDefParser;
31 import com.vladium.util.ByteArrayOStream;
32 import com.vladium.util.Descriptors;
33 
34 // ----------------------------------------------------------------------------
35 /**
36  * TODO: doc
37  *
38  * @author Vlad Roubtsov, (C) 2004
39  */
40 public class ClassDep
41 {
42     // public: ................................................................
43 
main(final String [] args)44     public static void main (final String [] args)
45         throws Exception
46     {
47         if (args.length < 2)
48             throw new IllegalArgumentException ("usage: classpath output_file rootset_classname_1 [rootset_classname_2 ...]");
49 
50         final String _classPath = args [0];
51         final URL [] classPath;
52         {
53             final StringTokenizer tokenizer = new StringTokenizer (_classPath, File.pathSeparator);
54             classPath = new URL [tokenizer.countTokens ()];
55 
56             for (int i = 0; tokenizer.hasMoreTokens (); ++ i)
57             {
58                 classPath [i] = new File (tokenizer.nextToken ()).toURL ();
59             }
60         }
61 
62         final File outFile = new File (args [1]);
63 
64         final int offset = 2;
65         final String [] rootSet = args.length > offset
66             ? new String [args.length - offset]
67             : new String [0];
68         {
69             for (int a = 0; a < rootSet.length; ++ a)
70             {
71                 rootSet [a] = args [a + offset];
72             }
73         }
74 
75         final ClassDep _this = new ClassDep (rootSet, classPath);
76         final String [] deps = _this.getDependencies (true);
77 
78         final StringBuffer s = new StringBuffer ();
79         for (int d = deps.length - 1; d >= 0; -- d) // reverse topological order
80         {
81             s.append (deps [d]);
82             if (d > 0) s.append (',');
83         }
84 
85         final File parent = outFile.getParentFile ();
86         if (parent != null) parent.mkdirs ();
87 
88         final FileOutputStream out = new FileOutputStream (outFile);
89 
90         final Properties result = new Properties ();
91         result.setProperty ("closure", s.toString ());
92 
93         result.store (out, "this file is auto-generated, do not edit");
94 
95         out.close ();
96     }
97 
98 
ClassDep(final String [] rootSet, final URL [] classPath)99     public ClassDep (final String [] rootSet, final URL [] classPath)
100     {
101         if (rootSet == null)
102             throw new IllegalArgumentException ("null input: rootSet");
103 
104         if (classPath == null)
105             throw new IllegalArgumentException ("null input: classPath");
106 
107         m_rootSet = rootSet;
108         m_classPath = classPath;
109     }
110 
getDependencies(final boolean includeRootSet)111     public String [] getDependencies (final boolean includeRootSet)
112         throws IOException
113     {
114         final Set /* class Java name:String */ _result = new HashSet ();
115         final ClassLoader loader = new URLClassLoader (m_classPath, null);
116 
117         if (includeRootSet)
118         {
119             for (int i = 0; i < m_rootSet.length; ++ i)
120             {
121                 _result.add (m_rootSet [i]);
122             }
123         }
124 
125         final LinkedList /* class VM name:String */ queue = new LinkedList ();
126         for (int i = 0; i < m_rootSet.length; ++ i)
127         {
128             queue.add (Descriptors.javaNameToVMName (m_rootSet [i]));
129         }
130 
131         final ByteArrayOStream baos = new ByteArrayOStream (8 * 1024);
132         final byte [] readbuf = new byte [8 * 1024];
133 
134         while (! queue.isEmpty ())
135         {
136             final String classVMName = (String) queue.removeFirst ();
137 
138             // keep at most one file descriptor open:
139 
140             InputStream in = null;
141             try
142             {
143                 // NOTE: getResources() not used
144                 in = loader.getResourceAsStream (classVMName + ".class");
145 
146                 if (in == null)
147                 {
148                     throw new IllegalArgumentException ("class [" + Descriptors.vmNameToJavaName (classVMName) + "] not found in the input classpath");
149                 }
150                 else
151                 {
152                     baos.reset ();
153                     for (int read; (read = in.read (readbuf)) >= 0; baos.write (readbuf, 0, read));
154                 }
155             }
156             finally
157             {
158                 if (in != null) try { in.close (); } catch (IOException ignore) { ignore.printStackTrace (); }
159             }
160             in = null;
161 
162             final ClassDef cls = ClassDefParser.parseClass (baos.getByteArray (), baos.size ());
163             final List /* class VM name:String */ clsDeps  = getCONSTANT_Class_info (cls);
164 
165             for (Iterator i = clsDeps.iterator (); i.hasNext (); )
166             {
167                 final String classDepVMName = (String) i.next ();
168 
169                 if (classDepVMName.startsWith ("com/vladium/")) // TODO: generic filtering
170                 {
171                     if (_result.add (Descriptors.vmNameToJavaName (classDepVMName)))
172                     {
173                         queue.addLast (classDepVMName);
174                     }
175                 }
176             }
177         }
178 
179         final String [] result = new String [_result.size ()];
180         _result.toArray (result);
181 
182         return result;
183     }
184 
185     /**
186      * @return array of class VM names [may contain duplicates]
187      */
getCONSTANT_Class_info(final ClassDef cls)188     public static List getCONSTANT_Class_info (final ClassDef cls)
189     {
190         if (cls == null)
191             throw new IllegalArgumentException ("null input: cls");
192 
193         final IConstantCollection constants = cls.getConstants ();
194         final IConstantCollection.IConstantIterator i = constants.iterator ();
195 
196         final List result = new ArrayList ();
197 
198         for (CONSTANT_info entry; (entry = i.nextConstant ()) != null; )
199         {
200             if (entry instanceof CONSTANT_Class_info)
201             {
202                 result.add (((CONSTANT_Class_info) entry).getName (cls));
203             }
204         }
205 
206         return result;
207     }
208 
209     // protected: .............................................................
210 
211     // package: ...............................................................
212 
213     // private: ...............................................................
214 
215 
216     private final String [] m_rootSet;
217     private final URL [] m_classPath;
218 
219 } // end of class
220 // ----------------------------------------------------------------------------