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;
026
027import org.apache.bcel.Const;
028
029/**
030 * This class is derived from the abstract {@link Constant}
031 * and represents a reference to a Utf8 encoded string.
032 *
033 * @version $Id: ConstantUtf8.java 1806200 2017-08-25 16:33:06Z ggregory $
034 * @see     Constant
035 */
036public final class ConstantUtf8 extends Constant {
037
038    private final String bytes;
039
040    // TODO these should perhaps be AtomicInt?
041    private static volatile int considered = 0;
042    private static volatile int hits = 0;
043    private static volatile int skipped = 0;
044    private static volatile int created = 0;
045
046    // Set the size to 0 or below to skip caching entirely
047    private static final int MAX_CACHED_SIZE =
048            Integer.getInteger("bcel.maxcached.size", 200).intValue();// CHECKSTYLE IGNORE MagicNumber
049    private static final boolean BCEL_STATISTICS = Boolean.getBoolean("bcel.statistics");
050
051
052    private static class CACHE_HOLDER {
053
054        private static final int MAX_CACHE_ENTRIES = 20000;
055        private static final int INITIAL_CACHE_CAPACITY = (int)(MAX_CACHE_ENTRIES/0.75);
056
057        private static final HashMap<String, ConstantUtf8> CACHE =
058                new LinkedHashMap<String, ConstantUtf8>(INITIAL_CACHE_CAPACITY, 0.75f, true) {
059            private static final long serialVersionUID = -8506975356158971766L;
060
061            @Override
062            protected boolean removeEldestEntry(final Map.Entry<String, ConstantUtf8> eldest) {
063                 return size() > MAX_CACHE_ENTRIES;
064            }
065        };
066
067    }
068
069    // for accesss by test code
070    static void printStats() {
071        System.err.println("Cache hit " + hits + "/" + considered +", " + skipped + " skipped");
072        System.err.println("Total of " + created + " ConstantUtf8 objects created");
073    }
074
075    // for accesss by test code
076    static void clearStats() {
077        hits = considered = skipped = created = 0;
078    }
079
080    static {
081        if (BCEL_STATISTICS) {
082            Runtime.getRuntime().addShutdownHook(new Thread() {
083                @Override
084                public void run() {
085                    printStats();
086                }
087            });
088        }
089    }
090
091    /**
092     * @since 6.0
093     */
094    public static ConstantUtf8 getCachedInstance(final String s) {
095        if (s.length() > MAX_CACHED_SIZE) {
096            skipped++;
097            return  new ConstantUtf8(s);
098        }
099        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     */
115    public static ConstantUtf8 getInstance(final String s) {
116        return new ConstantUtf8(s);
117    }
118
119    /**
120     * @since 6.0
121     */
122    public static ConstantUtf8 getInstance (final DataInput input)  throws IOException {
123        return getInstance(input.readUTF());
124    }
125
126    /**
127     * Initialize from another object.
128     */
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     */
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     */
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
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
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     */
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
199    public final void setBytes( final String bytes ) {
200        throw new UnsupportedOperationException();
201    }
202
203
204    /**
205     * @return String representation
206     */
207    @Override
208    public final String toString() {
209        return super.toString() + "(\"" + Utility.replace(bytes, "\n", "\\n") + "\")";
210    }
211}