Example usage for org.apache.commons.digester Rule Rule

List of usage examples for org.apache.commons.digester Rule Rule

Introduction

In this page you can find the example usage for org.apache.commons.digester Rule Rule.

Prototype

public Rule() 

Source Link

Document

Base constructor.

Usage

From source file:com.pureinfo.srmcenter.datasync.client.SyncClientHelper.java

public static Map getSrmSrmcMapping(String _sFileName) throws PureException {
    Digester digester = new Digester();
    digester.addObjectCreate("sync-data", HashMap.class);
    digester.addRule("sync-data/property", new Rule() {
        public void begin(String _sNamespace, String _sName, Attributes _attributes) throws Exception {
            Map mapping = (Map) digester.peek();
            SyncMapping sync = new SyncMapping();
            sync.setSRM(_attributes.getValue("srm"));
            sync.setSRMC(_attributes.getValue("srmc"));
            String sType = _attributes.getValue("type");
            if (sType == null)
                sync.setType(0);/* ww  w  .ja v  a2s .co  m*/
            else
                sync.setType(Integer.parseInt(sType));
            mapping.put(sync.getSRMC(), sync);
        }
    });
    try {
        return (Map) digester.parse(_sFileName);
    } catch (Exception ex) {
        throw new PureException(PureException.UNKNOWN, "", ex);
    }
}

From source file:com.threerings.stage.tools.xml.StageSceneParser.java

/**
 * Constructs a parser that can be used to parse Stage scene models.
 *//*from   ww  w  .  j  a  v  a  2  s  .co  m*/
public StageSceneParser() {
    super("");

    // add a rule to parse scene colorizations
    _digester.addRule("scene/zations/zation", new Rule() {
        @Override
        public void begin(String namespace, String name, Attributes attrs) throws Exception {
            StageSceneModel yoscene = (StageSceneModel) digester.peek();
            int classId = Integer.parseInt(attrs.getValue("classId"));
            int colorId = Integer.parseInt(attrs.getValue("colorId"));
            yoscene.setDefaultColor(classId, colorId);
        }
    });

    // add rule sets for our aux scene models
    registerAuxRuleSet(new SpotSceneRuleSet() {
        @Override
        protected Location createLocation() {
            return new StageLocation();
        }
    });
    registerAuxRuleSet(new StageMisoSceneRuleSet());
}

From source file:com.threerings.miso.tools.xml.SparseMisoSceneRuleSet.java

public void addRuleInstances(String prefix, Digester dig) {
    // this creates the appropriate instance when we encounter our
    // prefix tag
    dig.addRule(prefix, new Rule() {
        @Override/*from ww w. j  a  va  2s  . c om*/
        public void begin(String namespace, String name, Attributes attributes) throws Exception {
            digester.push(createMisoSceneModel());
        }

        @Override
        public void end(String namespace, String name) throws Exception {
            digester.pop();
        }
    });

    // set up rules to parse and set our fields
    dig.addRule(prefix + "/swidth", new SetFieldRule("swidth"));
    dig.addRule(prefix + "/sheight", new SetFieldRule("sheight"));
    dig.addRule(prefix + "/defTileSet", new SetFieldRule("defTileSet"));

    String sprefix = prefix + "/sections/section";
    dig.addObjectCreate(sprefix, Section.class.getName());
    dig.addRule(sprefix, new SetPropertyFieldsRule());
    dig.addRule(sprefix + "/base", new SetFieldRule("baseTileIds"));
    addObjectExtractor(dig, "objects", sprefix, "addObject");
    dig.addSetNext(sprefix, "setSection", Section.class.getName());
}

From source file:com.threerings.miso.tools.xml.SimpleMisoSceneRuleSet.java

public void addRuleInstances(String prefix, Digester dig) {
    // this creates the appropriate instance when we encounter our
    // prefix tag
    dig.addRule(prefix, new Rule() {
        @Override/*from   w ww .ja  v  a  2  s.c  om*/
        public void begin(String namespace, String name, Attributes attributes) throws Exception {
            digester.push(createMisoSceneModel());
        }

        @Override
        public void end(String namespace, String name) throws Exception {
            digester.pop();
        }
    });

    // set up rules to parse and set our fields
    dig.addRule(prefix + "/width", new SetFieldRule("width"));
    dig.addRule(prefix + "/height", new SetFieldRule("height"));
    dig.addRule(prefix + "/viewwidth", new SetFieldRule("vwidth"));
    dig.addRule(prefix + "/viewheight", new SetFieldRule("vheight"));
    dig.addRule(prefix + "/base", new SetFieldRule("baseTileIds"));

    dig.addObjectCreate(prefix + "/objects", ArrayList.class.getName());
    dig.addObjectCreate(prefix + "/objects/object", ObjectInfo.class.getName());
    dig.addSetNext(prefix + "/objects/object", "add", Object.class.getName());

    dig.addRule(prefix + "/objects/object", new SetPropertyFieldsRule());

    dig.addRule(prefix + "/objects", new CallMethodSpecialRule() {
        @Override
        public void parseAndSet(String bodyText, Object target) throws Exception {
            @SuppressWarnings("unchecked")
            ArrayList<ObjectInfo> ilist = (ArrayList<ObjectInfo>) target;
            ArrayList<ObjectInfo> ulist = Lists.newArrayList();
            SimpleMisoSceneModel model = (SimpleMisoSceneModel) digester.peek(1);

            // filter interesting and uninteresting into two lists
            for (int ii = 0; ii < ilist.size(); ii++) {
                ObjectInfo info = ilist.get(ii);
                if (!info.isInteresting()) {
                    ilist.remove(ii--);
                    ulist.add(info);
                }
            }

            // now populate the model
            SimpleMisoSceneModel.populateObjects(model, ilist, ulist);
        }
    });
}

From source file:com.threerings.media.image.tools.xml.ColorPositoryParser.java

@Override
protected void addRules(Digester digest) {
    // create and configure class record instances
    String prefix = "colors/class";
    digest.addObjectCreate(prefix, ClassRecord.class.getName());
    digest.addRule(prefix, new SetPropertyFieldsRule());
    digest.addSetNext(prefix, "addClass", ClassRecord.class.getName());

    // create and configure color record instances
    prefix += "/color";
    digest.addRule(prefix, new Rule() {
        @Override/*from www.j  a v a2  s  .c  o  m*/
        public void begin(String namespace, String name, Attributes attributes) throws Exception {
            // we want to inherit settings from the color class when
            // creating the record, so we do some custom stuff
            ColorRecord record = new ColorRecord();
            ClassRecord clrec = (ClassRecord) digester.peek();
            record.starter = clrec.starter;
            digester.push(record);
        }

        @Override
        public void end(String namespace, String name) throws Exception {
            digester.pop();
        }
    });
    digest.addRule(prefix, new SetPropertyFieldsRule());
    digest.addSetNext(prefix, "addColor", ColorRecord.class.getName());
}

From source file:com.hextilla.cardbox.xml.GameParser.java

public GameParser() {
    // create and configure our digester
    _digester = new Digester() {
        @Override/*from w  w w.  j  a  v a  2  s. c om*/
        public void fatalError(SAXParseException exception) throws SAXException {
            // the standard digester needlessly logs a fatal warning here
            if (errorHandler != null) {
                errorHandler.fatalError(exception);
            }
        }
    };

    // add the rules to parse the GameDefinition and its fields
    _digester.addObjectCreate("game", getGameDefinitionClass());
    _digester.addRule("game/ident", new SetFieldRule("ident"));
    _digester.addRule("game/controller", new SetFieldRule("controller"));
    _digester.addRule("game/manager", new SetFieldRule("manager"));

    _digester.addRule("game/match", new Rule() {
        @Override
        public void begin(String namespace, String name, Attributes attrs) throws Exception {
            String type = attrs.getValue("type");
            if (StringUtil.isBlank(type)) {
                String errmsg = "<match> block missing type attribute.";
                throw new Exception(errmsg);
            }
            addMatchParsingRules(digester, type);
        }

        @Override
        public void end(String namespace, String name) throws Exception {
            MatchConfig match = (MatchConfig) digester.pop();
            ((GameDefinition) digester.peek()).match = match;
        }
    });

    // these rules handle customization parameters
    _digester.addRule("game/params", new ObjectCreateRule(ArrayList.class));
    _digester.addSetNext("game/params", "setParams", List.class.getName());
    addParameter("game/params/ai", AIParameter.class);
    addParameter("game/params/range", RangeParameter.class);
    addParameter("game/params/choice", ChoiceParameter.class);
    addParameter("game/params/toggle", ToggleParameter.class);
    addParameter("game/params/file", FileParameter.class);

    // add a rule to put the parsed definition onto our list
    _digester.addSetNext("game", "add", Object.class.getName());
}

From source file:ca.sqlpower.architect.ProjectLoader.java

protected Digester setupDigester() throws ParserConfigurationException, SAXException {
    Digester d = new Digester(new UnescapingSaxParser());
    final ArchitectSession messageOwner = (siblingSession == null ? session : siblingSession);
    d.setValidating(false);//from   w w  w .  j  a  v  a 2 s  . c  om
    d.push(session);

    d.addRule("architect-enterprise-project", new Rule() {
        @Override
        public void begin(String namespace, String name, Attributes attributes) throws Exception {
            UserPrompter loadingWarningPrompt = messageOwner.createUserPrompter(
                    "This file contains an Enterprise project and can only\n"
                            + "be opened in the Architect Enterprise Edition.",
                    UserPromptType.BOOLEAN, UserPromptOptions.OK_CANCEL, UserPromptResponse.CANCEL,
                    UserPromptResponse.CANCEL, "Get Enterprise", "Cancel");
            UserPromptResponse upr = loadingWarningPrompt.promptUser();
            if (upr == UserPromptResponse.OK) {
                try {
                    BrowserUtil.launch("http://www.sqlpower.ca/page/architect-e");
                } catch (IOException e) {
                    throw new DigesterCancelledException();
                }
            }
            throw new DigesterCancelledException();
        }
    });

    //app version number
    d.addRule("architect-project", new Rule() {
        @Override
        public void begin(String namespace, String name, Attributes attributes) throws Exception {
            fileVersion = attributes.getValue("appversion");
            String loadingMessage;
            try {
                if (fileVersion == null) {
                    loadingMessage = "The version of the file cannot be found.";
                    fileVersion = "0";
                } else if (ArchitectVersion.APP_FULL_VERSION.compareTo(new ArchitectVersion(fileVersion)) < 0) {
                    loadingMessage = "This file was last saved with a newer version.\n"
                            + "Loading with an older version may cause data loss.";
                } else {
                    return;
                }
            } catch (Exception e) {
                loadingMessage = "The version of the file cannot be understood.";
            }
            UserPrompter loadingWarningPrompt = messageOwner.createUserPrompter(
                    loadingMessage + "\nDo you wish to try and open the file?", UserPromptType.BOOLEAN,
                    UserPromptOptions.OK_NOTOK_CANCEL, UserPromptResponse.OK, UserPromptResponse.OK,
                    "Try loading", "Upgrade...", "Cancel");
            UserPromptResponse response = loadingWarningPrompt.promptUser();
            if (response == UserPromptResponse.OK) {
                //continue to try loading
            } else if (response == UserPromptResponse.NOT_OK) {
                BrowserUtil.launch(SPSUtils.SQLP_ARCHITECT_URL);
                throw new DigesterCancelledException();
            } else if (response == UserPromptResponse.CANCEL) {
                throw new DigesterCancelledException();
            }
        }
    });

    // project name
    d.addCallMethod("architect-project/project-name", "setName", 0); // argument is element body text

    // source DB connection specs (deprecated in favour of project-data-sources; this is only here for backward compatibility)
    DBCSFactory dbcsFactory = new DBCSFactory();
    d.addFactoryCreate("architect-project/project-connection-specs/dbcs", dbcsFactory);
    d.addSetProperties("architect-project/project-connection-specs/dbcs",
            new String[] { "connection-name", "driver-class", "jdbc-url", "user-name", "user-pass",
                    "sequence-number", "single-login" },
            new String[] { "displayName", "driverClass", "url", "user", "pass", "seqNo", "singleLogin" });
    d.addCallMethod("architect-project/project-connection-specs/dbcs", "setName", 0);
    // these instances get picked out of the dbcsIdMap by the SQLDatabase factory

    // project data sources (replaces project connection specs)
    d.addFactoryCreate("architect-project/project-data-sources/data-source", dbcsFactory);
    d.addCallMethod("architect-project/project-data-sources/data-source/property", "put", 2);
    d.addCallParam("architect-project/project-data-sources/data-source/property", 0, "key");
    d.addCallParam("architect-project/project-data-sources/data-source/property", 1, "value");
    // for the project-data-sources, these instances get picked out of the dbcsIdMap by the SQLDatabase factory

    // but for the create kettle job settings, we add them explicitly

    // source database hierarchy
    d.addObjectCreate("architect-project/source-databases", LinkedList.class);
    d.addSetNext("architect-project/source-databases", "setSourceDatabaseList");

    SQLDatabaseFactory dbFactory = new SQLDatabaseFactory();
    d.addFactoryCreate("architect-project/source-databases/database", dbFactory);
    d.addSetProperties("architect-project/source-databases/database");
    d.addSetNext("architect-project/source-databases/database", "add");

    d.addObjectCreate("architect-project/source-databases/database/catalog", SQLCatalog.class);
    d.addSetProperties("architect-project/source-databases/database/catalog");
    d.addSetNext("architect-project/source-databases/database/catalog", "addChild");

    SQLSchemaFactory schemaFactory = new SQLSchemaFactory();
    d.addFactoryCreate("*/schema", schemaFactory);
    d.addSetProperties("*/schema");
    d.addSetNext("*/schema", "addChild");

    SQLTableFactory tableFactory = new SQLTableFactory();
    d.addFactoryCreate("*/table", tableFactory);
    d.addSetProperties("*/table");
    d.addCallMethod("*/remarks", "setRemarks", 0);
    d.addSetNext("*/table", "addChild");

    d.addFactoryCreate("*/folder", new SQLFolderFactory());

    SQLColumnFactory columnFactory = new SQLColumnFactory();
    d.addFactoryCreate("*/column", columnFactory);
    d.addSetProperties("*/column");
    d.addCallMethod("*/remarks", "setRemarks", 0);
    // this needs to be manually set last to prevent generic types
    // from overwriting database specific types

    // Old name (it has been updated to sourceDataTypeName)
    d.addCallMethod("*/column", "setSourceDataTypeName", 1);
    d.addCallParam("*/column", 0, "sourceDBTypeName");

    // new name
    d.addCallMethod("*/column", "setSourceDataTypeName", 1);
    d.addCallParam("*/column", 0, "sourceDataTypeName");
    d.addSetNext("*/column", "addChild");

    SQLRelationshipFactory relationshipFactory = new SQLRelationshipFactory();
    d.addFactoryCreate("*/relationship", relationshipFactory);
    d.addSetProperties("*/relationship");
    // the factory adds the relationships to the correct PK and FK tables

    ColumnMappingFactory columnMappingFactory = new ColumnMappingFactory();
    d.addFactoryCreate("*/column-mapping", columnMappingFactory);
    d.addSetProperties("*/column-mapping");
    d.addSetNext("*/column-mapping", "addChild");

    SQLIndexFactory indexFactory = new SQLIndexFactory();
    d.addFactoryCreate("*/index", indexFactory);
    d.addSetProperties("*/index");
    d.addSetNext("*/index", "addChild");

    SQLIndexColumnFactory indexColumnFactory = new SQLIndexColumnFactory();
    d.addFactoryCreate("*/index-column", indexColumnFactory);
    d.addSetProperties("*/index-column");
    d.addSetNext("*/index-column", "addChild");

    SQLExceptionFactory exceptionFactory = new SQLExceptionFactory();
    d.addFactoryCreate("*/sql-exception", exceptionFactory);
    d.addSetProperties("*/sql-exception");
    d.addSetNext("*/sql-exception", "setChildrenInaccessibleReason");

    TargetDBFactory targetDBFactory = new TargetDBFactory();
    // target database hierarchy
    d.addFactoryCreate("architect-project/target-database", targetDBFactory);
    d.addSetProperties("architect-project/target-database");

    DDLGeneratorFactory ddlgFactory = new DDLGeneratorFactory();
    d.addFactoryCreate("architect-project/ddl-generator", ddlgFactory);
    d.addSetProperties("architect-project/ddl-generator");
    d.addSetNext("architect-project/ddl-generator", "setDDLGenerator");

    LiquibaseSettingsFactory lbFactory = new LiquibaseSettingsFactory();
    d.addFactoryCreate("architect-project/liquibase-settings", lbFactory);
    d.addSetProperties("architect-project/liquibase-settings");
    d.addSetNext("architect-project/liquibase-settings", "setLiquibaseSettings");

    ProfileManagerFactory profileManagerFactory = new ProfileManagerFactory();
    d.addFactoryCreate("*/profiles", profileManagerFactory);
    d.addSetProperties("*/profiles");

    /*
     * Backward compatibility: the table and column profiles used to be
     * stored as siblings to each other, with the parent of a column result
     * being the last table result that was read.
     */
    ProfileResultFactory profileResultFactory = new ProfileResultFactory();
    d.addFactoryCreate("*/profiles/profile-result", profileResultFactory);
    /*
     * backward compatibility: the exception property used to be a boolean, and now it's an actual exception.
     * this causes an IllegalArgumentException when parsing old files.
     * this workaround tells the digester not to auto-map the exception property.
     */
    d.addRule("*/profiles/profile-result",
            new SetPropertiesRule(new String[] { "exception" }, new String[] {}));
    d.addSetNext("*/profiles/profile-result", "loadResult");

    d.addFactoryCreate("*/profiles/table-profile-result", new TableProfileResultFactory());
    d.addRule("*/profiles/table-profile-result",
            new SetPropertiesRule(new String[] { "exception" }, new String[] {}));
    d.addSetNext("*/profiles/table-profile-result", "addTableProfileResult");

    d.addFactoryCreate("*/profiles/table-profile-result/column-profile-result",
            new ColumnProfileResultFactory());
    d.addRule("*/profiles/table-profile-result/column-profile-result",
            new SetPropertiesRule(new String[] { "exception" }, new String[] {}));
    d.addSetNext("*/profiles/table-profile-result/column-profile-result", "addColumnProfileResult");

    ProfileResultValueFactory profileResultValueFactory = new ProfileResultValueFactory();
    d.addFactoryCreate("*/profiles/table-profile-result/column-profile-result/avgValue",
            profileResultValueFactory);
    d.addSetNext("*/profiles/table-profile-result/column-profile-result/avgValue", "setAvgValue");
    d.addFactoryCreate("*/profiles/table-profile-result/column-profile-result/minValue",
            profileResultValueFactory);
    d.addSetNext("*/profiles/table-profile-result/column-profile-result/minValue", "setMinValue");
    d.addFactoryCreate("*/profiles/table-profile-result/column-profile-result/maxValue",
            profileResultValueFactory);
    d.addSetNext("*/profiles/table-profile-result/column-profile-result/maxValue", "setMaxValue");

    ProfileResultTopNValueFactory topNValueFactory = new ProfileResultTopNValueFactory();
    d.addFactoryCreate("*/profiles/table-profile-result/column-profile-result/topNvalue", topNValueFactory);
    d.addSetNext("*/profiles/table-profile-result/column-profile-result/topNvalue", "addValueCount");

    FileFactory fileFactory = new FileFactory();
    d.addFactoryCreate("*/file", fileFactory);
    d.addSetNext("*/file", "setFile");

    return d;
}

From source file:org.eclipse.wb.internal.core.model.description.helpers.FactoryDescriptionHelper.java

/**
 * Adds {@link Rule}'s for factory description parsing.
 *//*from  w  w w .j a  v  a  2 s.co  m*/
private static void addRules(Digester digester, EditorState state, final Class<?> declaringClass) {
    // allMethodsAreFactories flag
    {
        String pattern = "factory/allMethodsAreFactories";
        digester.addRule(pattern, new Rule() {
            @Override
            public void body(String namespace, String name, String text) throws Exception {
                Object list = getDigester().pop();
                Boolean allMethodsAreFactories = (Boolean) getDigester().pop();
                if ("true".equalsIgnoreCase(text)) {
                    allMethodsAreFactories = Boolean.TRUE;
                }
                if ("false".equalsIgnoreCase(text)) {
                    allMethodsAreFactories = Boolean.FALSE;
                }
                getDigester().push(allMethodsAreFactories);
                getDigester().push(list);
            }
        });
    }
    // methods
    {
        String pattern = "factory/method";
        digester.addRule(pattern, new Rule() {
            @Override
            public void begin(String namespace, String name, Attributes attributes) throws Exception {
                FactoryMethodDescription factoryMethodDescription = new FactoryMethodDescription(
                        declaringClass);
                Boolean allMethodsAreFactories = (Boolean) getDigester().peek(1);
                factoryMethodDescription.setFactory(
                        allMethodsAreFactories != null ? allMethodsAreFactories.booleanValue() : true);
                digester.push(factoryMethodDescription);
            }

            @Override
            public void end(String namespace, String name) throws Exception {
                digester.pop();
            }
        });
        digester.addSetProperties(pattern);
        digester.addSetNext(pattern, "add");
        digester.addCallMethod(pattern, "postProcess");
        ComponentDescriptionHelper.addParametersRules(digester, pattern + "/parameter", state);
    }
    // invocation
    {
        String pattern = "factory/method/invocation";
        digester.addRule(pattern, new ObjectCreateRule(CreationInvocationDescription.class));
        digester.addRule(pattern, new SetListedPropertiesRule(new String[] { "signature" }));
        // arguments
        digester.addCallMethod(pattern, "setArguments", 1);
        digester.addCallParam(pattern, 0);
        // add
        digester.addSetNext(pattern, "addInvocation");
    }
    // name text
    {
        String pattern = "factory/method/name";
        digester.addCallMethod(pattern, "setPresentationName", 1);
        digester.addCallParam(pattern, 0);
    }
    // untyped parameters
    {
        String pattern = "factory/method/parameters/parameter";
        digester.addCallMethod(pattern, "addParameter", 2);
        digester.addCallParam(pattern, 0, "name");
        digester.addCallParam(pattern, 1);
    }
}

From source file:org.eclipse.wb.internal.core.model.description.LayoutDescription.java

private void loadDescription() throws Exception {
    // prepare resource
    String resourcePath = m_layoutClassName.replace('.', '/') + ".wbp-component.xml";
    ResourceInfo resourceInfo = DescriptionHelper.getResourceInfo(resourcePath, m_toolkit.getId());
    if (resourceInfo == null) {
        DesignerPlugin.log("Not found resource " + m_layoutClassName.replace('.', '/') + ".wbp-component.xml"
                + " in bundle " + m_toolkit.getId());
        return;//from www.j a v  a2  s.c om
    }
    Digester digester;
    // prepare digester
    {
        digester = new Digester();
        digester.setLogger(new NoOpLog());
        digester.addRule("component/creation", new Rule() {
            @Override
            public void begin(String namespace, String name, Attributes attributes) throws Exception {
                final String id = attributes.getValue("id");
                digester.push(id != null ? id : StringUtils.EMPTY);
            }

            @Override
            public void end(String namespace, String name) throws Exception {
                digester.pop();
            }
        });
        digester.addRule("component/creation/source", new Rule() {
            @Override
            public void body(String namespace, String name, String text) throws Exception {
                final String id = (String) digester.peek();
                if (id.equals(m_creationId)) {
                    m_source = text;
                }
            }
        });
    }
    // do parse
    InputStream is = resourceInfo.getURL().openStream();
    try {
        digester.parse(is);
    } finally {
        IOUtils.closeQuietly(is);
    }
}

From source file:org.eclipse.wb.internal.swing.ams.model.property.ConfigurationReader.java

private static List<PropertyGroup> parsePropertyGroups(InputStream input) throws Exception {
    final List<PropertyGroup> groups = Lists.newArrayList();
    // prepare Digester
    Digester digester;//w ww. j  a  v  a2s.  c om
    {
        digester = new Digester();
        digester.setLogger(new NoOpLog());
        // groups/group
        {
            String pattern = "groups/group";
            digester.addRule(pattern, new Rule() {
                @Override
                public void begin(String namespace, String element, Attributes attributes) throws Exception {
                    // prepare required name
                    String name = attributes.getValue("name");
                    Assert.isNotNull(name, Messages.ConfigurationReader_errGroup_noNameAttribute);
                    // prepare optional description
                    String description = attributes.getValue("description");
                    Assert.isNotNull(name, Messages.ConfigurationReader_errGroup_noDescriptionAttribute);
                    // prepare optional category
                    PropertyCategory category = null;
                    {
                        String categoryString = attributes.getValue("category");
                        if (categoryString != null) {
                            category = PropertyCategory.get(categoryString, null);
                        }
                    }
                    // add group
                    PropertyGroup group = new PropertyGroup(name, description, category);
                    digester.push(group);
                    groups.add(group);
                }

                @Override
                public void end(String namespace, String name) throws Exception {
                    digester.pop();
                    super.end(namespace, name);
                }
            });
        }
        // groups/group/property
        {
            String pattern = "groups/group/property";
            digester.addRule(pattern, new Rule() {
                @Override
                public void begin(String namespace, String name, Attributes attributes) throws Exception {
                    // prepare required name
                    String propertyName = attributes.getValue("name");
                    Assert.isNotNull(propertyName, Messages.ConfigurationReader_errProperty_noNameAttribute);
                    // prepare optional category
                    PropertyCategory category = null;
                    {
                        String categoryString = attributes.getValue("category");
                        if (categoryString != null) {
                            category = PropertyCategory.get(categoryString, null);
                        }
                    }
                    // add property
                    PropertyGroup group = (PropertyGroup) digester.peek();
                    group.addProperty(new PropertyConfiguration(propertyName, category));
                }
            });
        }
        // groups/group/other-properties
        {
            String pattern = "groups/group/other-properties";
            digester.addRule(pattern, new Rule() {
                @Override
                public void begin(String namespace, String name, Attributes attributes) throws Exception {
                    PropertyGroup group = (PropertyGroup) digester.peek();
                    group.addProperty(new PropertyConfiguration(null, null));
                }
            });
        }
    }
    // read XML
    try {
        digester.push(groups);
        digester.parse(input);
    } finally {
        IOUtils.closeQuietly(input);
    }
    // done
    return groups;
}