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  */
18 package org.apache.bcel.classfile;
19 
20 import java.io.DataInput;
21 import java.io.DataOutputStream;
22 import java.io.IOException;
23 import java.util.HashMap;
24 import java.util.LinkedHashMap;
25 import java.util.Map;
26 
27 import org.apache.bcel.Const;
28 
29 /**
30  * This class is derived from the abstract {@link Constant}
31  * and represents a reference to a Utf8 encoded string.
32  *
33  * @version $Id$
34  * @see     Constant
35  */
36 public final class ConstantUtf8 extends Constant {
37 
38     private final String bytes;
39 
40     // TODO these should perhaps be AtomicInt?
41     private static volatile int considered = 0;
42     private static volatile int hits = 0;
43     private static volatile int skipped = 0;
44     private static volatile int created = 0;
45 
46     // Set the size to 0 or below to skip caching entirely
47     private static final int MAX_CACHED_SIZE =
48             Integer.getInteger("bcel.maxcached.size", 200).intValue();// CHECKSTYLE IGNORE MagicNumber
49     private static final boolean BCEL_STATISTICS = Boolean.getBoolean("bcel.statistics");
50 
51 
52     private static class CACHE_HOLDER {
53 
54         private static final int MAX_CACHE_ENTRIES = 20000;
55         private static final int INITIAL_CACHE_CAPACITY = (int)(MAX_CACHE_ENTRIES/0.75);
56 
57         private static final HashMap<String, ConstantUtf8> CACHE =
58                 new LinkedHashMap<String, ConstantUtf8>(INITIAL_CACHE_CAPACITY, 0.75f, true) {
59             private static final long serialVersionUID = -8506975356158971766L;
60 
61             @Override
62             protected boolean removeEldestEntry(final Map.Entry<String, ConstantUtf8> eldest) {
63                  return size() > MAX_CACHE_ENTRIES;
64             }
65         };
66 
67     }
68 
69     // for accesss by test code
printStats()70     static void printStats() {
71         System.err.println("Cache hit " + hits + "/" + considered +", " + skipped + " skipped");
72         System.err.println("Total of " + created + " ConstantUtf8 objects created");
73     }
74 
75     // for accesss by test code
clearStats()76     static void clearStats() {
77         hits = considered = skipped = created = 0;
78     }
79 
80     static {
81         if (BCEL_STATISTICS) {
82             Runtime.getRuntime().addShutdownHook(new Thread() {
83                 @Override
84                 public void run() {
85                     printStats();
86                 }
87             });
88         }
89     }
90 
91     /**
92      * @since 6.0
93      */
getCachedInstance(final String s)94     public static ConstantUtf8 getCachedInstance(final String s) {
95         if (s.length() > MAX_CACHED_SIZE) {
96             skipped++;
97             return  new ConstantUtf8(s);
98         }
99         considered++;
100         synchronized (ConstantUtf8.class) { // might be better with a specific lock object
101             ConstantUtf8 result = CACHE_HOLDER.CACHE.get(s);
102             if (result != null) {
103                     hits++;
104                     return result;
105                 }
106             result = new ConstantUtf8(s);
107             CACHE_HOLDER.CACHE.put(s, result);
108             return result;
109         }
110     }
111 
112     /**
113      * @since 6.0
114      */
getInstance(final String s)115     public static ConstantUtf8 getInstance(final String s) {
116         return new ConstantUtf8(s);
117     }
118 
119     /**
120      * @since 6.0
121      */
getInstance(final DataInput input)122     public static ConstantUtf8 getInstance (final DataInput input)  throws IOException {
123         return getInstance(input.readUTF());
124     }
125 
126     /**
127      * Initialize from another object.
128      */
ConstantUtf8(final ConstantUtf8 c)129     public ConstantUtf8(final ConstantUtf8 c) {
130         this(c.getBytes());
131     }
132 
133 
134     /**
135      * Initialize instance from file data.
136      *
137      * @param file Input stream
138      * @throws IOException
139      */
ConstantUtf8(final DataInput file)140     ConstantUtf8(final DataInput file) throws IOException {
141         super(Const.CONSTANT_Utf8);
142         bytes = file.readUTF();
143         created++;
144     }
145 
146 
147     /**
148      * @param bytes Data
149      */
ConstantUtf8(final String bytes)150     public ConstantUtf8(final String bytes) {
151         super(Const.CONSTANT_Utf8);
152         if (bytes == null) {
153             throw new IllegalArgumentException("bytes must not be null!");
154         }
155         this.bytes = bytes;
156         created++;
157     }
158 
159 
160     /**
161      * Called by objects that are traversing the nodes of the tree implicitely
162      * defined by the contents of a Java class. I.e., the hierarchy of methods,
163      * fields, attributes, etc. spawns a tree of objects.
164      *
165      * @param v Visitor object
166      */
167     @Override
accept( final Visitor v )168     public void accept( final Visitor v ) {
169         v.visitConstantUtf8(this);
170     }
171 
172 
173     /**
174      * Dump String in Utf8 format to file stream.
175      *
176      * @param file Output file stream
177      * @throws IOException
178      */
179     @Override
dump( final DataOutputStream file )180     public final void dump( final DataOutputStream file ) throws IOException {
181         file.writeByte(super.getTag());
182         file.writeUTF(bytes);
183     }
184 
185 
186     /**
187      * @return Data converted to string.
188      */
getBytes()189     public final String getBytes() {
190         return bytes;
191     }
192 
193 
194     /**
195      * @param bytes the raw bytes of this Utf-8
196      * @deprecated (since 6.0)
197      */
198     @java.lang.Deprecated
setBytes( final String bytes )199     public final void setBytes( final String bytes ) {
200         throw new UnsupportedOperationException();
201     }
202 
203 
204     /**
205      * @return String representation
206      */
207     @Override
toString()208     public final String toString() {
209         return super.toString() + "(\"" + Utility.replace(bytes, "\n", "\\n") + "\")";
210     }
211 }
212