1 | /******************************************************************************* |
2 | * Copyright (c) 2007, 2009 IBM Corporation and others. |
3 | * All rights reserved. This program and the accompanying materials |
4 | * are made available under the terms of the Eclipse Public License v1.0 |
5 | * which accompanies this distribution, and is available at |
6 | * http://www.eclipse.org/legal/epl-v10.html |
7 | * |
8 | * Contributors: |
9 | * IBM Corporation - initial API and implementation |
10 | *******************************************************************************/ |
11 | |
12 | package org.eclipse.pde.api.tools.internal.model; |
13 | |
14 | import java.io.ByteArrayInputStream; |
15 | import java.io.DataInputStream; |
16 | import java.io.IOException; |
17 | import java.io.PrintWriter; |
18 | import java.io.StringWriter; |
19 | import java.util.HashMap; |
20 | import java.util.Map; |
21 | |
22 | import org.eclipse.core.runtime.CoreException; |
23 | import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
24 | import org.eclipse.pde.api.tools.internal.model.StubArchiveApiTypeContainer.ArchiveApiTypeRoot; |
25 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
26 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
27 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; |
28 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; |
29 | import org.objectweb.asm.AnnotationVisitor; |
30 | import org.objectweb.asm.ClassAdapter; |
31 | import org.objectweb.asm.ClassReader; |
32 | import org.objectweb.asm.ClassVisitor; |
33 | import org.objectweb.asm.FieldVisitor; |
34 | import org.objectweb.asm.Label; |
35 | import org.objectweb.asm.MethodAdapter; |
36 | import org.objectweb.asm.MethodVisitor; |
37 | import org.objectweb.asm.Opcodes; |
38 | import org.objectweb.asm.tree.ClassNode; |
39 | import org.objectweb.asm.util.TraceAnnotationVisitor; |
40 | |
41 | /** |
42 | * Class adapter used to create an API type structure |
43 | */ |
44 | public class TypeStructureBuilder extends ClassAdapter { |
45 | ApiType fType; |
46 | IApiComponent fComponent; |
47 | IApiTypeRoot fFile; |
48 | |
49 | /** |
50 | * Builds a type structure for a class file. Note that if an API |
51 | * component is not specified, then some operations on the resulting |
52 | * {@link IApiType} will not be available (navigating super types, |
53 | * member types, etc). |
54 | * |
55 | * @param cv class file visitor |
56 | * @param component originating API component or <code>null</code> if unknown |
57 | */ |
58 | TypeStructureBuilder(ClassVisitor cv, IApiComponent component, IApiTypeRoot file) { |
59 | super(cv); |
60 | fComponent = component; |
61 | fFile = file; |
62 | } |
63 | |
64 | /** |
65 | * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) |
66 | */ |
67 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { |
68 | StringBuffer simpleSig = new StringBuffer(); |
69 | simpleSig.append('L'); |
70 | simpleSig.append(name); |
71 | simpleSig.append(';'); |
72 | String enclosingName = null; |
73 | int index = name.lastIndexOf('$'); |
74 | if (index > -1) { |
75 | enclosingName = name.substring(0, index).replace('/', '.'); |
76 | } |
77 | int laccess = access; |
78 | // TODO: inner types should be have enclosing type as parent instead of component |
79 | if ((laccess & Opcodes.ACC_DEPRECATED) != 0) { |
80 | laccess &= ~Opcodes.ACC_DEPRECATED; |
81 | laccess |= ClassFileConstants.AccDeprecated; |
82 | } |
83 | fType = new ApiType(fComponent, name.replace('/', '.'), simpleSig.toString(), signature, laccess, enclosingName, fFile); |
84 | if (superName != null) { |
85 | fType.setSuperclassName(superName.replace('/', '.')); |
86 | } |
87 | if (interfaces != null && interfaces.length > 0) { |
88 | String[] names = new String[interfaces.length]; |
89 | for (int i = 0; i < names.length; i++) { |
90 | names[i] = interfaces[i].replace('/', '.'); |
91 | } |
92 | fType.setSuperInterfaceNames(names); |
93 | } |
94 | super.visit(version, laccess, name, signature, superName, interfaces); |
95 | } |
96 | /** |
97 | * @see org.objectweb.asm.ClassAdapter#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int) |
98 | */ |
99 | public void visitInnerClass(String name, String outerName, String innerName, int access) { |
100 | super.visitInnerClass(name, outerName, innerName, access); |
101 | String currentName = name.replace('/', '.'); |
102 | if (currentName.equals(fType.getName())) { |
103 | if (innerName == null) { |
104 | fType.setAnonymous(); |
105 | } |
106 | else if(outerName == null) { |
107 | fType.setLocal(); |
108 | fType.setSimpleName(innerName); |
109 | } |
110 | } |
111 | if (outerName != null && innerName != null) { |
112 | // technically speaking innerName != null is not necessary, but this is a workaround for some |
113 | // bogus synthetic types created by another compiler |
114 | String currentOuterName = outerName.replace('/', '.'); |
115 | if (currentOuterName.equals(fType.getName())) { |
116 | // this is a real type member defined in the descriptor (not just a reference to a type member) |
117 | fType.addMemberType(currentName, access); |
118 | } else if (currentName.equals(fType.getName())) { |
119 | fType.setModifiers(access); |
120 | fType.setSimpleName(innerName); |
121 | fType.setMemberType(); |
122 | } |
123 | } |
124 | } |
125 | |
126 | /* (non-Javadoc) |
127 | * @see org.objectweb.asm.ClassAdapter#visitOuterClass(java.lang.String, java.lang.String, java.lang.String) |
128 | */ |
129 | public void visitOuterClass(String owner, String name, String desc) { |
130 | fType.setEnclosingMethodInfo(name, desc); |
131 | } |
132 | |
133 | /* (non-Javadoc) |
134 | * @see org.objectweb.asm.ClassAdapter#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object) |
135 | */ |
136 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { |
137 | int laccess = access; |
138 | if ((access & Opcodes.ACC_DEPRECATED) != 0) { |
139 | laccess &= ~Opcodes.ACC_DEPRECATED; |
140 | laccess |= ClassFileConstants.AccDeprecated; |
141 | } |
142 | fType.addField(name, desc, signature, laccess, value); |
143 | return null; |
144 | } |
145 | |
146 | /* (non-Javadoc) |
147 | * @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) |
148 | */ |
149 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
150 | String[] names = null; |
151 | int laccess = access; |
152 | if ((laccess & Opcodes.ACC_DEPRECATED) != 0) { |
153 | laccess &= ~Opcodes.ACC_DEPRECATED; |
154 | laccess |= ClassFileConstants.AccDeprecated; |
155 | } |
156 | if (exceptions != null && exceptions.length > 0) { |
157 | names = new String[exceptions.length]; |
158 | for (int i = 0; i < names.length; i++) { |
159 | names[i] = exceptions[i].replace('/', '.'); |
160 | } |
161 | } |
162 | final ApiMethod method = fType.addMethod(name, desc, signature, laccess, names); |
163 | return new MethodAdapter(super.visitMethod(laccess, name, desc, signature, exceptions)) { |
164 | public AnnotationVisitor visitAnnotationDefault() { |
165 | return new TraceAnnotationVisitor() { |
166 | public void visitEnd() { |
167 | super.visitEnd(); |
168 | StringWriter stringWriter = new StringWriter(); |
169 | PrintWriter writer = new PrintWriter(stringWriter); |
170 | print(writer); |
171 | writer.flush(); |
172 | writer.close(); |
173 | String def = String.valueOf(stringWriter.getBuffer()); |
174 | method.setDefaultValue(def); |
175 | } |
176 | }; |
177 | } |
178 | }; |
179 | } |
180 | |
181 | /** |
182 | * Builds a type structure with the given .class file bytes in the specified |
183 | * API component. |
184 | * |
185 | * @param bytes class file bytes |
186 | * @param component originating API component |
187 | * @param file associated class file |
188 | * @return |
189 | */ |
190 | public static IApiType buildTypeStructure(byte[] bytes, IApiComponent component, IApiTypeRoot file) { |
191 | TypeStructureBuilder visitor = new TypeStructureBuilder(new ClassNode(), component, file); |
192 | try { |
193 | ClassReader classReader = new ClassReader(bytes); |
194 | classReader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); |
195 | } catch (ArrayIndexOutOfBoundsException e) { |
196 | ApiPlugin.log(e); |
197 | } |
198 | return visitor.fType; |
199 | } |
200 | /** |
201 | * Builds a type structure with the given .class file bytes in the specified |
202 | * API component. |
203 | * |
204 | * @param bytes class file bytes |
205 | * @param component originating API component |
206 | * @param file associated class file |
207 | * @return |
208 | */ |
209 | public static void setEnclosingMethod(IApiType enclosingType, ApiType currentAnonymousLocalType) { |
210 | IApiTypeRoot typeRoot = enclosingType.getTypeRoot(); |
211 | if (typeRoot instanceof AbstractApiTypeRoot) { |
212 | AbstractApiTypeRoot abstractApiTypeRoot = (AbstractApiTypeRoot) typeRoot; |
213 | EnclosingMethodSetter visitor = new EnclosingMethodSetter(new ClassNode(), currentAnonymousLocalType.getName()); |
214 | try { |
215 | ClassReader classReader = new ClassReader(abstractApiTypeRoot.getContents()); |
216 | classReader.accept(visitor, ClassReader.SKIP_FRAMES); |
217 | } catch (ArrayIndexOutOfBoundsException e) { |
218 | ApiPlugin.log(e); |
219 | } catch(CoreException e) { |
220 | // bytes could not be retrieved for abstractApiTypeRoot |
221 | ApiPlugin.log(e); |
222 | } |
223 | if (visitor.found) { |
224 | currentAnonymousLocalType.setEnclosingMethodInfo(visitor.name, visitor.signature); |
225 | } |
226 | } |
227 | } |
228 | static class EnclosingMethodSetter extends ClassAdapter { |
229 | String name; |
230 | String signature; |
231 | boolean found = false; |
232 | String typeName; |
233 | |
234 | public EnclosingMethodSetter(ClassVisitor cv, String typeName) { |
235 | super(cv); |
236 | this.typeName = typeName.replace('.', '/'); |
237 | } |
238 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
239 | if ("<clinit>".equals(name)) { //$NON-NLS-1$ |
240 | return null; |
241 | } |
242 | if (!this.found) { |
243 | if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0) { |
244 | this.name = name; |
245 | this.signature = desc; |
246 | if (signature != null) { |
247 | this.signature = signature; |
248 | } |
249 | MethodVisitor mv; |
250 | if ("<init>".equals(name)) { //$NON-NLS-1$ |
251 | mv = new TypeNameFinderInConstructor(cv.visitMethod(access, name, desc, signature, exceptions), this); |
252 | } else { |
253 | mv = new TypeNameFinder(cv.visitMethod(access, name, desc, signature, exceptions), this); |
254 | } |
255 | return mv; |
256 | } |
257 | } |
258 | return null; |
259 | } |
260 | } |
261 | static class TypeNameFinder extends MethodAdapter { |
262 | protected EnclosingMethodSetter setter; |
263 | |
264 | public TypeNameFinder(MethodVisitor mv, EnclosingMethodSetter enclosingMethodSetter) { |
265 | super(mv); |
266 | this.setter = enclosingMethodSetter; |
267 | } |
268 | public void visitTypeInsn(int opcode, String type) { |
269 | if (setter.typeName.equals(type)) { |
270 | setter.found = true; |
271 | } |
272 | } |
273 | } |
274 | static class TypeNameFinderInConstructor extends TypeNameFinder { |
275 | int lineNumberStart; |
276 | int matchingLineNumber; |
277 | int currentLineNumber = -1; |
278 | |
279 | public TypeNameFinderInConstructor(MethodVisitor mv, EnclosingMethodSetter enclosingMethodSetter) { |
280 | super(mv, enclosingMethodSetter); |
281 | } |
282 | /* (non-Javadoc) |
283 | * @see org.objectweb.asm.MethodAdapter#visitFieldInsn(int, java.lang.String, java.lang.String, java.lang.String) |
284 | */ |
285 | public void visitFieldInsn(int opcode, String owner, String name, |
286 | String desc) { |
287 | super.visitFieldInsn(opcode, owner, name, desc); |
288 | } |
289 | public void visitTypeInsn(int opcode, String type) { |
290 | if (!setter.found && setter.typeName.equals(type)) { |
291 | this.matchingLineNumber = this.currentLineNumber; |
292 | setter.found = true; |
293 | } |
294 | } |
295 | public void visitLineNumber(int line, Label start) { |
296 | if (this.currentLineNumber == -1) { |
297 | this.lineNumberStart = line; |
298 | } |
299 | this.currentLineNumber = line; |
300 | } |
301 | public void visitEnd() { |
302 | if (setter.found) { |
303 | // check that the line number is between the constructor bounds |
304 | if (this.matchingLineNumber < this.lineNumberStart || this.matchingLineNumber > this.currentLineNumber) { |
305 | setter.found = false; |
306 | } |
307 | } |
308 | } |
309 | } |
310 | /* (non-Javadoc) |
311 | * @see java.lang.Object#toString() |
312 | */ |
313 | public String toString() { |
314 | StringBuffer buffer = new StringBuffer(); |
315 | buffer.append("Type structure builder for: ").append(fType.getName()); //$NON-NLS-1$ |
316 | buffer.append("\nBacked by file: ").append(fFile.getName()); //$NON-NLS-1$ |
317 | return buffer.toString(); |
318 | } |
319 | |
320 | public static IApiType buildStubTypeStructure(byte[] contents, |
321 | IApiComponent apiComponent, ArchiveApiTypeRoot archiveApiTypeRoot) { |
322 | // decode the byte[] |
323 | DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(contents)); |
324 | ApiType type = null; |
325 | try { |
326 | Map pool = new HashMap(); |
327 | short currentVersion = inputStream.readShort(); // read file version (for now there is only one version) |
328 | short poolSize = inputStream.readShort(); |
329 | for (int i = 0; i < poolSize; i++) { |
330 | String readUtf = inputStream.readUTF(); |
331 | int index = inputStream.readShort(); |
332 | pool.put(new Integer(index), readUtf); |
333 | } |
334 | int access = 0; |
335 | // access flag was added in version 2 of the stub format |
336 | if (currentVersion == 2) { |
337 | access = inputStream.readChar(); |
338 | } |
339 | int classIndex = inputStream.readShort(); |
340 | String name = (String) pool.get(new Integer(classIndex)); |
341 | StringBuffer simpleSig = new StringBuffer(); |
342 | simpleSig.append('L'); |
343 | simpleSig.append(name); |
344 | simpleSig.append(';'); |
345 | type = new ApiType(apiComponent, name.replace('/', '.'), simpleSig.toString(), null, access, null, archiveApiTypeRoot); |
346 | int superclassNameIndex = inputStream.readShort(); |
347 | if (superclassNameIndex != -1) { |
348 | String superclassName = (String) pool.get(new Integer(superclassNameIndex)); |
349 | type.setSuperclassName(superclassName.replace('/', '.')); |
350 | } |
351 | int interfacesLength = inputStream.readShort(); |
352 | if (interfacesLength != 0) { |
353 | String[] names = new String[interfacesLength]; |
354 | for (int i = 0; i < names.length; i++) { |
355 | String interfaceName = (String) pool.get(new Integer(inputStream.readShort())); |
356 | names[i] = interfaceName.replace('/', '.'); |
357 | } |
358 | type.setSuperInterfaceNames(names); |
359 | } |
360 | int fieldsLength = inputStream.readShort(); |
361 | for (int i = 0; i < fieldsLength; i++) { |
362 | String fieldName = (String) pool.get(new Integer(inputStream.readShort())); |
363 | type.addField(fieldName, null, null, 0, null); |
364 | } |
365 | int methodsLength = inputStream.readShort(); |
366 | for (int i = 0; i < methodsLength; i++) { |
367 | String methodSelector = (String) pool.get(new Integer(inputStream.readShort())); |
368 | String methodSignature = (String) pool.get(new Integer(inputStream.readShort())); |
369 | type.addMethod(methodSelector, methodSignature, null, 0, null); |
370 | } |
371 | } catch (IOException e) { |
372 | ApiPlugin.log(e); |
373 | } finally { |
374 | try { |
375 | inputStream.close(); |
376 | } catch (IOException e) { |
377 | // ignore |
378 | } |
379 | } |
380 | return type; |
381 | } |
382 | } |