com.github.exerrk.engine.util.ConcurrentMapping Maven / Gradle / Ivy
The newest version!
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2016 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package com.github.exerrk.engine.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.github.exerrk.engine.JRRuntimeException;
/**
* @author Lucian Chirita ([email protected])
*/
public class ConcurrentMapping
{
public static interface Mapper
{
V compute(K key);
}
private static class Entry
{
private static enum Status
{
PENDING,
AVAILABLE,
ERROR;
}
private static class Result
{
private Status status;
private V value;
private Result(Status status, V value)
{
this.status = status;
this.value = value;
}
public boolean isAvailable()
{
return status == Status.AVAILABLE;
}
public boolean isError()
{
return status == Status.ERROR;
}
}
private static final Result RESULT_PENDING = new Result<>(Status.PENDING, null);
private static final Result RESULT_ERROR = new Result<>(Status.ERROR, null);
@SuppressWarnings("unchecked")
private static Result pendingResult()
{
return (Result) RESULT_PENDING;
}
@SuppressWarnings("unchecked")
private static Result errorResult()
{
return (Result) RESULT_ERROR;
}
private final Lock lock;
private final Condition statusCondition;
private volatile Result result;
private Entry()
{
this.lock = new ReentrantLock();
this.statusCondition = this.lock.newCondition();
this.result = pendingResult();
}
private void lock()
{
try
{
lock.lockInterruptibly();
}
catch (InterruptedException e)
{
throw new JRRuntimeException(e);
}
}
private void unlock()
{
lock.unlock();
}
private void setValue(V value)
{
this.result = new Result(Status.AVAILABLE, value);
this.statusCondition.signalAll();
}
private void setError()
{
this.result = errorResult();
this.statusCondition.signalAll();
}
private void waitForStatus()
{
try
{
while (result.status == Entry.Status.PENDING)
{
statusCondition.await();
}
}
catch (InterruptedException e)
{
throw new JRRuntimeException(e);
}
}
}
private final Mapper mapper;
private ConcurrentHashMap> entries;
public ConcurrentMapping(Mapper mapper)
{
this.mapper = mapper;
this.entries = new ConcurrentHashMap>();
}
public V get(K key)
{
Entry existingEntry = entries.get(key);
Entry newEntry = null;
V result;
if (existingEntry == null)
{
newEntry = new Entry();
existingEntry = entries.putIfAbsent(key, newEntry);
if (existingEntry == null)
{
result = getNew(newEntry, key);
}
else
{
result = getExisting(existingEntry, key);
}
}
else
{
result = getExisting(existingEntry, key);
}
return result;
}
private V getExisting(Entry entry, K key)
{
V result;
Entry.Result entryResult = entry.result;
if (entryResult.isAvailable())
{
result = entryResult.value;
}
else
{
entry.lock();
try
{
entry.waitForStatus();
entryResult = entry.result;
if (entryResult.isAvailable())
{
result = entryResult.value;
}
else if (entryResult.isError())
{
//compute again
entry.result = Entry.pendingResult();
result = getNew(entry, key);
}
else
{
//should not happen
throw new JRRuntimeException("Unexpected entry status " + entry.result.status);
}
}
finally
{
entry.unlock();
}
}
return result;
}
private V getNew(Entry entry, K key)
{
entry.lock();
try
{
V value = null;
boolean success = false;
try
{
value = mapper.compute(key);
success = true;
return value;
}
finally
{
if (success)
{
entry.setValue(value);
}
else
{
entry.setError();
}
}
}
finally
{
entry.unlock();
}
}
public void clear()
{
entries.clear();
}
public Iterator currentValues()
{
return new ValuesIterator();
}
private class ValuesIterator implements Iterator
{
private Iterator> entriesIterator;
private Entry.Result currentResult;
private ValuesIterator()
{
entriesIterator = entries.values().iterator();
}
private void findAvailable()
{
currentResult = null;
while (currentResult == null && entriesIterator.hasNext())
{
Entry entry = entriesIterator.next();
Entry.Result result = entry.result;
if (result.isAvailable())
{
currentResult = result;
}
}
}
@Override
public boolean hasNext()
{
return currentResult != null;
}
@Override
public V next()
{
if (currentResult == null)
{
throw new NoSuchElementException();
}
V value = currentResult.value;
findAvailable();
return value;
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
}