// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.google.collide.client.codeunderstanding;
import com.google.collide.client.communication.FrontendApi;
import com.google.collide.client.util.DeferredCommandExecutor;
import com.google.collide.client.util.logging.Log;
import com.google.collide.clientlibs.invalidation.InvalidationRegistrar;
import com.google.collide.dto.CodeGraphRequest;
import com.google.collide.dto.CodeGraphResponse;
import com.google.collide.dto.CubePing;
import com.google.collide.dto.RoutingTypes;
import com.google.collide.dto.client.DtoUtils;
import com.google.collide.json.client.JsoArray;
import com.google.collide.json.shared.JsonArray;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
/**
* A response distributor for {@link CubeState}.
*
* Presents a high level API for end clients.
*
*/
public class CubeClient
implements CubeState.CubeResponseDistributor, InvalidationRegistrar.Listener {
/**
* A command and listener that requests state refresh after text changes.
*
* <p>Idle timeout is 10 x 100ms = 1s.
*/
private class RefreshWatchdog extends DeferredCommandExecutor {
protected RefreshWatchdog() {
super(100);
}
@Override
protected boolean execute() {
state.refresh();
return false;
}
}
/**
* Request sender / response receiver & logic.
*/
private final CubeState state;
/**
* List of subscribers.
*/
private final JsonArray<CubeUpdateListener> listeners = JsoArray.create();
/**
* Text changes detector / idle period actor.
*/
private final RefreshWatchdog refreshWatchdog = new RefreshWatchdog();
/**
* Constructs object, and initialises it's state with given parameters.
*
* @param api Cube API
*/
CubeClient(
FrontendApi.RequestResponseApi<CodeGraphRequest, CodeGraphResponse> api) {
Preconditions.checkNotNull(api);
state = new CubeState(api, this);
}
public void addListener(CubeUpdateListener listener) {
listeners.add(listener);
}
/**
* Prevents further response distribution / processing.
*/
void cleanup() {
refreshWatchdog.cancel();
listeners.clear();
state.dismiss();
}
@Override
public void notifyListeners(CubeDataUpdates updates) {
if (!CubeDataUpdates.hasUpdates(updates)) {
return;
}
int l = listeners.size();
for (int i = 0; i < l; i++) {
listeners.get(i).onCubeResponse(state.getData(), updates);
}
}
@Override
public void onInvalidated(String objectName, long version, @Nullable String payload,
AsyncProcessingHandle asyncProcessingHandle) {
if (payload == null) {
// darn - we lost the payload; this just means that we should refresh
// state just in case its changed (which may be inefficient if our state
// actually isn't out of date)
} else {
CubePing message;
try {
message = DtoUtils.parseAsDto(payload, RoutingTypes.CUBEPING);
} catch (Exception e) {
Log.warn(getClass(), "Failed to deserialize Tango payload", e);
return;
}
// TODO: We should use message.getFullGraphFreshness()
}
// if we haven't returned out yet, that means we need to refresh state
state.refresh();
}
public CubeData getData() {
return state.getData();
}
public void removeListener(CubeUpdateListener listener) {
listeners.remove(listener);
}
void setPath(String filePath) {
Preconditions.checkNotNull(filePath);
state.setFilePath(filePath);
refreshWatchdog.cancel();
state.refresh();
}
void refresh() {
refreshWatchdog.schedule(10);
}
}