All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.config.DocumentOrderingWrapper Maven / Gradle / Ivy

Go to download

This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.

There is a newer version: 2.2.20
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.faces.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.text.MessageFormat;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.Timer;

/**
 * This class is used by the config system to order faces-config
 * documents found on the classpath or configured explicitly via the
 * javax.faces.CONFIG_FILES context init parameter.
 */
public class DocumentOrderingWrapper {

    /**
     * Logger for this class.
     */
    private static final Logger LOGGER = FacesLogger.CONFIG.getLogger();

    /**
     * {@link Comparator} implementation to aid in sorting faces-config
     * documents.
     */
    private static Comparator COMPARATOR =
          new DocumentOrderingComparator();

    /**
     * This is the limit on the number of attempts made to sort the documents.
     * Any attempt to exceed this limit will result in an Exception being thrown.
     */
    private static final int MAX_SORT_PASSED = 1000;

    /**
     * Constant for the ordering element.
     */
    private static final String ORDERING = "ordering";

    /**
     * Constant for the before element.
     */
    private static final String BEFORE = "before";

    /**
     * Constant for the after element.
     */
    private static final String AFTER = "after";

    /**
     * Constant for the name element.
     */
    private static final String NAME = "name";

    /**
     * Constant for the others element.
     */
    private static final String OTHERS = "others";

    /**
     * Others keyword for sorting.
     */
    private static final String OTHERS_KEY = DocumentOrderingWrapper.class.getName() + ".OTHERS_KEY";

    /**
     * Return code indicating that element n is to be swapped
     * with n + 1
     */
    private static final int SWAP = -1;

    /**
     * Return code indicating that no swap needs to occur for the elements
     * being compared.
     */
    private static final int DO_NOT_SWAP = 0;

    /**
     * The wrapped Document.
     */
    private DocumentInfo documentInfo;

    /**
     * The wrapped Document's ID.
     */
    private String id;

    /**
     * The wrapped Document's before IDs.
     */
    private String[] beforeIds;

    /**
     * The wrapped Document's before IDs.
     */
    private String[] afterIds;

    // -------------------------------------------------------- Constructors


    /**
     * Constructs a new DocumentOrderingWrapper for the specified
     * Document.
     */
    public DocumentOrderingWrapper(DocumentInfo document) {

        this.documentInfo = document;
        init();

    }


    // ------------------------------------------------------ Public Methods


    /**
     * @return the wrapped Document
     */
    public DocumentInfo getDocument() {

        return documentInfo;

    }


    /**
     * @return this Document's ID, if any
     */
    public String getDocumentId() {

        return id;

    }


    /**
     * @return this Document's before IDs, if any
     */
    public String[] getBeforeIds() {

        return beforeIds;

    }


    /**
     * @return this Document's after IDs, if any
     */
    public String[] getAfterIds() {

        return afterIds;

    }


    /**
     * @return true if any before IDs are present, otherwise
     *  false
     */
    public boolean isBeforeOrdered() {
        return beforeIds.length != 0;
    }


    /**
     * @return true if any after IDs are present, otherwise,
     *  false
     */
    public boolean isAfterOrdered() {
        return afterIds.length != 0;
    }


    /**
     * @return true if this document has any before or after IDs,
     *  otherwise false
     */
    public boolean isOrdered() {
        return (isBeforeOrdered() || isAfterOrdered());
    }


    /**
     * @return true if this document is before the specified
     *  id, otherwise false
     */
    public boolean isBefore(String id) {

        return (search(beforeIds, id));

    }


    /**
     * @return true if this document is after the specified
     *  id, otherwise false
     */
    public boolean isAfter(String id) {

        return (search(afterIds, id));
    }


    /**
     * @return true if this document is after others, otherwise
     *  false
     */
    public boolean isAfterOthers() {

        return (search(afterIds, OTHERS_KEY));

    }


    /**
     * @return true if this document is before others, otherwise
     *  false
     */
    public boolean isBeforeOthers() {

         return (search(beforeIds, OTHERS_KEY));

    }


    @Override
    public String toString() {
        return "Document{" +
               "id='" + id + '\'' +
               ", beforeIds="
               + (beforeIds == null ? null : Arrays.asList(beforeIds))
               +
               ", afterIds="
               + (afterIds == null ? null : Arrays.asList(afterIds))
               +
               '}';
    }


    /**
     * Sort the provided array of Documents per the order specified
     * in the List represented by absoluteOrder.
     * @param documents Documents to sort
     * @param absoluteOrder the absolute order as specified in the /WEB-INF/faces-config.xml
     * @return an array of DocumentOrderingWrappers that may be smaller than the
     *  input array of wrappers.
     */
    public static DocumentOrderingWrapper[] sort(DocumentOrderingWrapper[] documents,
                                                 List absoluteOrder) {

        List sourceList = new CopyOnWriteArrayList();
        sourceList.addAll(Arrays.asList(documents));

        List targetList = new ArrayList();
        for (String name : absoluteOrder) {
            if ("others".equals(name)) {
                continue;
            }
            boolean found = false;
            for (DocumentOrderingWrapper wrapper : sourceList) {
                if (!found && name.equals(wrapper.getDocumentId())) {
                    found = true;
                    targetList.add(wrapper);
                    sourceList.remove(wrapper);
                } else if (found && name.equals(wrapper.getDocumentId())){
                    // we've already processed a document with this name
                    if (LOGGER.isLoggable(Level.WARNING)) {
                        LOGGER.log(Level.WARNING,
                                   "jsf.configuration.absolute.order.duplicate.document",
                                   new Object[] { name });
                    }
                    // only log this once
                    break;
                }
            }
            if (!found && LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.log(Level.WARNING,
                    "jsf.configuration.absolute.order.missing.document",
                    new Object[] { name });
            }
        }

        int othersIndex = absoluteOrder.indexOf("others");
        if (othersIndex != -1) {
            // any wrappers left in sourceList are considered others.
            // start pushing them into targetList at the index
            for (DocumentOrderingWrapper wrapper : sourceList) {
                targetList.add(othersIndex, wrapper);
            }
        }

        return targetList.toArray(new DocumentOrderingWrapper[targetList.size()]);
        
    }


    /**
     * Sort the provided array of Documents per the requirements
     * of the 2.0 specification.  Note, that this method only provides partial
     * ordering and not absolute ordering.
     */
    public static void sort(DocumentOrderingWrapper[] documents) {

        Timer t = Timer.getInstance();
        if (t != null) {
            t.startTiming();
        }
        try {
            enhanceOrderingData(documents);
        } catch (CircularDependencyException re) {
            String msg = "Circular dependencies detected!\nDocument Info\n==================\n";
            for (DocumentOrderingWrapper w : documents) {
                msg += ("  " + w.toString() + '\n');
            }
            throw new ConfigurationException(msg);
        }

        // Sort the documents such that specified ordering will be considered.
        //
        // It turns out that some of the specified ordering, if it was not discovered by the sort routine 
        // until later in its processing, was not being considered correctly in the ordering algorithm.
        //
        // This preSort method puts all of the documents with specified ordering as early on in the
        // list of documents as possible for Mojarra to consider it quickly, and be 
        // able to use its ordering algorithm to the best of its ability.
        preSort(documents);

        // original inner sort algorithm
        int numberOfPasses = innerSort(documents);
        
        // final sort
        for (int i = 0; i < documents.length; i++) {
        	LinkedList ids = getIds(documents);
        	if (done(documents, ids)) {
        		break;
        	}
        }

        if (t != null) {
            t.stopTiming();
            t.logResult("\"faces-config\" document sorting complete in " + numberOfPasses + '.');
        }

    }
    
    // Check to see if the sort is complete, and if not, finish it, if possible.
    public static boolean done(DocumentOrderingWrapper[] documents, LinkedList ids) {
    	
    	for (int i = 0; i < documents.length; i++) {
    		int ii = 0;
    		for(String documentId : ids) {
    			if (documents[i].getDocumentId().equals(documentId)) {
    				break;
    			}
    			if (documents[i].isBefore(documentId)) {
    				if (LOGGER.isLoggable(Level.FINE)) {
    	                LOGGER.log(Level.FINE, "done: " + documentId + " should be after " + documents[i].getDocumentId() + 
    	                		" given that it should be before: " + Arrays.asList(documents[i].getBeforeIds()));
    	            }
    				
    				// we have a document that is out of order, and his index is ii, he belongs at index i, and all the documents in between need to be shifted left.
    				DocumentOrderingWrapper temp = null;
    				for (int j = 0; j < documents.length; j++) {
    					// This is one that is out of order and needs to be moved.
    					if (j==ii) {
    						temp = documents[j];
    					}
    					// this is one in between that needs to be shifted left.
    					if (temp != null && j != i) {
    						documents[j] = documents[j+1];
    					}
    					// this is where the one that is out of order needs to be moved to.
    					if (j==i) {
    						documents[j] = temp;
    						return false;
    					}
    				}
    			}
    			ii = ii + 1;
    		}
    	}
    	
    	return true;
    }
    
    public static LinkedList getIds(DocumentOrderingWrapper[] documents) {
    	LinkedList ids = new LinkedList();
    	for (int i = 0; i < documents.length; i++) {
    		ids.add(documents[i].getDocumentId());
    	}
    	return ids;
    }
    
    public static int innerSort(DocumentOrderingWrapper[] documents) {
    	
    	int numberOfPasses = 0;
    	boolean doMore = true;

        while (doMore) {
            numberOfPasses++;
            if (numberOfPasses == MAX_SORT_PASSED) {
                if (LOGGER.isLoggable(Level.SEVERE)) {
                    String msg = "Exceeded maximum number of attempts to sort the application's faces-config documents.\nDocument Info\n==================";
                    for (DocumentOrderingWrapper w : documents) {
                        msg += ("  " + w.toString() + '\n');
                    }
                    LOGGER.severe(msg);                    
                }
                throw new ConfigurationException("Exceeded maximum number of attempts to sort the faces-config documents.");
            }
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE,
                           "Starting sort pass number {0}...",
                           numberOfPasses);
            }
            doMore = false;
            for (int i = 0; i < documents.length - 1; i++) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE,
                               "Comparing {0}, {1}",
                               new Object[] { documents[i].id, documents[i + 1].id });
                }
                if (COMPARATOR.compare(documents[i], documents[i + 1]) != DO_NOT_SWAP) {
                    if (LOGGER.isLoggable(Level.FINE)) {
                        LOGGER.log(Level.FINE,
                                   "Swapping {0} with {1}",
                                   new Object[] { documents[i].id, documents[i + 1].id });
                    }
                    DocumentOrderingWrapper temp = documents[i];
                    documents[i] = documents[i + 1];
                    documents[i + 1] = temp;
                    doMore = true;
                }
            }

            // compare first and last elements
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE,
                           "Comparing {0}, {1}",
                           new Object[]{documents[0].id, documents[documents.length - 1].id});
            }
            if (COMPARATOR.compare(documents[0], documents[documents.length - 1]) != DO_NOT_SWAP) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE,
                               "Swapping {0} with {1}",
                               new Object[]{documents[0].id, documents[documents.length - 1].id});
                }
                DocumentOrderingWrapper temp = documents[0];
                documents[0] = documents[documents.length - 1];
                documents[documents.length - 1] = temp;
                doMore = true;
            }
        }
        
        return numberOfPasses;
    }

    // ----------------------------------------------------- Private Methods


    /**
     * Update before/after knowledge of all nodes in the array.
     * Consider the case of A after B, B after C, C after D, and D
     * with no ordering characteristics.
     * When the code below executes, the before/after of specific nodes
     * will be updated so that in the case of the example above we'd
     * have:
     *
     *  A -> after B
     *  B -> before A, after C
     *  C -> before B, A after D
     *  D -> before C, B, A
     *
     * So when an attempt is made to sort [A, B, C, D] the end
     * result is [D, C, B, A]
     * No extra enhancement of after ID information is necessary outside
     * of the single node information due to the way the algorithm processes
     * the array.
     *
     * This method also performs cyclic detection.  If, after updating the
     * before/after information, the before/after information contains a
     * reference to the document ID we're currently processing, throw an
     * exception.
     */
    private static void enhanceOrderingData(DocumentOrderingWrapper[] wrappers)
    throws CircularDependencyException {

        for (int i = 0; i < wrappers.length; i++) {
            DocumentOrderingWrapper w = wrappers[i];

            // process before IDs other than 'others'
            for (String id : w.getBeforeIds()) {
                if (OTHERS_KEY.equals(id)) {
                    continue;
                }
                for (int ii = 0; ii < wrappers.length; ii++) {
                    DocumentOrderingWrapper other = wrappers[ii];
                    if (id.equals(other.id)) {
                        String[] afterIds = other.getAfterIds();
                        if (Arrays.binarySearch(afterIds, w.id) < 0) {
                            Set newAfterIds = new HashSet(afterIds.length + 1);
                            newAfterIds.addAll(Arrays.asList(afterIds));
                            newAfterIds.add(w.id);
                            other.afterIds = newAfterIds
                                  .toArray(new String[newAfterIds.size()]);
                            Arrays.sort(other.afterIds);
                        }

                        String[] otherBeforeIds = other.getBeforeIds();
                        if (otherBeforeIds.length > 0) {

                            String[] currentBeforeIds = w.getBeforeIds();
                            Set newBeforeIds = new HashSet();
                            newBeforeIds.addAll(Arrays.asList(currentBeforeIds));
                            for (String bid : otherBeforeIds) {
                                if (OTHERS_KEY.equals(bid)) {
                                    continue;
                                }
                                newBeforeIds.add(bid);
                            }
                            String[] temp = newBeforeIds.toArray(new String[newBeforeIds.size()]);
                            Arrays.sort(temp);
                            if (search(temp, w.id)) {
                                throw new CircularDependencyException();
                            }
                            w.beforeIds = temp;
                        }
                    }
                }

            }

            // process after IDs other than 'others'
            for (String id : w.getAfterIds()) {
                if (OTHERS_KEY.equals(id)) {
                    continue;
                }
                for (int ii = 0; ii < wrappers.length; ii++) {
                    DocumentOrderingWrapper other = wrappers[ii];
                    if (id.equals(other.id)) {
                        String[] beforeIds = other.getBeforeIds();
                        if (Arrays.binarySearch(beforeIds, w.id) < 0) {
                            Set newBeforeIds =
                                  new HashSet(beforeIds.length + 1);
                            newBeforeIds.addAll(Arrays.asList(beforeIds));
                            newBeforeIds.add(w.id);
                            other.beforeIds = newBeforeIds
                                  .toArray(new String[newBeforeIds.size()]);
                            Arrays.sort(other.beforeIds);
                        }
                        String[] otherAfterIds = other.getAfterIds();
                        if (otherAfterIds.length > 0) {
                            String[] currentAfterIds = w.getAfterIds();
                            Set newAfterIds = new HashSet();
                            newAfterIds.addAll(Arrays.asList(currentAfterIds));
                            for (String bid : otherAfterIds) {
                                if (OTHERS_KEY.equals(bid)) {
                                    continue;
                                }
                                newAfterIds.add(bid);
                            }
                            String[] temp = newAfterIds.toArray(new String[newAfterIds.size()]);
                            Arrays.sort(temp);
                            if (search(temp, w.id)) {
                                throw new CircularDependencyException();
                            }
                            w.afterIds = temp;
                        }
                    }
                }
            }
        }
    }


    /**
     * Simple helper method around Arrays.binarySearch().
     * @param ids an array of IDs
     * @param id the ID to search for
     * @return true if ids contains id
     */
    private static boolean search(String[] ids, String id) {

        return (Arrays.binarySearch(ids, id) >= 0);

    }


    /**
     * 

* Performs the initialization necessary to allow sorting of * faces-config documents. *

*/ private void init() { Element documentElement = documentInfo.getDocument().getDocumentElement(); String namespace = documentElement.getNamespaceURI(); id = getDocumentName(documentElement); NodeList orderingElements = documentElement.getElementsByTagNameNS(namespace, ORDERING); Set beforeIds = null; Set afterIds = null; if (orderingElements.getLength() > 0) { for (int i = 0, len = orderingElements.getLength(); i < len; i++) { Node orderingNode = orderingElements.item(i); NodeList children = orderingNode.getChildNodes(); for (int j = 0, jlen = children.getLength(); j < jlen; j++) { Node n = children.item(j); if (beforeIds == null) { beforeIds = extractIds(n, BEFORE); } if (afterIds == null) { afterIds = extractIds(n, AFTER); } } } } this.beforeIds = ((beforeIds != null) ? beforeIds.toArray(new String[beforeIds.size()]) : new String[0]); this.afterIds = ((afterIds != null) ? afterIds.toArray(new String[afterIds.size()]) : new String[0]); Arrays.sort(this.beforeIds); Arrays.sort(this.afterIds); // ensure any ID defined in the 'before' array isn't present in the // 'after' array and vice versa as a documents can't come before // *and* after another. checkDuplicates(this.beforeIds, this.afterIds); checkDuplicates(this.afterIds, this.beforeIds); } private String getDocumentName(Element documentElement) { NodeList children = documentElement.getChildNodes(); String documentName = ""; if (children != null && children.getLength() > 0) { for (int i = 0, len = children.getLength(); i < len; i++) { Node n = children.item(i); if (NAME.equals(n.getLocalName())) { documentName = getNodeText(n); break; } } } return documentName; } /** * Ensure the IDs in source aren't present in searchTarget. */ private void checkDuplicates(String[] source, String[] searchTarget) { for (String id : source) { if (search(searchTarget, id)) { String msg = MessageFormat.format("Document {0} is specified to come before and after {1}.", documentInfo.getDocument().getDocumentURI(), id); throw new ConfigurationException(msg); } } } /** * Extract and return a Set of IDs contained within the provided * Node. */ private Set extractIds(Node n, String nodeName) { Set idsList = null; if (nodeName.equals(n.getLocalName())) { idsList = new HashSet(); NodeList ids = n.getChildNodes(); for (int k = 0, klen = ids.getLength(); k < klen; k++) { Node idNode = ids.item(k); if (NAME.equals(idNode.getLocalName())) { String id = getNodeText(idNode); if (id != null) { idsList.add(id); } } if (OTHERS.equals(idNode.getLocalName())) { if (id != null) { idsList.add(OTHERS_KEY); } } } } return idsList; } /** * Return the textual content, if any, of the provided Node. */ private String getNodeText(Node node) { String res = null; if (node != null) { res = node.getTextContent(); if (res != null) { res = res.trim(); } } return ((res != null && res.length() != 0) ? res : null); } public static HashMap getDocumentHashMap(DocumentOrderingWrapper[] documents) { HashMap configMap = new HashMap(); for (DocumentOrderingWrapper document : documents) { String name = document.id; if (name != null && !"".equals(name)) { configMap.put(name, document); } } return configMap; } public static void preSort(DocumentOrderingWrapper[] documents) { List anonymousAndUnorderedList = new ArrayList(); Map linkedMap = new LinkedHashMap(); DocumentOrderingWrapper[] copyOfDocuments = new DocumentOrderingWrapper[documents.length]; System.arraycopy( documents, 0, copyOfDocuments, 0, documents.length ); int i = 0; for (DocumentOrderingWrapper w : documents) { String[] bfs = w.getBeforeIds(); String[] afs = w.getAfterIds(); int knowledge = bfs.length + afs.length; if ((w.id == null || "".equals(w.id)) && (!w.isOrdered())) { anonymousAndUnorderedList.add(w); } else { linkedMap.put(i, knowledge); } i++; } linkedMap = descendingByValue(linkedMap); i = 0; for (Map.Entry entry : linkedMap.entrySet()) { Integer index = entry.getKey(); documents[i] = copyOfDocuments[index]; i++; } for (DocumentOrderingWrapper w : anonymousAndUnorderedList) { documents[i] = w; i++; } } public static > Map descendingByValue(Map map) { List> list = new LinkedList>(map.entrySet()); Collections.sort(list, new Comparator>() { public int compare(Map.Entry a, Map.Entry b) { return (b.getValue()).compareTo(a.getValue()); } }); Map result = new LinkedHashMap(); for (Map.Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } // ---------------------------------------------------------- Nested Classes @SuppressWarnings({"ComparatorNotSerializable"}) private static final class DocumentOrderingComparator implements Comparator { // --------------------------------------------- Methods from Comparator public int compare(DocumentOrderingWrapper wrapper1, DocumentOrderingWrapper wrapper2) { String w1Id = wrapper1.id; String w2Id = wrapper2.id; boolean w1IsOrdered = wrapper1.isOrdered(); boolean w2IsOrdered = wrapper2.isOrdered(); if (w1IsOrdered && !w2IsOrdered) { if (wrapper1.isAfterOrdered() && !wrapper1.isBeforeOthers()) { return SWAP; } } boolean w2IsBeforeW1 = wrapper2.isBefore(w1Id); boolean w1IsAfterW2 = wrapper1.isAfter(w2Id); if (w2IsBeforeW1 || w1IsAfterW2) { return SWAP; // move w2 before w1 } // no explicit ID ordering. Check others ordering boolean w1IsAfterOthers = wrapper1.isAfterOthers(); if (w1IsAfterOthers && !wrapper1.isBefore(w2Id) && !(wrapper1.isAfterOthers() && wrapper2.isAfterOthers())) { return SWAP; } boolean w2IsBeforeOthers = wrapper2.isBeforeOthers(); if (w2IsBeforeOthers && !wrapper2.isAfter(w1Id) && !(wrapper1.isBeforeOthers() && wrapper2.isBeforeOthers())) { return SWAP; } return DO_NOT_SWAP; } } // END FacesConfigComparator @SuppressWarnings({"serial"}) private static final class CircularDependencyException extends Exception { // -------------------------------------------------------- Constructors public CircularDependencyException() { super(); } public CircularDependencyException(String message) { super(message); } public CircularDependencyException(String message, Throwable cause) { super(message, cause); } public CircularDependencyException(Throwable cause) { super(cause); } } // END CircularDependencyException }