/*
* Copyright 2013 Hewlett-Packard Development Company, L.P
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hp.alm.ali.idea.services;
import com.hp.alm.ali.idea.rest.RestService;
import com.hp.alm.ali.idea.rest.ServerType;
import com.hp.alm.ali.idea.rest.ServerTypeListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Transform;
import com.intellij.util.ui.UIUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public abstract class AbstractCachingService<S, T, C extends AbstractCachingService.Callback<T>> implements ServerTypeListener {
private Map<S, Pair<List<C>, Future<T>>> active;
private Map<S, RuntimeException> failures;
final protected Map<S, T> cache;
final protected Project project;
public AbstractCachingService(Project project) {
this.project = project;
active = new HashMap<S, Pair<List<C>, Future<T>>>();
cache = new HashMap<S, T>();
failures = new HashMap<S, RuntimeException>();
project.getComponent(RestService.class).addServerTypeListener(this);
}
protected abstract T doGetValue(S key);
@Override
public void connectedTo(ServerType serverType) {
synchronized (cache) {
active.clear();
cache.clear();
failures.clear();
}
}
protected T getCachedValue(S key) {
synchronized (cache) {
T value = cache.get(key);
if(value != null) {
return value;
}
return null;
}
}
protected T getValue(S key) {
Future<T> future;
synchronized (cache) {
T value = cache.get(key);
if(value != null) {
return value;
}
RuntimeException ex = failures.get(key);
if(ex != null) {
throw ex;
}
future = getFuture(key, null);
}
try {
return future.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
protected void getValueAsync(S key, C callback) {
synchronized (cache) {
T value = cache.get(key);
if(value != null) {
invoke(new LinkedList<C>(Collections.singletonList(callback)), value);
return;
}
RuntimeException ex = failures.get(key);
if(ex != null) {
failureInvoke(new LinkedList<C>(Collections.singletonList(callback)));
return;
}
getFuture(key, callback);
}
}
private Future<T> getFuture(final S key, C callback) {
Pair<List<C>, Future<T>> listAndFuture = active.get(key);
if(listAndFuture == null) {
final LinkedList<C> listeners = new LinkedList<C>();
Future<T> future = ApplicationManager.getApplication().executeOnPooledThread(new Callable<T>() {
public T call() {
final T value;
try {
value = doGetValue(key);
} catch(RuntimeException e) {
synchronized (cache) {
if(active.containsKey(key) && listeners.equals(active.get(key).getFirst())) {
failures.put(key, e);
active.remove(key);
}
failureInvoke(listeners);
throw e;
}
}
synchronized (cache) {
if(active.containsKey(key) && listeners.equals(active.get(key).getFirst())) {
cache.put(key, value);
active.remove(key);
}
invoke(listeners, value);
}
return value;
}
});
listAndFuture = new Pair<List<C>, Future<T>>(listeners, future);
active.put(key, listAndFuture);
}
if(callback != null) {
listAndFuture.getFirst().add(callback);
}
return listAndFuture.getSecond();
}
private void invoke(final List<C> listeners, final T value) {
for (Iterator<C> it = listeners.iterator(); it.hasNext(); ) {
C listener = it.next();
if(!(listener instanceof Dispatch)) {
listener.loaded(value);
it.remove();
}
}
if(!listeners.isEmpty()) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
for (C listener : listeners) {
listener.loaded(value);
}
}
});
}
}
private void failureInvoke(final List<C> listeners) {
for (Iterator<C> it = listeners.iterator(); it.hasNext(); ) {
C listener = it.next();
if(!(listener instanceof FailureCallback)) {
it.remove();
} else if(!(listener instanceof Dispatch)) {
((FailureCallback)listener).failed();
it.remove();
}
}
if(!listeners.isEmpty()) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
for (C listener : listeners) {
((FailureCallback)listener).failed();
}
}
});
}
}
public static <E, X> Callback<E> translate(final Callback<X> callback, final Transform<E, X> transform) {
if(callback instanceof Dispatch) {
return new DispatchCallback<E>() {
@Override
public void loaded(E data) {
callback.loaded(transform.transform(data));
}
};
} else {
return new Callback<E>() {
@Override
public void loaded(E data) {
callback.loaded(transform.transform(data));
}
};
}
}
protected Exception getCachedFailure(S key) {
synchronized (cache) {
return failures.get(key);
}
}
public static interface Dispatch {
}
public static interface FailureCallback {
void failed();
}
public static interface Callback<E> {
void loaded(E data);
}
public static interface DispatchCallback<E> extends Callback<E>, Dispatch {
}
}