ConstantUtf8.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.bcel.classfile;

  18. import java.io.DataInput;
  19. import java.io.DataOutputStream;
  20. import java.io.IOException;
  21. import java.util.HashMap;
  22. import java.util.LinkedHashMap;
  23. import java.util.Map;
  24. import java.util.Objects;

  25. import org.apache.bcel.Const;

  26. /**
  27.  * Extends the abstract {@link Constant} to represent a reference to a UTF-8 encoded string.
  28.  * <p>
  29.  * The following system properties govern caching this class performs.
  30.  * </p>
  31.  * <ul>
  32.  * <li>{@link #SYS_PROP_CACHE_MAX_ENTRIES} (since 6.4): The size of the cache, by default 0, meaning caching is
  33.  * disabled.</li>
  34.  * <li>{@link #SYS_PROP_CACHE_MAX_ENTRY_SIZE} (since 6.0): The maximum size of the values to cache, by default 200, 0
  35.  * disables caching. Values larger than this are <em>not</em> cached.</li>
  36.  * <li>{@link #SYS_PROP_STATISTICS} (since 6.0): Prints statistics on the console when the JVM exits.</li>
  37.  * </ul>
  38.  * <p>
  39.  * Here is a sample Maven invocation with caching disabled:
  40.  * </p>
  41.  *
  42.  * <pre>
  43.  * mvn test -Dbcel.statistics=true -Dbcel.maxcached.size=0 -Dbcel.maxcached=0
  44.  * </pre>
  45.  * <p>
  46.  * Here is a sample Maven invocation with caching enabled:
  47.  * </p>
  48.  *
  49.  * <pre>
  50.  * mvn test -Dbcel.statistics=true -Dbcel.maxcached.size=100000 -Dbcel.maxcached=5000000
  51.  * </pre>
  52.  *
  53.  * @see Constant
  54.  */
  55. public final class ConstantUtf8 extends Constant {

  56.     private static final class Cache {

  57.         private static final boolean BCEL_STATISTICS = Boolean.getBoolean(SYS_PROP_STATISTICS);
  58.         private static final int MAX_ENTRIES = Integer.getInteger(SYS_PROP_CACHE_MAX_ENTRIES, 0).intValue();
  59.         private static final int INITIAL_CAPACITY = (int) (MAX_ENTRIES / 0.75);

  60.         private static final HashMap<String, ConstantUtf8> CACHE = new LinkedHashMap<String, ConstantUtf8>(INITIAL_CAPACITY, 0.75f, true) {

  61.             private static final long serialVersionUID = -8506975356158971766L;

  62.             @Override
  63.             protected boolean removeEldestEntry(final Map.Entry<String, ConstantUtf8> eldest) {
  64.                 return size() > MAX_ENTRIES;
  65.             }
  66.         };

  67.         // Set the size to 0 or below to skip caching entirely
  68.         private static final int MAX_ENTRY_SIZE = Integer.getInteger(SYS_PROP_CACHE_MAX_ENTRY_SIZE, 200).intValue();

  69.         static boolean isEnabled() {
  70.             return MAX_ENTRIES > 0 && MAX_ENTRY_SIZE > 0;
  71.         }

  72.     }

  73.     // TODO these should perhaps be AtomicInt?
  74.     private static volatile int considered;
  75.     private static volatile int created;
  76.     private static volatile int hits;
  77.     private static volatile int skipped;

  78.     private static final String SYS_PROP_CACHE_MAX_ENTRIES = "bcel.maxcached";
  79.     private static final String SYS_PROP_CACHE_MAX_ENTRY_SIZE = "bcel.maxcached.size";
  80.     private static final String SYS_PROP_STATISTICS = "bcel.statistics";

  81.     static {
  82.         if (Cache.BCEL_STATISTICS) {
  83.             Runtime.getRuntime().addShutdownHook(new Thread(ConstantUtf8::printStats));
  84.         }
  85.     }

  86.     /**
  87.      * Clears the cache.
  88.      *
  89.      * @since 6.4.0
  90.      */
  91.     public static synchronized void clearCache() {
  92.         Cache.CACHE.clear();
  93.     }

  94.     // for access by test code
  95.     static synchronized void clearStats() {
  96.         hits = considered = skipped = created = 0;
  97.     }

  98.     // Avoid Spotbugs complaint about Write to static field
  99.     private static void countCreated() {
  100.         created++;
  101.     }

  102.     /**
  103.      * Gets a new or cached instance of the given value.
  104.      * <p>
  105.      * See {@link ConstantUtf8} class Javadoc for details.
  106.      * </p>
  107.      *
  108.      * @param value the value.
  109.      * @return a new or cached instance of the given value.
  110.      * @since 6.0
  111.      */
  112.     public static ConstantUtf8 getCachedInstance(final String value) {
  113.         if (value.length() > Cache.MAX_ENTRY_SIZE) {
  114.             skipped++;
  115.             return new ConstantUtf8(value);
  116.         }
  117.         considered++;
  118.         synchronized (ConstantUtf8.class) { // might be better with a specific lock object
  119.             ConstantUtf8 result = Cache.CACHE.get(value);
  120.             if (result != null) {
  121.                 hits++;
  122.                 return result;
  123.             }
  124.             result = new ConstantUtf8(value);
  125.             Cache.CACHE.put(value, result);
  126.             return result;
  127.         }
  128.     }

  129.     /**
  130.      * Gets a new or cached instance of the given value.
  131.      * <p>
  132.      * See {@link ConstantUtf8} class Javadoc for details.
  133.      * </p>
  134.      *
  135.      * @param dataInput the value.
  136.      * @return a new or cached instance of the given value.
  137.      * @throws IOException if an I/O error occurs.
  138.      * @since 6.0
  139.      */
  140.     public static ConstantUtf8 getInstance(final DataInput dataInput) throws IOException {
  141.         return getInstance(dataInput.readUTF());
  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 value the value.
  150.      * @return a new or cached instance of the given value.
  151.      * @since 6.0
  152.      */
  153.     public static ConstantUtf8 getInstance(final String value) {
  154.         return Cache.isEnabled() ? getCachedInstance(value) : new ConstantUtf8(value);
  155.     }

  156.     // for access by test code
  157.     static void printStats() {
  158.         final String prefix = "[Apache Commons BCEL]";
  159.         System.err.printf("%s Cache hit %,d/%,d, %d skipped.%n", prefix, hits, considered, skipped);
  160.         System.err.printf("%s Total of %,d ConstantUtf8 objects created.%n", prefix, created);
  161.         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,
  162.             Cache.MAX_ENTRY_SIZE);
  163.     }

  164.     private final String value;

  165.     /**
  166.      * Initializes from another object.
  167.      *
  168.      * @param constantUtf8 the value.
  169.      */
  170.     public ConstantUtf8(final ConstantUtf8 constantUtf8) {
  171.         this(constantUtf8.getBytes());
  172.     }

  173.     /**
  174.      * Initializes instance from file data.
  175.      *
  176.      * @param dataInput Input stream
  177.      * @throws IOException if an I/O error occurs.
  178.      */
  179.     ConstantUtf8(final DataInput dataInput) throws IOException {
  180.         super(Const.CONSTANT_Utf8);
  181.         value = dataInput.readUTF();
  182.         countCreated();
  183.     }

  184.     /**
  185.      * @param value Data
  186.      */
  187.     public ConstantUtf8(final String value) {
  188.         super(Const.CONSTANT_Utf8);
  189.         this.value = Objects.requireNonNull(value, "value");
  190.         countCreated();
  191.     }

  192.     /**
  193.      * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
  194.      * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
  195.      *
  196.      * @param v Visitor object
  197.      */
  198.     @Override
  199.     public void accept(final Visitor v) {
  200.         v.visitConstantUtf8(this);
  201.     }

  202.     /**
  203.      * Dumps String in Utf8 format to file stream.
  204.      *
  205.      * @param file Output file stream
  206.      * @throws IOException if an I/O error occurs.
  207.      */
  208.     @Override
  209.     public void dump(final DataOutputStream file) throws IOException {
  210.         file.writeByte(super.getTag());
  211.         file.writeUTF(value);
  212.     }

  213.     /**
  214.      * @return Data converted to string.
  215.      */
  216.     public String getBytes() {
  217.         return value;
  218.     }

  219.     /**
  220.      * @param bytes the raw bytes of this UTF-8
  221.      * @deprecated (since 6.0)
  222.      */
  223.     @java.lang.Deprecated
  224.     public void setBytes(final String bytes) {
  225.         throw new UnsupportedOperationException();
  226.     }

  227.     /**
  228.      * @return String representation
  229.      */
  230.     @Override
  231.     public String toString() {
  232.         return super.toString() + "(\"" + Utility.replace(value, "\n", "\\n") + "\")";
  233.     }
  234. }