blob: b55ed7983ffdaa6feab7a7c333108caada6deacd [file] [log] [blame]
/*
* 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.
*/
/* $Id$ */
package org.apache.fop.area;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Used by the AreaTreeHandler to keep track of ID reference usage
* on a PageViewport level.
*/
public class IDTracker {
private static final Log log = LogFactory.getLog(IDTracker.class);
// HashMap of ID's whose area is located on one or more consecutive
// PageViewports. Each ID has an arraylist of PageViewports that
// form the defined area of this ID
private Map idLocations = new java.util.HashMap();
// idref's whose target PageViewports have yet to be identified
// Each idref has a HashSet of Resolvable objects containing that idref
private Map unresolvedIDRefs = new java.util.HashMap();
private Set unfinishedIDs = new java.util.HashSet();
private Set alreadyResolvedIDs = new java.util.HashSet();
/**
* Tie a PageViewport with an ID found on a child area of the PV. Note that
* an area with a given ID may be on more than one PV, hence an ID may have
* more than one PV associated with it.
*
* @param id the property ID of the area
* @param pv a page viewport that contains the area with this ID
*/
public void associateIDWithPageViewport(String id, PageViewport pv) {
if (log.isDebugEnabled()) {
log.debug("associateIDWithPageViewport(" + id + ", " + pv + ")");
}
List pvList = (List) idLocations.get(id);
if (pvList == null) { // first time ID located
pvList = new ArrayList();
idLocations.put(id, pvList);
pvList.add(pv);
// signal the PageViewport that it is the first PV to contain this id:
pv.setFirstWithID(id);
/*
* See if this ID is in the unresolved idref list, if so resolve
* Resolvable objects tied to it.
*/
if (!unfinishedIDs.contains(id)) {
tryIDResolution(id, pv, pvList);
}
} else {
/* TODO: The check is a quick-fix to avoid a waste
* when adding inline-ids to the page */
if (!pvList.contains(pv)) {
pvList.add(pv);
}
}
}
/**
* This method tie an ID to the areaTreeHandler until this one is ready to
* be processed. This is used in page-number-citation-last processing so we
* know when an id can be resolved.
*
* @param id the id of the object being processed
*/
public void signalPendingID(String id) {
if (log.isDebugEnabled()) {
log.debug("signalPendingID(" + id + ")");
}
unfinishedIDs.add(id);
}
/**
* Signals that all areas for the formatting object with the given ID have
* been generated. This is used to determine when page-number-citation-last
* ref-ids can be resolved.
*
* @param id the id of the formatting object which was just finished
*/
public void signalIDProcessed(String id) {
if (log.isDebugEnabled()) {
log.debug("signalIDProcessed(" + id + ")");
}
alreadyResolvedIDs.add(id);
if (!unfinishedIDs.contains(id)) {
return;
}
unfinishedIDs.remove(id);
List pvList = (List) idLocations.get(id);
Set todo = (Set) unresolvedIDRefs.get(id);
if (todo != null) {
for (Iterator iter = todo.iterator(); iter.hasNext();) {
Resolvable res = (Resolvable) iter.next();
res.resolveIDRef(id, pvList);
}
unresolvedIDRefs.remove(id);
}
}
/**
* Check if an ID has already been resolved
*
* @param id the id to check
* @return true if the ID has been resolved
*/
public boolean alreadyResolvedID(String id) {
return (alreadyResolvedIDs.contains(id));
}
/**
* Tries to resolve all unresolved ID references on the given page.
*
* @param id ID to resolve
* @param pv page viewport whose ID refs to resolve
* @param pvList of PageViewports
*/
private void tryIDResolution(String id, PageViewport pv, List pvList) {
Set todo = (Set) unresolvedIDRefs.get(id);
if (todo != null) {
for (Iterator iter = todo.iterator(); iter.hasNext();) {
Resolvable res = (Resolvable) iter.next();
if (!unfinishedIDs.contains(id)) {
res.resolveIDRef(id, pvList);
} else {
return;
}
}
alreadyResolvedIDs.add(id);
unresolvedIDRefs.remove(id);
}
}
/**
* Tries to resolve all unresolved ID references on the given page.
*
* @param pv page viewport whose ID refs to resolve
*/
public void tryIDResolution(PageViewport pv) {
String[] ids = pv.getIDRefs();
if (ids != null) {
for (int i = 0; i < ids.length; i++) {
List pvList = (List) idLocations.get(ids[i]);
if (pvList != null) {
tryIDResolution(ids[i], pv, pvList);
}
}
}
}
/**
* Get the list of page viewports that have an area with a given id.
*
* @param id the id to lookup
* @return the list of PageViewports
*/
public List getPageViewportsContainingID(String id) {
return (List) idLocations.get(id);
}
/**
* Add an Resolvable object with an unresolved idref
*
* @param idref the idref whose target id has not yet been located
* @param res the Resolvable object needing the idref to be resolved
*/
public void addUnresolvedIDRef(String idref, Resolvable res) {
Set todo = (Set) unresolvedIDRefs.get(idref);
if (todo == null) {
todo = new java.util.HashSet();
unresolvedIDRefs.put(idref, todo);
}
// add Resolvable object to this HashSet
todo.add(res);
}
}