1 19 20 package org.netbeans.nbbuild; 21 22 import java.io.ByteArrayInputStream ; 23 import java.io.ByteArrayOutputStream ; 24 import java.io.DataInput ; 25 import java.io.DataInputStream ; 26 import java.io.File ; 27 import java.io.IOException ; 28 import java.io.InputStream ; 29 import java.util.Arrays ; 30 import java.util.Enumeration ; 31 import java.util.HashMap ; 32 import java.util.HashSet ; 33 import java.util.Map ; 34 import java.util.Set ; 35 import java.util.TreeMap ; 36 import java.util.jar.Attributes ; 37 import java.util.jar.JarEntry ; 38 import java.util.jar.JarFile ; 39 import java.util.jar.Manifest ; 40 import java.util.regex.Pattern ; 41 import org.apache.tools.ant.AntClassLoader; 42 import org.apache.tools.ant.BuildException; 43 import org.apache.tools.ant.Project; 44 import org.apache.tools.ant.Task; 45 import org.apache.tools.ant.types.Path; 46 47 53 public class VerifyClassLinkage extends Task { 54 55 public VerifyClassLinkage() {} 56 57 67 68 private File jar; 69 private boolean failOnError = true; 70 private boolean warnOnDefaultPackage = true; 71 private Path classpath = new Path(getProject()); 72 private String ignores; 73 74 79 public Path createClasspath() { 80 return classpath.createPath(); 81 } 82 83 87 public void setJar(File jar) { 88 this.jar = jar; 89 } 90 91 95 public void setFailOnError(boolean failOnError) { 96 this.failOnError = failOnError; 97 } 98 99 103 public void setIgnores(String ignores) { 104 this.ignores = ignores; 105 } 106 107 111 public void setWarnOnDefaultPackage(boolean warnOnDefaultPackage) { 112 this.warnOnDefaultPackage = warnOnDefaultPackage; 113 } 114 115 public void execute() throws BuildException { 116 if (jar == null) { 117 throw new BuildException("Must specify a JAR file", getLocation()); 118 } 119 try { 120 Map <String ,Boolean > loadable = new HashMap <String ,Boolean >(); 122 Map <String ,byte[]> classfiles = new TreeMap <String ,byte[]>(); 123 read(jar, classfiles, new HashSet <File >()); 124 for (String clazz: classfiles.keySet()) { 125 loadable.put(clazz, Boolean.TRUE); 127 if (warnOnDefaultPackage && clazz.indexOf('.') == -1) { 128 log("Warning: class '" + clazz + "' found in default package", Project.MSG_WARN); 129 } 130 } 131 ClassLoader loader = new AntClassLoader(ClassLoader.getSystemClassLoader().getParent(), getProject(), classpath, true); 132 for (Map.Entry <String , byte[]> entry: classfiles.entrySet()) { 133 String clazz = entry.getKey(); 134 byte[] data = entry.getValue(); 135 verify(clazz, data, loadable, loader); 136 } 137 } catch (IOException e) { 138 throw new BuildException("While verifying " + jar + " or its Class-Path extensions: " + e, e, getLocation()); 139 } 140 } 141 142 private void read(File jar, Map <String ,byte[]> classfiles, Set <File > alreadyRead) throws IOException { 143 if (!alreadyRead.add(jar)) { 144 log("Already read " + jar, Project.MSG_VERBOSE); 145 return; 146 } 147 log("Reading " + jar, Project.MSG_VERBOSE); 148 JarFile jf = new JarFile (jar); 149 Pattern p = (ignores != null)? Pattern.compile(ignores): null; 150 try { 151 Enumeration e = jf.entries(); 152 while (e.hasMoreElements()) { 153 JarEntry entry = (JarEntry ) e.nextElement(); 154 String name = entry.getName(); 155 if (!name.endsWith(".class")) { 156 continue; 157 } 158 String clazz = name.substring(0, name.length() - 6).replace('/', '.'); 159 if (p != null && p.matcher(clazz).matches()) { 160 continue; 161 } 162 ByteArrayOutputStream baos = new ByteArrayOutputStream (Math.max((int) entry.getSize(), 0)); 163 InputStream is = jf.getInputStream(entry); 164 try { 165 byte[] buf = new byte[4096]; 166 int read; 167 while ((read = is.read(buf)) != -1) { 168 baos.write(buf, 0, read); 169 } 170 } finally { 171 is.close(); 172 } 173 classfiles.put(clazz, baos.toByteArray()); 174 } 175 Manifest mf = jf.getManifest(); 176 if (mf != null) { 177 String cp = mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH); 178 if (cp != null) { 179 String [] uris = cp.trim().split("[, ]+"); 180 for (int i = 0; i < uris.length; i++) { 181 File otherJar = new File (jar.toURI().resolve(uris[i])); 182 if (otherJar.isFile()) { 183 read(otherJar, classfiles, alreadyRead); 184 } 185 } 186 } 187 } 188 } finally { 189 jf.close(); 190 } 191 } 192 193 private static void skip(DataInput input, int bytes) throws IOException { 194 int skipped = input.skipBytes(bytes); 195 if (skipped != bytes) { 196 throw new IOException ("Truncated class file"); 197 } 198 } 199 private void verify(String clazz, byte[] data, Map <String ,Boolean > loadable, ClassLoader loader) throws IOException , BuildException { 200 DataInput input = new DataInputStream (new ByteArrayInputStream (data)); 202 skip(input, 8); int size = input.readUnsignedShort() - 1; String [] utf8Strings = new String [size]; 205 boolean[] isClassName = new boolean[size]; 206 for (int i = 0; i < size; i++) { 207 byte tag = input.readByte(); 208 switch (tag) { 209 case 1: utf8Strings[i] = input.readUTF(); 211 break; 212 case 7: int index = input.readUnsignedShort() - 1; 214 if (index >= size) { 215 throw new IOException ("CONSTANT_Class index " + index + " too big for size of pool " + size); 216 } 217 isClassName[index] = true; 219 break; 220 case 3: case 4: case 9: case 10: case 11: case 12: skip(input, 4); 227 break; 228 case 8: skip(input, 2); 230 break; 231 case 5: case 6: skip(input, 8); 234 i++; break; 236 default: 237 throw new IOException ("Unrecognized constant pool tag " + tag + " at index " + i + "; running UTF-8 strings: " + Arrays.asList(utf8Strings)); 238 } 239 } 240 log("UTF-8 strings: " + Arrays.asList(utf8Strings), Project.MSG_DEBUG); 241 for (int i = 0; i < size; i++) { 242 if (!isClassName[i]) { 243 continue; 244 } 245 String vmname = utf8Strings[i]; 246 while (vmname.charAt(0) == '[') { 247 vmname = vmname.substring(1); 249 } 250 if (vmname.length() == 1) { 251 continue; 253 } 254 String clazz2; 255 if (vmname.charAt(vmname.length() - 1) == ';' && vmname.charAt(0) == 'L') { 256 clazz2 = vmname.substring(1, vmname.length() - 1); 258 } else { 259 clazz2 = vmname; 260 } 261 Boolean exists = loadable.get(clazz2.replace('/', '.')); 262 if (exists == null) { 263 exists = Boolean.valueOf(loader.getResource(clazz2 + ".class") != null); 264 loadable.put(clazz2, exists); 265 } 266 if (!exists.booleanValue()) { 267 String message = clazz + " cannot access " + clazz2.replace('/', '.'); 268 if (failOnError) { 269 throw new BuildException(message, getLocation()); 270 } else { 271 log("Warning: " + message, Project.MSG_WARN); 272 } 273 } else { 274 } 276 } 277 } 278 279 } 280 | Popular Tags |