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