/* * A extended base abstract class for a property solver. * * Copyright (c) 1998-2009 The Regents of the University of California. All * rights reserved. Permission is hereby granted, without written agreement and * without license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies of * this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.data.ontologies; import java.io.File; import java.io.FilenameFilter; import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import ptolemy.actor.parameters.SharedParameter; import ptolemy.data.BooleanToken; import ptolemy.data.expr.Parameter; import ptolemy.data.properties.PropertyFailedRegressionTestException; import ptolemy.data.type.BaseType; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.InternalErrorException; import ptolemy.kernel.util.KernelException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.NamedObj; import ptolemy.kernel.util.StringAttribute; import ptolemy.util.ClassUtilities; import ptolemy.util.FileUtilities; import ptolemy.util.MessageHandler; import ptolemy.util.StringUtilities; ////////////////////////////////////////////////////////////////////////// ////PropertySolver /** * A extended base abstract class for a property solver. * * @author Man-Kit Leung * @version $Id$ * @since Ptolemy II 7.1 * @Pt.ProposedRating Red (mankit) * @Pt.AcceptedRating Red (mankit) */ public abstract class PropertySolver extends PropertySolverBase { /** * Construct a PropertySolver with the specified container and name. If this * is the first PropertySolver created in the model, the shared utility * object will also be created. * @param container The specified container. * @param name The specified name. * @exception IllegalActionException If the PropertySolver is not of an * acceptable attribute for the container. * @exception NameDuplicationException If the name coincides with an * attribute already in the container. */ public PropertySolver(NamedObj container, String name) throws IllegalActionException, NameDuplicationException { super(container, name); action = new SharedParameter(this, "action", PropertySolver.class, TRAINING); action.setStringMode(true); _addActions(action); _momlHandler = new PropertyMoMLHandler(this, "PropertyMoMLHandler"); // FIXME: We do not want this GUI dependency here... // This attribute should be put in the MoML in the library instead // of here in the Java code. //new PropertyDisplayActions(this, "PropertyDisplayActions"); } /////////////////////////////////////////////////////////////////// //// ports and parameters //// /** * The action mode of the solver (e.g. ANNOTATE, TRAINING, CLEAR, and etc.). */ public Parameter action; /////////////////////////////////////////////////////////////////// //// public methods //// /** * Record the specified error message. * @param error The specified error message string. */ public void addErrors(String error) { _sharedUtilities.addErrors(error); } /** * Check if there is any regression testing errors after resolving * properties. If so, throw a new PropertyFailedRegressionTestException with * an error message that includes all the properties that does not match the * regression test values. * @exception PropertyFailedRegressionTestException Thrown if there is any * errors in the regression test. */ public void checkErrors() throws PropertyResolutionException { // first, store errors to statistics _addErrorStatistics(); // FIXME: remove the errors as well. List errors = _sharedUtilities.removeErrors(); Collections.sort(errors); if (!errors.isEmpty()) { String errorMessage = errors.toString(); throw new PropertyResolutionException(this, errorMessage); } } public void checkResolutionErrors() throws IllegalActionException { for (Object propertyable : getAllPropertyables()) { _recordUnacceptableSolution(propertyable, getProperty(propertyable)); } checkErrors(); } /** * If the value of the highlight parameter is set to true, highlight the * given property-able object with the specified color associated with the * given property, if there exists any. If the value of the showText * parameter is true, show the given property value for the given * property-able object. If the property is not null, this looks for the * _showInfo parameter in the property-able object. Create a new _showInfo * StringParameter if one does not already exist. Set its value to * the given property value. If the given property is null, this removes the * _showInfo parameter from the property-able object. * @exception IllegalActionException Thrown if an error occurs when creating * or setting the value for the _showInfo parameter in the property-able * object. Thrown if an error occurs when creating or setting the value for * the highlightColor attribute in the property-able object. */ public void displayProperties() throws IllegalActionException { // Do nothing if we are not in a mode that allows display. if (!(isResolve() || isView())) { return; } if (_momlHandler.highlight.getToken() == BooleanToken.TRUE) { _momlHandler.highlightProperties(); } if (_momlHandler.showText.getToken() == BooleanToken.TRUE) { _momlHandler.showProperties(); } } public PropertyMoMLHandler getMoMLHandler() { return _momlHandler; } /** * Return the previous resolved property for the given object. * @param object The given object. * @return The previous resolved property for the given object. */ public Concept getPreviousProperty(Object object) { return _previousProperties.get(object); } /** * Return the statistics map. * @return The statistics map. */ public Map<Object, Object> getStats() { return _stats; } /** * Return the trained exception message string. If there is no trained * exception, an empty string is return. * @return The trained exception message string. */ public String getTrainedException() { StringAttribute attribute = (StringAttribute) getAttribute(_TRAINED_EXCEPTION_ATTRIBUTE_NAME); if (attribute == null) { return ""; } else { return attribute.getExpression(); } } /** * Return the name of the trained exception attribute. * @return The name of the trained exception attribute. */ public Attribute getTrainedExceptionAttribute() { return getAttribute(_TRAINED_EXCEPTION_ATTRIBUTE_NAME); } public String getTrainedExceptionAttributeName() { return _TRAINED_EXCEPTION_ATTRIBUTE_NAME; } /** * Return the error message string that shows the mismatch between the two * given exception strings. This method does not compare the content between * the input strings. It merely wraps the input strings into a larger error * message that says there is a mismatch between the two. This is used to * generate the error message for failed regression test that detects a * mismatch between the expected (trained) exception and the generate * exception. * @param exception The first input error message. * @param trainedException The second input error message. * @return The exception message string. */ public static String getTrainedExceptionMismatchMessage(String exception, String trainedException) { return "The generated exception:" + _eol + "-------------------------------------------------------" + _eol + exception + _eol + "-------------------------------------------------------" + _eol + " does not match the trained exception:" + _eol + "-------------------------------------------------------" + _eol + trainedException + _eol + "-------------------------------------------------------" + _eol; } /** * Increment the given field the solver statistics by a given number. This * is used for incrementing integer type statistics. If the given field does * not exist, it starts the count of the field at zero. * @param field The given field of the solver statistics. * @param increment The given number to increment by. */ public void incrementStats(Object field, long increment) { incrementStats(_stats, field, increment); } /** * Increment the given field in the given statistics map by a given number. * This is used for incrementing integer type statistics. If the given field * does not exist, it starts the count of the field at zero. * @param map The statistics map. * @param field The field (key) to increment. * @param increment The increment amount. */ public static void incrementStats(Map map, Object field, Number increment) { Number current = (Number) map.get(field); if (current == null) { current = 0; } map.put(field, current.longValue() + increment.longValue()); } /** * Invoke the solver directly. * @return True if the invocation succeeds; otherwise false which means an * error has occurred during the process. */ public boolean invokeSolver() { return invokeSolver(null); } /** * Invoke the solver from another component (e.g. model analyzer). * @param component The given component. * @return True if the invocation succeeds; otherwise false which means an * error has occurred during the process. */ public boolean invokeSolver(NamedObj component) { boolean success = false; try { success = resolveProperties(component, true); updateProperties(); checkErrors(); displayProperties(); if (isTraining() && success) { _setTesting(); } } catch (KernelException e) { resetAll(); throw new InternalErrorException(e); } return success; } /** True if the solver is in clearing mode; otherwise false. */ public boolean isClear() { return action.getExpression().equals(PropertySolver.CLEAR); } /** True if the solver is in resolution mode; otherwise false. */ public boolean isResolve() { return action.getExpression().equals(ANNOTATE) || action.getExpression().equals(TRAINING); } /** * Return true if the specified property-able object is settable; otherwise * false which means that its property has been set by * PropertyHelper.setEquals(). * @param object The specified property-able object. * @return True if the specified property-able object is settable, otherwise * false. */ public boolean isSettable(Object object) { return !_nonSettables.contains(object); } /** True if the solver is in testing mode; otherwise false. */ public boolean isTesting() { return action.getExpression().equals(PropertySolver.TEST); } /** True if the solver is in training mode; otherwise false. */ public boolean isTraining() { return action.getExpression().equals(TRAINING); } /** True if the solver is in viewing mode; otherwise false. */ public boolean isView() { return action.getExpression().equals(PropertySolver.VIEW); } /** * Record the previous property of the given object. * @param object The given object. * @param property The given property. */ public void recordPreviousProperty(Object object, Concept property) { _previousProperties.put(object, property); } /** * Record the specified exception message as a trained exception. This make * the trained exception persistent by creating or updating the trained * exception attribute contained by this solver. * @param exceptionMessage The given exception message. * @exception IllegalActionException Thrown if an error occurs when creating * or updating the trained exception attribute. */ public void recordTrainedException(String exceptionMessage) throws IllegalActionException { StringAttribute attribute = (StringAttribute) getAttribute(_TRAINED_EXCEPTION_ATTRIBUTE_NAME); if (attribute == null) { try { attribute = new StringAttribute(this, _TRAINED_EXCEPTION_ATTRIBUTE_NAME); } catch (NameDuplicationException e) { assert false; } } attribute.setExpression(exceptionMessage); } /** * Reset the solver. This removes the internal states of the solver (e.g. * previously recorded properties, statistics, and etc.). */ public void reset() { super.reset(); _analyzer = null; _previousProperties = new HashMap<Object, Concept>(); _stats = new TreeMap<Object, Object>(); } /** * */ public void resolveProperties() throws KernelException { resolveProperties(_analyzer, false); } /** * Resolve the properties. * @exception KernelException */ public boolean resolveProperties(boolean isInvoked) throws KernelException { return resolveProperties(_analyzer, isInvoked); } /** * Resolve the properties (invoked from a ModelAnalyzer). * @exception KernelException */ public boolean resolveProperties(NamedObj analyzer) throws KernelException { return resolveProperties(analyzer, false); } /** * Resolve the property values for the top-level entity that contains the * solver. * @param analyzer The model analyzer that invokes the solver. However, this * is null if the solver is invoked directly from its GUI. * @param isInvoked Whether the solver is directly invoked or activated * through solver dependencies. * @return True if resolution succeeds as expected; Otherwise, false. * @exception IllegalActionException TODO */ public boolean resolveProperties(NamedObj analyzer, boolean isInvoked) throws KernelException { System.out.println("In resolveProperties of PropertySolver"); boolean success = true; boolean noException = true; try { _initializeStatistics(); getSharedUtilities().addRanSolvers(this); _analyzer = analyzer; _isInvoked = isInvoked; // Clear the resolved properties for the chosen solver. String actionValue = action.getExpression(); if (actionValue.equals(CLEAR_ANNOTATION)) { if (isInvoked) { _momlHandler.clearAnnotations(); } return true; } else if (actionValue.equals(CLEAR)) { if (isInvoked) { _resolvedProperties.clear(); _momlHandler.clearProperties(); _momlHandler.clearDisplay(); } return true; } else if (actionValue.equals(VIEW)) { if (isInvoked) { _momlHandler.clearDisplay(); displayProperties(); } return true; } // If this is not an intermediate (invoked) solver, // we need to clear the display. if (isInvoked && isResolve()) { PropertySolver previousSolver = _sharedUtilities._previousInvokedSolver; // Clear the display properties of the previous invoked solver. // If no solver is invoked previously, at least clear // the previous highlighting for this solver. if (previousSolver == null || previousSolver.getContainer() == null) { previousSolver = this; } previousSolver._momlHandler.clearDisplay(); _sharedUtilities._previousInvokedSolver = this; } _resolveProperties(analyzer); checkResolutionErrors(); } catch (PropertyResolutionException ex) { noException = false; // resolution exceptions. that means resolution ended prematurely. // But that may not means that this is an improper behavior // Check whether we are expecting an exception, // if in testing mode, then add a RegressionTestErrorException PropertySolver failedSolver = (PropertySolver) ex.getSolver(); // Remove '\r' characters to make Windows-Linux comparable strings. String trainedException = failedSolver.getTrainedException() .replaceAll("\r", ""); String exception = ex.getMessage().replaceAll("\r", ""); if (isTesting()) { if (!exception.equals(trainedException)) { addErrors(PropertySolver .getTrainedExceptionMismatchMessage(exception, trainedException)); } } else if (isResolve()) { if (!exception.equals(trainedException)) { // ask the user if this is expected, boolean doRecord = MessageHandler .yesNoQuestion(PropertySolver .getTrainedExceptionMismatchMessage( exception, trainedException) + "Do you want to record it?"); if (doRecord) { // If so, record the exception in ex.solver. failedSolver.recordTrainedException(exception); } else { if (isTraining()) { // Don't set mode to TEST because the user // did not train (record) this exception. success = false; } throw ex; } } } } if (isTesting() && noException && getTrainedException().length() > 0) { // if in TEST mode, if there is a previously trained // RegressionTestErrorExceptionException // and we do not get one in the resolution, // then we throw an exception. addErrors(PropertySolver.getTrainedExceptionMismatchMessage("", getTrainedException())); } return success; } /** * Set the action mode of the solver. This sets the expression of the action * parameter to the specified action value string. * @param actionString The specified action value. * @return The previous value of the action parameter. */ public String setAction(String actionString) { String oldAction = action.getExpression(); action.setExpression(actionString); return oldAction; } /** * Prepare for automatic testing. In this base class, do nothing. */ public void setOptions(Map options) { return; } /** * Update the property. This method is called from both invoked and * auxiliary solvers. * @exception IllegalActionException */ public void updateProperties() throws IllegalActionException { if (isView() || isClear()) { return; } boolean hasDecided = false; boolean userDecision = true; // Only test the invoked solver. boolean updating = isResolve(); _addStatistics(); for (Object propertyable : getAllPropertyables()) { if (!NamedObj.class.isInstance(propertyable)) { // FIXME: This happens when the propertyable is an ASTNodes, // or any non-Ptolemy objects. We are not updating their // property values, nor doing regression test for them. continue; } NamedObj namedObj = (NamedObj) propertyable; // Get the value resolved by the solver. Concept property = getProperty(namedObj); if (updating) { Concept previous = getPreviousProperty(namedObj); if (!_isInvoked && !hasDecided) { // Check if the previous and resolved properties are // different. if (previous == null && property != null || previous != null && !previous.equals(property)) { /* FIXME: find a better way. if (_analyzer == null) { // Get user's decision. userDecision = MessageHandler .yesNoQuestion("Resolved auxilary property for \"" + getExtendedUseCaseName() + "\" is different from previous. " + "Update this property?"); } else { // Suppress the dialog. userDecision = ((Parameter) _analyzer .getAttribute("overwriteDependentProperties")) .getToken() == BooleanToken.TRUE; } */ // Remember that we have made a decision. hasDecided = true; } } // Do nothing only if the previous resolved property // did not exist AND the user did not want to update. if (userDecision || previous != null) { // Get the property attribute so we can either update // its value or compare its value against the resolved // value (regression testing). ConceptAttribute attribute = _getPropertyAttribute(namedObj); _updatePropertyAttribute(attribute, userDecision ? property : previous); } } } System.out.println(_getStatsAsString(": ")); } /////////////////////////////////////////////////////////////////// //// public variables //// public static final String NONDEEP_TEST_OPTION = "-nondeep"; /////////////////////////////////////////////////////////////////// //// protected methods //// /** * Populate the possible choices for the specified action parameter. * @param actionParameter The specified action parameter. */ protected static void _addActions(Parameter actionParameter) { actionParameter.addChoice(ANNOTATE); actionParameter.addChoice(CLEAR); actionParameter.addChoice(TEST); actionParameter.addChoice(TRAINING); actionParameter.addChoice(VIEW); actionParameter.addChoice(CLEAR_ANNOTATION); } /** * Add choices to the parameter where the choices are subdirectories of the * directoryPath. DirectoryPaths in the file system and in Jar URLs are * handled. * @param parameter The parameter to be updated with the subdirectories * @param directoryPath The directory to be searched for subdirectories. * @exception IllegalActionException If there is a problem reading the * directory as a file or JAR URL. */ protected void _addChoices(Parameter parameter, String directoryPath) throws IllegalActionException { // Use a FilenameFilter so that we can access files via // Web Start. try { URI directoryURI = new URI(FileUtilities.nameToURL(directoryPath, null, null).toExternalForm().replaceAll(" ", "%20")); File directory = null; try { try { directory = new File(directoryURI); } catch (Throwable throwable) { throw new InternalErrorException(this, throwable, "Failed to find directories in the URI: \"" + directoryURI + "\""); } DirectoryNameFilter filter = new DirectoryNameFilter(); File[] directories = directory.listFiles(filter); if (directories == null) { throw new InternalErrorException(this, null, "Failed to find directories in \"" + directoryPath + "\""); } else { for (File element : directories) { String directoryName = element.getName(); parameter.addChoice(directoryName); } } } catch (Throwable throwable) { try { if (!directoryURI.toString().startsWith("jar:")) { throw throwable; } else { // We have a jar URL, we are probably in Web Start List<String> directories = ClassUtilities .jarURLDirectories(directoryURI.toURL()); for (String directoryFullPath : directories) { // Get the name of just the directory String directoryName = directoryFullPath; // System.out.println("PropertyConstraintSolver0: " // + directoryName.lastIndexOf("/") // + " " + directoryName.length() // + " " + directoryName); if (directoryName.lastIndexOf("/") > -1) { if (directoryName.lastIndexOf("/") == directoryName .length() - 1) { // Remove the trailing / directoryName = directoryName.substring(0, directoryName.length() - 1); //System.out.println("PropertyConstraintSolver1: " // + directoryName); } directoryName = directoryName .substring(directoryName .lastIndexOf("/") + 1); //System.out.println("PropertyConstraintSolver2: " // + directoryName); } //System.out.println("PropertyConstraintSolver3: " // + directoryName); parameter.addChoice(directoryName); } } } catch (Throwable throwable2) { System.err.println("Tried to look in jarURL"); throwable2.printStackTrace(); throw new IllegalActionException(this, throwable, "Failed to process " + directoryURI); } } } catch (Throwable ex) { throw new InternalErrorException(this, ex, "Failed to find directories in \"" + directoryPath + "\", the parameter \"" + parameter.getFullName() + "\"cannot be set."); } } /** * Record tracing statistics. * @exception IllegalActionException */ protected void _addStatistics() throws IllegalActionException { _stats.put("# of adapters", _adapterStore.size()); _stats.put("# of propertyables", getAllPropertyables().size()); _stats.put("# of resolved properties", _resolvedProperties.size()); _stats.put("# of resolution errors", _sharedUtilities.getErrors() .size()); _stats.put("has trained resolution errors", getTrainedException() .length() > 0); } /** * Get the propertyable attribute contained by the given propertyable. * @param propertyable The given propertyable object. * @return The property attribute contained by the given propertyable. * @exception IllegalActionException */ protected ConceptAttribute _getPropertyAttribute(NamedObj propertyable) throws IllegalActionException { ConceptAttribute attribute = null; // write results to attribute /* FIXME: Not using attribute. if (getExtendedUseCaseName().startsWith("lattice")) { attribute = (ConceptAttribute) propertyable .getAttribute(getExtendedUseCaseName()); if (attribute == null) { try { attribute = new ConceptAttribute(propertyable, getExtendedUseCaseName()); } catch (NameDuplicationException e) { // This shouldn't happen. If another attribute // has the same name, we should find it before. assert false; } } } else { // FIXME: Error checking? throw new PropertyResolutionException(this, propertyable, "Failed to get the PropertyAttribute."); } */ return attribute; } /* * Return the string representation of the recorded statistics. * * @param separator The delimiter to separate the statistics fields. * * @return The string representation of the recorded statistics. */ protected String _getStatsAsString(String separator) { StringBuffer result = new StringBuffer(); for (Object field : _stats.keySet()) { result.append(field + separator + _stats.get(field) + _eol); } return result.toString(); } /* * Resolve the property values for the specified top-level entity. Print out * the name of the this solver. Sub-classes should overrides this method. * * @param analyzer The specified model analyzer. * * @exception IllegalActionException Not thrown in this base class. */ protected void _resolveProperties(NamedObj analyzer) throws KernelException { /* FIXME: What the heck is an analyzer? System.out.println("Invoking \"" + getName() + "\" (" + getExtendedUseCaseName() + "):"); */ } /////////////////////////////////////////////////////////////////// //// protected variables //// /** * The model analyzer, if the solver is created by one; otherwise, this is * null. */ protected NamedObj _analyzer = null; /** * The handler that issues MoML requests and makes model changes. */ protected PropertyMoMLHandler _momlHandler; /** * True if the solver is invoked directly; otherwise, false which means it * acts as an intermediate solver. */ protected boolean _isInvoked; /** * The system-specific end-of-line character. */ protected static final String _eol = StringUtilities .getProperty("line.separator"); /** The display label for "annotate" in the action choices */ protected static final String ANNOTATE = "ANNOTATE"; /** The display label for "clear" in the action choices */ protected static final String CLEAR = "CLEAR"; /** The display label for "clear annotation" in the action choices */ protected static final String CLEAR_ANNOTATION = "CLEAR_ANNOTATION"; /** The display label for "test" in the action choices */ protected static final String TEST = "TEST"; /** The display label for "training" in the action choices */ protected static final String TRAINING = "TRAINING"; /** The display label for "view" in the action choices */ protected static final String VIEW = "VIEW"; /////////////////////////////////////////////////////////////////// //// private methods //// /** * Record as an error for the given property-able object and its resolved * property. If the given property is null, it does nothing. If the given * property is unacceptable, an error is recorded for the given * property-able object and the property. */ private void _recordUnacceptableSolution(Object propertyable, Concept property) { // Check for unacceptable solution. if (property != null && !property.isValueAcceptable()) { addErrors("Property \"" + property + "\" is not an acceptable solution for " + propertyable + "." + _eol); } } /** * Set the solver to testing mode. */ private void _setTesting() { action.setPersistent(true); setAction(TEST); } /** * @param attribute * @param property * @exception IllegalActionException */ private void _updatePropertyAttribute(ConceptAttribute attribute, Concept property) throws IllegalActionException { if (property != null) { // Write results to attribute attribute.setExpression(property.toString()); } else { attribute.setExpression(""); } } /////////////////////////////////////////////////////////////////// //// private variables //// /** * The record of the previously resolved properties. It is a map between the * property-able objects and their resolved properties. */ private HashMap<Object, Concept> _previousProperties = new HashMap<Object, Concept>(); /** * The record of statistics for the resolution. It is a mapping between keys * and values. To keep track of numerical data, by inserting an Integer or * Long as value (See {@link #incrementStats(Object, long)}). */ private Map<Object, Object> _stats = new LinkedHashMap<Object, Object>(); /** * The name of the trained exception attribute. */ private static String _TRAINED_EXCEPTION_ATTRIBUTE_NAME = "PropertyResolutionExceptionMessage"; protected void _initializeStatistics() { _stats.put("has trained resolution errors", false); _stats.put("# of trained resolution errors", 0); _stats.put("# of adapters", 0); _stats.put("# of propertyables", 0); _stats.put("# of resolved properties", 0); _stats.put("# of manual annotations", 0); } protected void _addErrorStatistics() { Integer errorCount = (Integer) _stats .get("# of trained resolution errors"); if (errorCount == null) { errorCount = 0; } _stats.put("# of trained resolution errors", errorCount + _sharedUtilities.getErrors().size()); } /////////////////////////////////////////////////////////////////// //// inner classes //// /** * Look for directories that do are not CVS or .svn. */ static class DirectoryNameFilter implements FilenameFilter { // FindBugs suggests making this class static so as to decrease // the size of instances and avoid dangling references. /** * Return true if the specified file names a directory that is not named * "CVS" or ".svn". * @param directory the directory in which the potential directory was * found. * @param name the name of the directory or file. * @return true if the file is a directory that contains a file called * configuration.xml */ public boolean accept(File directory, String name) { try { File file = new File(directory, name); if (!file.isDirectory() || file.getName().equals("CVS") || file.getName().equals(".svn")) { return false; } } catch (Exception ex) { return false; } return true; } } }