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 */ 017package org.apache.bcel.classfile; 018 019import java.io.DataInput; 020import java.io.DataOutputStream; 021import java.io.IOException; 022import java.util.HashMap; 023import java.util.LinkedHashMap; 024import java.util.Map; 025import java.util.Objects; 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>{@link #SYS_PROP_CACHE_MAX_ENTRIES} (since 6.4): The size of the cache, by default 0, meaning caching is 036 * disabled.</li> 037 * <li>{@link #SYS_PROP_CACHE_MAX_ENTRY_SIZE} (since 6.0): The maximum size of the values to cache, by default 200, 0 038 * disables caching. Values larger than this are <em>not</em> cached.</li> 039 * <li>{@link #SYS_PROP_STATISTICS} (since 6.0): Prints statistics on the console when the JVM exits.</li> 040 * </ul> 041 * <p> 042 * Here is a sample Maven invocation with caching disabled: 043 * </p> 044 * 045 * <pre> 046 * mvn test -Dbcel.statistics=true -Dbcel.maxcached.size=0 -Dbcel.maxcached=0 047 * </pre> 048 * <p> 049 * Here is a sample Maven invocation with caching enabled: 050 * </p> 051 * 052 * <pre> 053 * mvn test -Dbcel.statistics=true -Dbcel.maxcached.size=100000 -Dbcel.maxcached=5000000 054 * </pre> 055 * 056 * @see Constant 057 */ 058public final class ConstantUtf8 extends Constant { 059 060 private static final class Cache { 061 062 private static final boolean BCEL_STATISTICS = Boolean.getBoolean(SYS_PROP_STATISTICS); 063 private static final int MAX_ENTRIES = Integer.getInteger(SYS_PROP_CACHE_MAX_ENTRIES, 0).intValue(); 064 private static final int INITIAL_CAPACITY = (int) (MAX_ENTRIES / 0.75); 065 066 private static final HashMap<String, ConstantUtf8> CACHE = new LinkedHashMap<String, ConstantUtf8>(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 MAX_ENTRIES > 0 && MAX_ENTRY_SIZE > 0; 081 } 082 083 } 084 085 // TODO these should perhaps be AtomicInt? 086 private static volatile int considered; 087 private static volatile int created; 088 private static volatile int hits; 089 private static volatile int skipped; 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(ConstantUtf8::printStats)); 098 } 099 } 100 101 /** 102 * Clears the cache. 103 * 104 * @since 6.4.0 105 */ 106 public static synchronized void clearCache() { 107 Cache.CACHE.clear(); 108 } 109 110 // for access by test code 111 static synchronized void clearStats() { 112 hits = considered = skipped = created = 0; 113 } 114 115 // Avoid Spotbugs complaint about Write to static field 116 private static void countCreated() { 117 created++; 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 access 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, SYS_PROP_CACHE_MAX_ENTRY_SIZE, 183 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 if an I/O error occurs. 202 */ 203 ConstantUtf8(final DataInput dataInput) throws IOException { 204 super(Const.CONSTANT_Utf8); 205 value = dataInput.readUTF(); 206 countCreated(); 207 } 208 209 /** 210 * @param value Data 211 */ 212 public ConstantUtf8(final String value) { 213 super(Const.CONSTANT_Utf8); 214 this.value = Objects.requireNonNull(value, "value"); 215 countCreated(); 216 } 217 218 /** 219 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. 220 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 221 * 222 * @param v Visitor object 223 */ 224 @Override 225 public void accept(final Visitor v) { 226 v.visitConstantUtf8(this); 227 } 228 229 /** 230 * Dumps String in Utf8 format to file stream. 231 * 232 * @param file Output file stream 233 * @throws IOException if an I/O error occurs. 234 */ 235 @Override 236 public void dump(final DataOutputStream file) throws IOException { 237 file.writeByte(super.getTag()); 238 file.writeUTF(value); 239 } 240 241 /** 242 * @return Data converted to string. 243 */ 244 public String getBytes() { 245 return value; 246 } 247 248 /** 249 * @param bytes the raw bytes of this UTF-8 250 * @deprecated (since 6.0) 251 */ 252 @java.lang.Deprecated 253 public void setBytes(final String bytes) { 254 throw new UnsupportedOperationException(); 255 } 256 257 /** 258 * @return String representation 259 */ 260 @Override 261 public String toString() { 262 return super.toString() + "(\"" + Utility.replace(value, "\n", "\\n") + "\")"; 263 } 264}