001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018package org.apache.bcel.classfile; 019 020import java.io.DataInput; 021import java.io.DataOutputStream; 022import java.io.IOException; 023import java.util.HashMap; 024import java.util.LinkedHashMap; 025import java.util.Map; 026 027import org.apache.bcel.Const; 028 029/** 030 * Extends the abstract {@link Constant} to represent a reference to a UTF-8 encoded string. 031 * <p> 032 * The following system properties govern caching this class performs. 033 * </p> 034 * <ul> 035 * <li>{@value #SYS_PROP_CACHE_MAX_ENTRIES} (since 6.4): The size of the cache, by default 0, meaning caching is disabled.</li> 036 * <li>{@value #SYS_PROP_CACHE_MAX_ENTRY_SIZE} (since 6.0): The maximum size of the values to cache, by default 200, 0 disables 037 * caching. Values larger than this are <em>not</em> cached.</li> 038 * <li>{@value #SYS_PROP_STATISTICS} (since 6.0): Prints statistics on the console when the JVM exits.</li> 039 * </ul> 040 * <p> 041 * Here is a sample Maven invocation with caching disabled: 042 * </p> 043 * 044 * <pre> 045 * mvn test -Dbcel.statistics=true -Dbcel.maxcached.size=0 -Dbcel.maxcached=0 046 * </pre> 047 * <p> 048 * Here is a sample Maven invocation with caching enabled: 049 * </p> 050 * 051 * <pre> 052 * mvn test -Dbcel.statistics=true -Dbcel.maxcached.size=100000 -Dbcel.maxcached=5000000 053 * </pre> 054 * 055 * @see Constant 056 */ 057public final class ConstantUtf8 extends Constant { 058 059 private static class Cache { 060 061 private static final boolean BCEL_STATISTICS = Boolean.getBoolean(SYS_PROP_STATISTICS); 062 private static final int MAX_ENTRIES = Integer.getInteger(SYS_PROP_CACHE_MAX_ENTRIES, 0).intValue(); 063 private static final int INITIAL_CAPACITY = (int) (MAX_ENTRIES / 0.75); 064 065 private static final HashMap<String, ConstantUtf8> CACHE = new LinkedHashMap<String, ConstantUtf8>( 066 INITIAL_CAPACITY, 0.75f, true) { 067 068 private static final long serialVersionUID = -8506975356158971766L; 069 070 @Override 071 protected boolean removeEldestEntry(final Map.Entry<String, ConstantUtf8> eldest) { 072 return size() > MAX_ENTRIES; 073 } 074 }; 075 076 // Set the size to 0 or below to skip caching entirely 077 private static final int MAX_ENTRY_SIZE = Integer.getInteger(SYS_PROP_CACHE_MAX_ENTRY_SIZE, 200).intValue(); 078 079 static boolean isEnabled() { 080 return Cache.MAX_ENTRIES > 0 && MAX_ENTRY_SIZE > 0; 081 } 082 083 } 084 085 // TODO these should perhaps be AtomicInt? 086 private static volatile int considered = 0; 087 private static volatile int created = 0; 088 private static volatile int hits = 0; 089 private static volatile int skipped = 0; 090 091 private static final String SYS_PROP_CACHE_MAX_ENTRIES = "bcel.maxcached"; 092 private static final String SYS_PROP_CACHE_MAX_ENTRY_SIZE = "bcel.maxcached.size"; 093 private static final String SYS_PROP_STATISTICS = "bcel.statistics"; 094 095 static { 096 if (Cache.BCEL_STATISTICS) { 097 Runtime.getRuntime().addShutdownHook(new Thread() { 098 @Override 099 public void run() { 100 printStats(); 101 } 102 }); 103 } 104 } 105 106 /** 107 * Clears the cache. 108 * 109 * @since 6.4.0 110 */ 111 public static synchronized void clearCache() { 112 Cache.CACHE.clear(); 113 } 114 115 // for accesss by test code 116 static synchronized void clearStats() { 117 hits = considered = skipped = created = 0; 118 } 119 120 /** 121 * Gets a new or cached instance of the given value. 122 * <p> 123 * See {@link ConstantUtf8} class Javadoc for details. 124 * </p> 125 * 126 * @param value the value. 127 * @return a new or cached instance of the given value. 128 * @since 6.0 129 */ 130 public static ConstantUtf8 getCachedInstance(final String value) { 131 if (value.length() > Cache.MAX_ENTRY_SIZE) { 132 skipped++; 133 return new ConstantUtf8(value); 134 } 135 considered++; 136 synchronized (ConstantUtf8.class) { // might be better with a specific lock object 137 ConstantUtf8 result = Cache.CACHE.get(value); 138 if (result != null) { 139 hits++; 140 return result; 141 } 142 result = new ConstantUtf8(value); 143 Cache.CACHE.put(value, result); 144 return result; 145 } 146 } 147 148 /** 149 * Gets a new or cached instance of the given value. 150 * <p> 151 * See {@link ConstantUtf8} class Javadoc for details. 152 * </p> 153 * 154 * @param dataInput the value. 155 * @return a new or cached instance of the given value. 156 * @throws IOException if an I/O error occurs. 157 * @since 6.0 158 */ 159 public static ConstantUtf8 getInstance(final DataInput dataInput) throws IOException { 160 return getInstance(dataInput.readUTF()); 161 } 162 163 /** 164 * Gets a new or cached instance of the given value. 165 * <p> 166 * See {@link ConstantUtf8} class Javadoc for details. 167 * </p> 168 * 169 * @param value the value. 170 * @return a new or cached instance of the given value. 171 * @since 6.0 172 */ 173 public static ConstantUtf8 getInstance(final String value) { 174 return Cache.isEnabled() ? getCachedInstance(value) : new ConstantUtf8(value); 175 } 176 177 // for accesss by test code 178 static void printStats() { 179 final String prefix = "[Apache Commons BCEL]"; 180 System.err.printf("%s Cache hit %,d/%,d, %d skipped.%n", prefix, hits, considered, skipped); 181 System.err.printf("%s Total of %,d ConstantUtf8 objects created.%n", prefix, created); 182 System.err.printf("%s Configuration: %s=%,d, %s=%,d.%n", prefix, SYS_PROP_CACHE_MAX_ENTRIES, Cache.MAX_ENTRIES, 183 SYS_PROP_CACHE_MAX_ENTRY_SIZE, Cache.MAX_ENTRY_SIZE); 184 } 185 186 private final String value; 187 188 /** 189 * Initializes from another object. 190 * 191 * @param constantUtf8 the value. 192 */ 193 public ConstantUtf8(final ConstantUtf8 constantUtf8) { 194 this(constantUtf8.getBytes()); 195 } 196 197 /** 198 * Initializes instance from file data. 199 * 200 * @param dataInput Input stream 201 * @throws IOException 202 */ 203 ConstantUtf8(final DataInput dataInput) throws IOException { 204 super(Const.CONSTANT_Utf8); 205 value = dataInput.readUTF(); 206 created++; 207 } 208 209 /** 210 * @param value Data 211 */ 212 public ConstantUtf8(final String value) { 213 super(Const.CONSTANT_Utf8); 214 if (value == null) { 215 throw new IllegalArgumentException("Value must not be null."); 216 } 217 this.value = value; 218 created++; 219 } 220 221 /** 222 * Called by objects that are traversing the nodes of the tree implicitely defined by the contents of a Java class. 223 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 224 * 225 * @param v Visitor object 226 */ 227 @Override 228 public void accept(final Visitor v) { 229 v.visitConstantUtf8(this); 230 } 231 232 /** 233 * Dumps String in Utf8 format to file stream. 234 * 235 * @param file Output file stream 236 * @throws IOException 237 */ 238 @Override 239 public void dump(final DataOutputStream file) throws IOException { 240 file.writeByte(super.getTag()); 241 file.writeUTF(value); 242 } 243 244 /** 245 * @return Data converted to string. 246 */ 247 public String getBytes() { 248 return value; 249 } 250 251 /** 252 * @param bytes the raw bytes of this UTF-8 253 * @deprecated (since 6.0) 254 */ 255 @java.lang.Deprecated 256 public void setBytes(final String bytes) { 257 throw new UnsupportedOperationException(); 258 } 259 260 /** 261 * @return String representation 262 */ 263 @Override 264 public String toString() { 265 return super.toString() + "(\"" + Utility.replace(value, "\n", "\\n") + "\")"; 266 } 267}