1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.bind.v2.bytecode;
  27 
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.DataInputStream;
  30 import java.io.DataOutputStream;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.util.logging.Level;
  34 import java.util.logging.Logger;
  35 
  36 import com.sun.xml.internal.bind.Util;
  37 
  38 /**
  39  * Replaces a few constant pool tokens from a class "template" and then loads it into the VM.
  40  *
  41  * @author Kohsuke Kawaguchi
  42  */
  43 public final class ClassTailor {
  44 
  45     private ClassTailor() {} // no instanciation please
  46 
  47     private static final Logger logger = Util.getClassLogger();
  48 
  49     /**
  50      * Returns the class name in the JVM format (such as "java/lang/String")
  51      */
  52     public static String toVMClassName( Class c ) {
  53         assert !c.isPrimitive();
  54         if(c.isArray())
  55             // I have no idea why it is designed like this, but javap says so.
  56             return toVMTypeName(c);
  57         return c.getName().replace('.','/');
  58     }
  59 
  60     public static String toVMTypeName( Class c ) {
  61         if(c.isArray()) {
  62             // TODO: study how an array type is encoded.
  63             return '['+toVMTypeName(c.getComponentType());
  64         }
  65         if(c.isPrimitive()) {
  66             if(c==Boolean.TYPE)     return "Z";
  67             if(c==Character.TYPE)   return "C";
  68             if(c==Byte.TYPE)        return "B";
  69             if(c==Double.TYPE)      return "D";
  70             if(c==Float.TYPE)       return "F";
  71             if(c==Integer.TYPE)     return "I";
  72             if(c==Long.TYPE)        return "J";
  73             if(c==Short.TYPE)       return "S";
  74 
  75             throw new IllegalArgumentException(c.getName());
  76         }
  77         return 'L'+c.getName().replace('.','/')+';';
  78     }
  79 
  80 
  81 
  82     public static byte[] tailor( Class templateClass, String newClassName, String... replacements ) {
  83         String vmname = toVMClassName(templateClass);
  84         return tailor(
  85             SecureLoader.getClassClassLoader(templateClass).getResourceAsStream(vmname+".class"),
  86             vmname, newClassName, replacements );
  87     }
  88 
  89 
  90     /**
  91      * Customizes a class file by replacing constant pools.
  92      *
  93      * @param image
  94      *      The image of the template class.
  95      * @param replacements
  96      *      A list of pair of strings that specify the substitution
  97      *      {@code String[]{search_0, replace_0, search_1, replace_1, ..., search_n, replace_n }}
  98      *
  99      *      The search strings found in the constant pool will be replaced by the corresponding
 100      *      replacement string.
 101      */
 102     public static byte[] tailor( InputStream image, String templateClassName, String newClassName, String... replacements ) {
 103         DataInputStream in = new DataInputStream(image);
 104 
 105         try {
 106             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
 107             DataOutputStream out = new DataOutputStream(baos);
 108 
 109             // skip until the constant pool count
 110             long l = in.readLong();
 111             out.writeLong(l);
 112 
 113             // read the constant pool size
 114             short count = in.readShort();
 115             out.writeShort(count);
 116 
 117             // replace constant pools
 118             for( int i=0; i<count; i++ ) {
 119                 byte tag = in.readByte();
 120                 out.writeByte(tag);
 121                 switch(tag) {
 122                 case 0:
 123                     // this isn't described in the spec,
 124                     // but class files often seem to have this '0' tag.
 125                     // we can apparently just ignore it, but not sure
 126                     // what this really means.
 127                     break;
 128 
 129                 case 1: // CONSTANT_UTF8
 130                     {
 131                         String value = in.readUTF();
 132                         if(value.equals(templateClassName))
 133                             value = newClassName;
 134                         else {
 135                             for( int j=0; j<replacements.length; j+=2 )
 136                                 if(value.equals(replacements[j])) {
 137                                     value = replacements[j+1];
 138                                     break;
 139                                 }
 140                         }
 141                         out.writeUTF(value);
 142                     }
 143                 break;
 144 
 145                 case 3: // CONSTANT_Integer
 146                 case 4: // CONSTANT_Float
 147                     out.writeInt(in.readInt());
 148                     break;
 149 
 150                 case 5: // CONSTANT_Long
 151                 case 6: // CONSTANT_Double
 152                     i++; // doubles and longs take two entries
 153                     out.writeLong(in.readLong());
 154                     break;
 155 
 156                 case 7: // CONSTANT_Class
 157                 case 8: // CONSTANT_String
 158                     out.writeShort(in.readShort());
 159                     break;
 160 
 161                 case 9: // CONSTANT_Fieldref
 162                 case 10: // CONSTANT_Methodref
 163                 case 11: // CONSTANT_InterfaceMethodref
 164                 case 12: // CONSTANT_NameAndType
 165                     out.writeInt(in.readInt());
 166                     break;
 167 
 168                 default:
 169                     throw new IllegalArgumentException("Unknown constant type "+tag);
 170                 }
 171             }
 172 
 173             // then copy the rest
 174             byte[] buf = new byte[512];
 175             int len;
 176             while((len=in.read(buf))>0)
 177                 out.write(buf,0,len);
 178 
 179             in.close();
 180             out.close();
 181 
 182             // by now we got the properly tailored class file image
 183             return baos.toByteArray();
 184 
 185         } catch( IOException e ) {
 186             // never happen
 187             logger.log(Level.WARNING,"failed to tailor",e);
 188             return null;
 189         }
 190     }
 191 }