/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.waveprotocol.wave.client.scheduler; import org.waveprotocol.wave.client.scheduler.Scheduler.IncrementalTask; import org.waveprotocol.wave.client.scheduler.Scheduler.Priority; import org.waveprotocol.wave.client.scheduler.Scheduler.Schedulable; import org.waveprotocol.wave.client.scheduler.Scheduler.Task; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.IntMap; import java.util.LinkedList; import java.util.Queue; /** * Optimised data structure used by BrowserBackedScheduler to store currently running jobs. * * @author danilatos@google.com (Daniel Danilatos) */ public class JobRegistry { /** * Jobs at each priority. * * Array of priority ordinals to ordered maps (which JSO maps inherently are) * Each map is an ordered map of id to job. */ private final IntMap<Queue<Schedulable>> priorities = CollectionUtils.createIntMap(); /** Controller that collects job counts. */ private final Controller jobCounter; /** Used as part of a pair in a return value, to avoid creating an object */ private Schedulable returnJob; /** Number of jobs in the registry, to provide fast {@link #isEmpty()}. */ private int jobCount; /** * Creates a job registry. * * @param jobCounter counter of jobs */ public JobRegistry(Controller jobCounter) { this.jobCounter = jobCounter; for (Priority p : Priority.values()) { // TODO(zdwang): Use a fast LinkedList that doens't mak this class untestable. priorities.put(p.ordinal(), new LinkedList<Schedulable>()); } } /** * Add a job at the given priority * * @param priority * @param job */ public void add(Priority priority, Schedulable job) { assert job != null : "tried to add null job"; Queue<Schedulable> queue = priorities.get(priority.ordinal()); if (queue.remove(job)) { // Do nothing else queue.add(job); return; } else { queue.add(job); jobCount++; jobCounter.jobAdded(priority, job); } } /** * @param priority * @return the number of jobs at the given priority. */ public int numJobsAtPriority(Priority priority) { return priorities.get(priority.ordinal()).size(); } /** * Remove the first job for the given priority. * The job and its id can be retrieved by the various getRemoved* methods * (This is to avoid creating a pair object as a return value). * * @param priority */ public void removeFirst(Priority priority) { Queue<Schedulable> queue = priorities.get(priority.ordinal()); if (queue.isEmpty()) { returnJob = null; } else { returnJob = queue.poll(); jobCount--; jobCounter.jobRemoved(priority, returnJob); } } /** * Obliterate the job with the given priority and id. * Does NOT store information to be retrieved like {@link #removeFirst(Priority)} does. * * @param priority * @param id */ public void remove(Priority priority, Schedulable job) { if (priorities.get(priority.ordinal()).remove(job)) { jobCount--; jobCounter.jobRemoved(priority, job); } } /** * @return Job removed by {@link #removeFirst(Priority)} */ public Schedulable getRemovedJob() { return returnJob; } /** * @return Job removed by {@link #removeFirst(Priority)}, Cast to IncrementalTask. */ public IncrementalTask getRemovedJobAsProcess() { return (IncrementalTask) returnJob; } /** * @return Job removed by {@link #removeFirst(Priority)}, Cast to Task. */ public Task getRemovedJobAsTask() { return (Task) returnJob; } /** * Used for testing */ boolean debugIsClear() { for (Priority p : Priority.values()) { if (!priorities.get(p.ordinal()).isEmpty()) { assert jobCount > 0 : "Count 0 when: " + toString(); return false; } } assert jobCount == 0 : "Count non-zero when: " + toString(); return true; } /** * Tests if this registry has any jobs in it. * * @return true if this registry has no jobs, false otherwise. */ boolean isEmpty() { return jobCount == 0; } /** {@inheritDoc} */ @Override public String toString() { // NOTE(user): this causes "too much recursion" errors in Firefox? // return priorities.toSource(); // ... so we do explicit building instead :( StringBuilder result = new StringBuilder(); for (Priority p : Priority.values()) { result.append(" { priority: " + p + "; "); result.append(" jobs: " + priorities.get(p.ordinal()) + "; } "); } return result.toString(); } /** * @return short description */ public String debugShortDescription() { // NOTE(user): this causes "too much recursion" errors in Firefox? // return priorities.toSource(); // ... so we do explicit building instead :( StringBuilder result = new StringBuilder(); for (Priority p : Priority.values()) { result.append(" { priority: " + p + "; "); result.append(" jobs count: " + priorities.get(p.ordinal()).size() + "; } "); } return result.toString(); } }