    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$
    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}