/** * 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.apache.hadoop.util; import java.util.ArrayList; /** Utility to assist with generation of progress reports. Applications build * a hierarchy of {@link Progress} instances, each modelling a phase of * execution. The root is constructed with {@link #Progress()}. Nodes for * sub-phases are created by calling {@link #addPhase()}. */ public class Progress { private String status = ""; private float progress; private int currentPhase; private ArrayList<Progress> phases = new ArrayList<Progress>(); private Progress parent; private float progressPerPhase; /** Creates a new root node. */ public Progress() {} /** Adds a named node to the tree. */ public Progress addPhase(String status) { Progress phase = addPhase(); phase.setStatus(status); return phase; } /** Adds a node to the tree. */ public synchronized Progress addPhase() { Progress phase = new Progress(); phases.add(phase); phase.parent = this; progressPerPhase = 1.0f / (float)phases.size(); return phase; } /** Called during execution to move to the next phase at this level in the * tree. */ public synchronized void startNextPhase() { currentPhase++; } /** Returns the current sub-node executing. */ public synchronized Progress phase() { return phases.get(currentPhase); } /** Completes this node, moving the parent node to its next child. */ public void complete() { // we have to traverse up to our parent, so be careful about locking. Progress myParent; synchronized(this) { progress = 1.0f; myParent = parent; } if (myParent != null) { // this will synchronize on the parent, so we make sure we release // our lock before getting the parent's, since we're traversing // against the normal traversal direction used by get() or toString(). // We don't need transactional semantics, so we're OK doing this. myParent.startNextPhase(); } } /** Called during execution on a leaf node to set its progress. */ public synchronized void set(float progress) { this.progress = progress; } /** Returns the overall progress of the root. */ // this method probably does not need to be synchronized as getINternal() is synchronized // and the node's parent never changes. Still, it doesn't hurt. public synchronized float get() { Progress node = this; while (node.parent != null) { // find the root node = parent; } return node.getInternal(); } /** Computes progress in this node. */ private synchronized float getInternal() { int phaseCount = phases.size(); if (phaseCount != 0) { float subProgress = currentPhase < phaseCount ? phase().getInternal() : 0.0f; return progressPerPhase*(currentPhase + subProgress); } else { return progress; } } /** * Returns progress in this node. get() would give overall progress of the * root node(not just given current node). */ public synchronized float getProgress() { return getInternal(); } public synchronized void setStatus(String status) { this.status = status; } public String toString() { StringBuffer result = new StringBuffer(); toString(result); return result.toString(); } private synchronized void toString(StringBuffer buffer) { buffer.append(status); if (phases.size() != 0 && currentPhase < phases.size()) { buffer.append(" > "); phase().toString(buffer); } } }