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 */
9package com.vladium.tools;
10
11import java.io.File;
12import java.io.FileOutputStream;
13import java.io.IOException;
14import java.io.InputStream;
15import java.net.URL;
16import java.net.URLClassLoader;
17import java.util.ArrayList;
18import java.util.HashSet;
19import java.util.Iterator;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Properties;
23import java.util.Set;
24import java.util.StringTokenizer;
25
26import com.vladium.jcd.cls.ClassDef;
27import com.vladium.jcd.cls.IConstantCollection;
28import com.vladium.jcd.cls.constant.CONSTANT_Class_info;
29import com.vladium.jcd.cls.constant.CONSTANT_info;
30import com.vladium.jcd.parser.ClassDefParser;
31import com.vladium.util.ByteArrayOStream;
32import com.vladium.util.Descriptors;
33
34// ----------------------------------------------------------------------------
35/**
36 * TODO: doc
37 *
38 * @author Vlad Roubtsov, (C) 2004
39 */
40public class ClassDep
41{
42    // public: ................................................................
43
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
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
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     */
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// ----------------------------------------------------------------------------