/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class to make serializable objects smaller, by reusing same immutable objects, also will help reduce memory in many client (=server) environment.
*
* @author jblok
*/
public final class Internalize
{
private static final Internalize instance = new Internalize();
static volatile int maxsize = 50000;
private final ConcurrentHashMap<Object, ObjectHolder> internedMap = new ConcurrentHashMap<Object, ObjectHolder>(5000);
private final AtomicBoolean makingroom = new AtomicBoolean(false);
private Internalize()
{
}
public static void setMaxSize(int size)
{
maxsize = size;
if (size <= 0)
{
instance.clear();
}
}
@SuppressWarnings("unchecked")
public static <T> T intern(T obj)
{
if (obj == null) return obj;
if (obj instanceof Boolean)
{
return (T)(((Boolean)obj).booleanValue() ? Boolean.TRUE : Boolean.FALSE);
}
if (maxsize <= 0) return obj;
if (obj instanceof String || obj instanceof Number || obj.getClass() == java.awt.Color.class)//ONLY handle immutable objects
{
return instance.add(obj);
}
if (obj instanceof Object[])
{
Object[] array = (Object[])obj;
for (int i = 0; i < array.length; i++)
{
array[i] = intern(array[i]);
}
}
return obj;
}
/**
*
*/
private void clear()
{
internedMap.clear();
}
@SuppressWarnings("unchecked")
private <T> T add(T obj)
{
ObjectHolder retValue = internedMap.get(obj);
if (retValue == null)
{
if (internedMap.size() >= maxsize)
{
// clear less used.
makeRoom();
}
internedMap.put(obj, new ObjectHolder(obj));
return obj;
}
// if making room then don't increment.
if (!makingroom.get()) retValue.increment();
return (T)retValue.object;
}
private void makeRoom()
{
if (makingroom.compareAndSet(false, true))
{
try
{
TreeSet<ObjectHolder> set = new TreeSet<ObjectHolder>(internedMap.values());
int counter = 0;
// clean 1/5 of max objects.
int toRemove = maxsize / 5;
for (ObjectHolder objectHolder : set)
{
internedMap.remove(objectHolder.object);
if (++counter == toRemove) break;
}
}
finally
{
makingroom.set(false);
}
}
}
private final static class ObjectHolder implements Comparable<ObjectHolder>
{
private final Object object;
private volatile int counter = 0;
/**
* @param obj
*/
public ObjectHolder(Object obj)
{
object = obj;
}
public final void increment()
{
counter++;
if (counter < 0) counter = 1000;
}
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public final int compareTo(ObjectHolder o)
{
int compare = counter - o.counter;
// if compare is 0 then test for hash, for the same hash 1 of them will fall out.
// but that will be picked up the next time, the chance is not that great.
return compare == 0 ? object.hashCode() - o.object.hashCode() : compare;
}
}
}