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 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; 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 /** 116 * Gets a new or cached instance of the given value. 117 * <p> 118 * See {@link ConstantUtf8} class Javadoc for details. 119 * </p> 120 * 121 * @param value the value. 122 * @return a new or cached instance of the given value. 123 * @since 6.0 124 */ 125 public static ConstantUtf8 getCachedInstance(final String value) { 126 if (value.length() > Cache.MAX_ENTRY_SIZE) { 127 skipped++; 128 return new ConstantUtf8(value); 129 } 130 considered++; 131 synchronized (ConstantUtf8.class) { // might be better with a specific lock object 132 ConstantUtf8 result = Cache.CACHE.get(value); 133 if (result != null) { 134 hits++; 135 return result; 136 } 137 result = new ConstantUtf8(value); 138 Cache.CACHE.put(value, result); 139 return result; 140 } 141 } 142 143 /** 144 * Gets a new or cached instance of the given value. 145 * <p> 146 * See {@link ConstantUtf8} class Javadoc for details. 147 * </p> 148 * 149 * @param dataInput the value. 150 * @return a new or cached instance of the given value. 151 * @throws IOException if an I/O error occurs. 152 * @since 6.0 153 */ 154 public static ConstantUtf8 getInstance(final DataInput dataInput) throws IOException { 155 return getInstance(dataInput.readUTF()); 156 } 157 158 /** 159 * Gets a new or cached instance of the given value. 160 * <p> 161 * See {@link ConstantUtf8} class Javadoc for details. 162 * </p> 163 * 164 * @param value the value. 165 * @return a new or cached instance of the given value. 166 * @since 6.0 167 */ 168 public static ConstantUtf8 getInstance(final String value) { 169 return Cache.isEnabled() ? getCachedInstance(value) : new ConstantUtf8(value); 170 } 171 172 // for access by test code 173 static void printStats() { 174 final String prefix = "[Apache Commons BCEL]"; 175 System.err.printf("%s Cache hit %,d/%,d, %d skipped.%n", prefix, hits, considered, skipped); 176 System.err.printf("%s Total of %,d ConstantUtf8 objects created.%n", prefix, created); 177 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, 178 Cache.MAX_ENTRY_SIZE); 179 } 180 181 private final String value; 182 183 /** 184 * Initializes from another object. 185 * 186 * @param constantUtf8 the value. 187 */ 188 public ConstantUtf8(final ConstantUtf8 constantUtf8) { 189 this(constantUtf8.getBytes()); 190 } 191 192 /** 193 * Initializes instance from file data. 194 * 195 * @param dataInput Input stream 196 * @throws IOException if an I/O error occurs. 197 */ 198 ConstantUtf8(final DataInput dataInput) throws IOException { 199 super(Const.CONSTANT_Utf8); 200 value = dataInput.readUTF(); 201 created++; 202 } 203 204 /** 205 * @param value Data 206 */ 207 public ConstantUtf8(final String value) { 208 super(Const.CONSTANT_Utf8); 209 this.value = Objects.requireNonNull(value, "value"); 210 created++; 211 } 212 213 /** 214 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. 215 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 216 * 217 * @param v Visitor object 218 */ 219 @Override 220 public void accept(final Visitor v) { 221 v.visitConstantUtf8(this); 222 } 223 224 /** 225 * Dumps String in Utf8 format to file stream. 226 * 227 * @param file Output file stream 228 * @throws IOException if an I/O error occurs. 229 */ 230 @Override 231 public void dump(final DataOutputStream file) throws IOException { 232 file.writeByte(super.getTag()); 233 file.writeUTF(value); 234 } 235 236 /** 237 * @return Data converted to string. 238 */ 239 public String getBytes() { 240 return value; 241 } 242 243 /** 244 * @param bytes the raw bytes of this UTF-8 245 * @deprecated (since 6.0) 246 */ 247 @java.lang.Deprecated 248 public void setBytes(final String bytes) { 249 throw new UnsupportedOperationException(); 250 } 251 252 /** 253 * @return String representation 254 */ 255 @Override 256 public String toString() { 257 return super.toString() + "(\"" + Utility.replace(value, "\n", "\\n") + "\")"; 258 } 259}