com.mcleodmoores.mvn.natives.defaults.Defaults.java Source code

Java tutorial

Introduction

Here is the source code for com.mcleodmoores.mvn.natives.defaults.Defaults.java

Source

/*
 * Maven tools for native builds
 *
 * Copyright 2014 by Andrew Ian William Griffin <griffin@beerdragon.co.uk>.
 * Released under the GNU General Public License.
 */

package com.mcleodmoores.mvn.natives.defaults;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import com.mcleodmoores.misc.IOCallback;
import com.mcleodmoores.mvn.natives.ArchSource;
import com.mcleodmoores.mvn.natives.DynamicLib;
import com.mcleodmoores.mvn.natives.Executable;
import com.mcleodmoores.mvn.natives.HeaderFile;
import com.mcleodmoores.mvn.natives.Source;
import com.mcleodmoores.mvn.natives.SourceVisitor;
import com.mcleodmoores.mvn.natives.StaticLib;

/**
 * Default values for plugin configuration. Configurations are supplied for common platform and
 * build conventions to simplify POM authoring.
 */
public class Defaults {

    private static final String IDENTIFIER_KEY = "identifier";

    private static final String DYNAMIC_LIB_KEY = "dynamic";

    private static final String EXECUTABLE_KEY = "exec";

    private static final String STATIC_LIB_KEY = "static";

    private static final String HEADER_FILE_KEY = "header";

    private static final String PATH_KEY = "path";

    private static final String PATTERN_KEY = "pattern";

    private static final String ARCH_KEY = "arch";

    private static final String IMPLIB_KEY = "implib";

    private static final String LIBRARY_KEY = "library";

    private static final String TYPE_KEY = "type";

    private static final String BUILD_COMMAND_KEY = "build";

    public static abstract class SourceDefaults {

        private String _path;

        private String _pattern;

        public void setPath(final String path) {
            _path = path;
        }

        public String getPath() {
            return _path;
        }

        public void setPattern(final String pattern) {
            _pattern = pattern;
        }

        public String getPattern() {
            return _pattern;
        }

        public void load(final Properties properties, final String prefix) {
            setPath(getSingle(properties, prefix, PATH_KEY));
            setPattern(getSingle(properties, prefix, PATTERN_KEY));
        }

        /* package */void saveImpl(final Properties properties, final String prefix,
                final AtomicInteger identifier) {
            setSingle(properties, prefix, PATH_KEY, getPath());
            setSingle(properties, prefix, PATTERN_KEY, getPattern());
        }

        public final void save(final Properties properties, final String prefix) {
            saveImpl(properties, prefix, new AtomicInteger());
        }

        /* package */void apply(final Source bean) {
            bean.setPath(ObjectUtils.defaultIfNull(bean.getPath(), getPath()));
            bean.setPattern(ObjectUtils.defaultIfNull(bean.getPattern(), getPattern()));
        }

        /* package */abstract Source create();

    }

    public static class ArchSourceDefaults extends SourceDefaults {

        private String _arch;

        public String getArch() {
            return _arch;
        }

        public void setArch(final String arch) {
            _arch = arch;
        }

        @Override
        public void load(final Properties properties, final String prefix) {
            setArch(getSingle(properties, prefix, ARCH_KEY));
            super.load(properties, prefix);
        }

        @Override
        /* package */void saveImpl(final Properties properties, final String prefix,
                final AtomicInteger identifier) {
            super.saveImpl(properties, prefix, identifier);
            setSingle(properties, prefix, ARCH_KEY, getArch());
        }

        /* package */void apply(final ArchSource bean) {
            super.apply(bean);
            if (bean.getArch() == null) {
                bean.setArch(getArch());
            }
        }

        @Override
        /* package */ArchSource create() {
            final ArchSource instance = new ArchSource();
            apply(instance);
            return instance;
        }

    }

    public static class DynamicLibDefaults extends ArchSourceDefaults {

        private HeaderFileDefaults[] _headers;

        private StaticLibDefaults[] _implibs;

        public HeaderFileDefaults[] getHeaders() {
            return ArrayUtils.clone(_headers);
        }

        public void setHeaders(final HeaderFileDefaults[] headers) {
            _headers = ArrayUtils.clone(headers);
        }

        public StaticLibDefaults[] getImplibs() {
            return ArrayUtils.clone(_implibs);
        }

        public void setImplibs(final StaticLibDefaults[] implibs) {
            _implibs = ArrayUtils.clone(implibs);
        }

        @Override
        public void load(final Properties properties, final String prefix) {
            setHeaders(loadHeaderDefaults(properties, prefix));
            final String[] implibIdentifiers = getMultiple(properties, prefix, IMPLIB_KEY);
            if (implibIdentifiers != null) {
                final StaticLibDefaults[] implibs = new StaticLibDefaults[implibIdentifiers.length];
                for (int i = 0; i < implibIdentifiers.length; i++) {
                    implibs[i] = new StaticLibDefaults();
                    implibs[i].load(properties, implibIdentifiers[i]);
                }
                setImplibs(implibs);
            }
            super.load(properties, prefix);
        }

        @Override
        /* package */void saveImpl(final Properties properties, final String prefix,
                final AtomicInteger identifier) {
            super.saveImpl(properties, prefix, identifier);
            saveMultiple(properties, prefix, HEADER_FILE_KEY, getHeaders(), identifier);
            saveMultiple(properties, prefix, IMPLIB_KEY, getImplibs(), identifier);
            if (prefix.charAt(0) == 'l')
                setSingle(properties, prefix, TYPE_KEY, DYNAMIC_LIB_KEY);
        }

        /* package */void applyLocal(final DynamicLib bean) {
            super.apply(bean);
            if (bean.getHeaders() == null) {
                bean.setHeaders(createHeaderDefaults(getHeaders()));
            }
            if (bean.getImplibs() == null) {
                final StaticLibDefaults[] implibDefaults = getImplibs();
                if (implibDefaults != null) {
                    final StaticLib[] implibs = new StaticLib[implibDefaults.length];
                    for (int i = 0; i < implibDefaults.length; i++) {
                        implibs[i] = implibDefaults[i].create();
                    }
                    bean.setImplibs(implibs);
                }
            }
        }

        /* package */void apply(final DynamicLib bean, final SourceVisitor visitor) {
            applyLocal(bean);
            visitor.applyTo(bean.getHeaders());
            visitor.applyTo(bean.getImplibs());
        }

        @Override
        /* package */DynamicLib create() {
            final DynamicLib instance = new DynamicLib();
            applyLocal(instance);
            return instance;
        }

    }

    public static class ExecutableDefaults extends ArchSourceDefaults {

        private HeaderFileDefaults[] _headers;

        private ArchSourceDefaults[] _libraries;

        public HeaderFileDefaults[] getHeaders() {
            return ArrayUtils.clone(_headers);
        }

        public void setHeaders(final HeaderFileDefaults[] headers) {
            _headers = ArrayUtils.clone(headers);
        }

        public ArchSourceDefaults[] getLibraries() {
            return ArrayUtils.clone(_libraries);
        }

        public void setLibraries(final ArchSourceDefaults[] libraries) {
            _libraries = ArrayUtils.clone(libraries);
        }

        @Override
        public void load(final Properties properties, final String prefix) {
            setHeaders(loadHeaderDefaults(properties, prefix));
            final String[] libraryIdentifiers = getMultiple(properties, prefix, LIBRARY_KEY);
            if (libraryIdentifiers != null) {
                final ArchSourceDefaults[] libraries = new ArchSourceDefaults[libraryIdentifiers.length];
                for (int i = 0; i < libraryIdentifiers.length; i++) {
                    final String type = getSingle(properties, libraryIdentifiers[i], TYPE_KEY);
                    if (STATIC_LIB_KEY.equals(type)) {
                        libraries[i] = new StaticLibDefaults();
                    } else if (DYNAMIC_LIB_KEY.equals(type)) {
                        libraries[i] = new DynamicLibDefaults();
                    } else {
                        libraries[i] = new ArchSourceDefaults();
                    }
                    libraries[i].load(properties, libraryIdentifiers[i]);
                }
                setLibraries(libraries);
            }
            super.load(properties, prefix);
        }

        @Override
        /* package */void saveImpl(final Properties properties, final String prefix,
                final AtomicInteger identifier) {
            super.saveImpl(properties, prefix, identifier);
            saveMultiple(properties, prefix, HEADER_FILE_KEY, getHeaders(), identifier);
            saveMultiple(properties, prefix, LIBRARY_KEY, getLibraries(), identifier);
        }

        /* package */void applyLocal(final Executable bean) {
            super.apply(bean);
            if (bean.getHeaders() == null) {
                bean.setHeaders(createHeaderDefaults(getHeaders()));
            }
            if (bean.getLibraries() == null) {
                final ArchSourceDefaults[] libraryDefaults = getLibraries();
                if (libraryDefaults != null) {
                    final ArchSource[] libraries = new ArchSource[libraryDefaults.length];
                    for (int i = 0; i < libraries.length; i++) {
                        libraries[i] = libraryDefaults[i].create();
                    }
                    bean.setLibraries(libraries);
                }
            }
        }

        /* package */void apply(final Executable bean, final SourceVisitor visitor) {
            applyLocal(bean);
            visitor.applyTo(bean.getHeaders());
            visitor.applyTo(bean.getLibraries());
        }

        @Override
        /* package */Executable create() {
            final Executable instance = new Executable();
            applyLocal(instance);
            return instance;
        }

    }

    public static class StaticLibDefaults extends ArchSourceDefaults {

        private HeaderFileDefaults[] _headers;

        public HeaderFileDefaults[] getHeaders() {
            return ArrayUtils.clone(_headers);
        }

        public void setHeaders(final HeaderFileDefaults[] headers) {
            _headers = ArrayUtils.clone(headers);
        }

        @Override
        public void load(final Properties properties, final String prefix) {
            super.load(properties, prefix);
            setHeaders(loadHeaderDefaults(properties, prefix));
        }

        @Override
        /* package */void saveImpl(final Properties properties, final String prefix,
                final AtomicInteger identifier) {
            super.saveImpl(properties, prefix, identifier);
            saveMultiple(properties, prefix, HEADER_FILE_KEY, getHeaders(), identifier);
            if (prefix.charAt(0) == 'l')
                setSingle(properties, prefix, TYPE_KEY, STATIC_LIB_KEY);
        }

        /* package */void applyLocal(final StaticLib bean) {
            super.apply(bean);
            if (bean.getHeaders() == null) {
                bean.setHeaders(createHeaderDefaults(getHeaders()));
            }
        }

        /* package */void apply(final StaticLib bean, final SourceVisitor visitor) {
            applyLocal(bean);
            visitor.applyTo(bean.getHeaders());
        }

        @Override
        /* package */StaticLib create() {
            final StaticLib instance = new StaticLib();
            applyLocal(instance);
            return instance;
        }

    }

    public static class HeaderFileDefaults extends SourceDefaults {

        @Override
        /* package */HeaderFile create() {
            final HeaderFile instance = new HeaderFile();
            super.apply(instance);
            return instance;
        }

    }

    private class ApplyDefaults extends SourceVisitor {

        private <T> T[] nullIfEmpty(final T[] arr) {
            if (arr != null) {
                if (arr.length > 0) {
                    return arr;
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        @Override
        protected void visitSource(final Source instance) {
            instance.setPath(StringUtils.defaultIfEmpty(instance.getPath(), null));
            instance.setPattern(StringUtils.defaultIfEmpty(instance.getPattern(), null));
        }

        @Override
        protected void visitArchSource(final ArchSource instance) {
            instance.setArch(StringUtils.defaultIfEmpty(instance.getArch(), null));
            super.visitArchSource(instance);
        }

        @Override
        protected void visitDynamicLib(final DynamicLib instance) {
            getDynamicLibDefaults().apply(instance, this);
            instance.setHeaders(nullIfEmpty(instance.getHeaders()));
            instance.setImplibs(nullIfEmpty(instance.getImplibs()));
            super.visitDynamicLib(instance);
        }

        @Override
        protected void visitExecutable(final Executable instance) {
            getExecutableDefaults().apply(instance, this);
            instance.setHeaders(nullIfEmpty(instance.getHeaders()));
            instance.setLibraries(nullIfEmpty(instance.getLibraries()));
            super.visitExecutable(instance);
        }

        @Override
        protected void visitStaticLib(final StaticLib instance) {
            getStaticLibDefaults().apply(instance, this);
            instance.setHeaders(nullIfEmpty(instance.getHeaders()));
            super.visitStaticLib(instance);
        }

        @Override
        protected void visitHeaderFile(final HeaderFile instance) {
            getHeaderFileDefaults().apply(instance);
            super.visitHeaderFile(instance);
        }

    };

    /**
     * The default document identifier.
     */
    private final String _identifier;

    /**
     * The dynamic library defaults.
     */
    private final DynamicLibDefaults _dynamicLibDefaults = new DynamicLibDefaults();

    /**
     * The default dynamic libraries.
     */
    private DynamicLibDefaults[] _defaultDynamicLibs;

    /**
     * The executable defaults.
     */
    private final ExecutableDefaults _executableDefaults = new ExecutableDefaults();

    /**
     * The default executables.
     */
    private ExecutableDefaults[] _defaultExecutables;

    /**
     * The static library defaults.
     */
    private final StaticLibDefaults _staticLibDefaults = new StaticLibDefaults();

    /**
     * The default static libraries.
     */
    private StaticLibDefaults[] _defaultStaticLibs;

    /**
     * The header file defaults.
     */
    private final HeaderFileDefaults _headerFileDefaults = new HeaderFileDefaults();

    /**
     * The default header files.
     */
    private HeaderFileDefaults[] _defaultHeaderFiles;

    /**
     * The build command.
     */
    private String _buildCommand;

    /**
     * Creates a new instance.
     * <p>
     * Note that instances are typically obtained through a call to {@link #get} instead of created
     * directly.
     * 
     * @param identifier
     *          the default document identifier, not {@code null}
     * @param properties
     *          the values to set into this instance
     */
    public Defaults(final String identifier, final Properties properties) {
        _identifier = properties.getProperty(IDENTIFIER_KEY, Objects.requireNonNull(identifier));
        _dynamicLibDefaults.load(properties, DYNAMIC_LIB_KEY);
        String[] ids = getMultiple(getSingle(properties, DYNAMIC_LIB_KEY));
        if (ids != null) {
            final DynamicLibDefaults[] defaults = new DynamicLibDefaults[ids.length];
            for (int i = 0; i < ids.length; i++) {
                (defaults[i] = new DynamicLibDefaults()).load(properties, ids[i]);
            }
            setDefaultDynamicLibs(defaults);
        }
        _executableDefaults.load(properties, EXECUTABLE_KEY);
        ids = getMultiple(getSingle(properties, EXECUTABLE_KEY));
        if (ids != null) {
            final ExecutableDefaults[] defaults = new ExecutableDefaults[ids.length];
            for (int i = 0; i < ids.length; i++) {
                (defaults[i] = new ExecutableDefaults()).load(properties, ids[i]);
            }
            setDefaultExecutables(defaults);
        }
        _staticLibDefaults.load(properties, STATIC_LIB_KEY);
        ids = getMultiple(getSingle(properties, STATIC_LIB_KEY));
        if (ids != null) {
            final StaticLibDefaults[] defaults = new StaticLibDefaults[ids.length];
            for (int i = 0; i < ids.length; i++) {
                (defaults[i] = new StaticLibDefaults()).load(properties, ids[i]);
            }
            setDefaultStaticLibs(defaults);
        }
        _headerFileDefaults.load(properties, HEADER_FILE_KEY);
        ids = getMultiple(getSingle(properties, HEADER_FILE_KEY));
        if (ids != null) {
            final HeaderFileDefaults[] defaults = new HeaderFileDefaults[ids.length];
            for (int i = 0; i < ids.length; i++) {
                (defaults[i] = new HeaderFileDefaults()).load(properties, ids[i]);
            }
            setDefaultHeaderFiles(defaults);
        }
        setDefaultBuildCommand(getSingle(properties, BUILD_COMMAND_KEY));
    }

    /**
     * Returns a stock instance.
     * <p>
     * Note that the named configuration might be an alias of the actual configuration document. If
     * the named configuration does not exist then an empty default document will be returned.
     * 
     * @param identifier
     *          the named configuration, or {@code null} for none
     * @return a configuration document, never {@code null}
     */
    public static Defaults get(final String identifier) {
        if (identifier == null)
            return new Defaults("none", new Properties());
        final Properties properties = (new IOCallback<InputStream, Properties>(
                Defaults.class.getResourceAsStream(identifier + ".defaults")) {

            @Override
            protected Properties apply(final InputStream resource) throws IOException {
                if (resource == null) {
                    return null;
                } else {
                    final Properties p = new Properties();
                    p.load(resource);
                    return p;
                }
            }

        }).callWithAssertion();
        if (properties != null) {
            return new Defaults(identifier, properties);
        } else {
            return new Defaults("none", new Properties());
        }
    }

    /**
     * Updates any uninitialised values in the bean with defaults if they are available.
     * 
     * @param bean
     *          the object to update, not {@code null}
     */
    public void applyTo(final Source bean) {
        new ApplyDefaults().applyTo(bean);
    }

    /**
     * Applies {@link #applyTo(Source)} to all members of the array.
     * 
     * @param beans
     *          the objects to update, may be {@code null} but may not contain {@code null}
     */
    public void applyTo(final Source[] beans) {
        new ApplyDefaults().applyTo(beans);
    }

    /**
     * Returns the identifier of this default document. Note that this might not match the name that
     * was requested by a call to {@link #get}.
     * 
     * @return the document identifier, never {@code null}
     */
    public String getIdentifier() {
        return _identifier;
    }

    /**
     * Returns the default settings used for incompletely initialised {@link DynamicLib} entries.
     * 
     * @return the default values, never {@code null}
     */
    public DynamicLibDefaults getDynamicLibDefaults() {
        return _dynamicLibDefaults;
    }

    /**
     * Returns the settings used to create default {@link DynamicLib} entries.
     * 
     * @return the default settings, or {@code null} if none
     */
    public DynamicLibDefaults[] getDefaultDynamicLibs() {
        return ArrayUtils.clone(_defaultDynamicLibs);
    }

    /**
     * Sets the settings used to create default {@link DynamicLib} entries.
     * 
     * @param defaults
     *          the settings to use, or {@code null} for none
     */
    public void setDefaultDynamicLibs(final DynamicLibDefaults[] defaults) {
        _defaultDynamicLibs = ArrayUtils.clone(defaults);
    }

    /**
     * Creates, configures, and returns any default {@link DynamicLib} entries from the configuration.
     * 
     * @return the default instances, or {@code null} for none
     */
    public DynamicLib[] createDefaultDynamicLibs() {
        final DynamicLibDefaults[] defaults = getDefaultDynamicLibs();
        if (defaults == null)
            return null;
        final DynamicLib[] dynamicLibs = new DynamicLib[defaults.length];
        for (int i = 0; i < defaults.length; i++) {
            dynamicLibs[i] = defaults[i].create();
        }
        applyTo(dynamicLibs);
        return dynamicLibs;
    }

    /**
     * Returns the default settings used for incompletely initialised {@link Executable} entries.
     * 
     * @return the default values, never {@code null}
     */
    public ExecutableDefaults getExecutableDefaults() {
        return _executableDefaults;
    }

    /**
     * Returns the settings used to create default {@link Executable} entries.
     * 
     * @return the default settings, or {@code null} if none
     */
    public ExecutableDefaults[] getDefaultExecutables() {
        return ArrayUtils.clone(_defaultExecutables);
    }

    /**
     * Sets the settings used to create default {@link Executable} entries.
     * 
     * @param defaults
     *          the settings to use, or {@code null} for none
     */
    public void setDefaultExecutables(final ExecutableDefaults[] defaults) {
        _defaultExecutables = ArrayUtils.clone(defaults);
    }

    /**
     * Creates, configures, and returns any default {@link Executable} entries from the configuration.
     * 
     * @return the default instances, or {@code null} for none
     */
    public Executable[] createDefaultExecutables() {
        final ExecutableDefaults[] defaults = getDefaultExecutables();
        if (defaults == null)
            return null;
        final Executable[] executables = new Executable[defaults.length];
        for (int i = 0; i < defaults.length; i++) {
            executables[i] = defaults[i].create();
        }
        applyTo(executables);
        return executables;
    }

    /**
     * Returns the default settings used for incompletely initialised {@link StaticLib} entries.
     * 
     * @return the default values, never {@code null}
     */
    public StaticLibDefaults getStaticLibDefaults() {
        return _staticLibDefaults;
    }

    /**
     * Returns the settings used to create default {@link StaticLib} entries.
     * 
     * @return the default settings, or {@code null} if none
     */
    public StaticLibDefaults[] getDefaultStaticLibs() {
        return ArrayUtils.clone(_defaultStaticLibs);
    }

    /**
     * Sets the settings used to create default {@link StaticLib} entries.
     * 
     * @param defaults
     *          the settings to use, or {@code null} for none
     */
    public void setDefaultStaticLibs(final StaticLibDefaults[] defaults) {
        _defaultStaticLibs = ArrayUtils.clone(defaults);
    }

    /**
     * Creates, configures, and returns any default {@link StaticLib} entries from the configuration.
     * 
     * @return the default instances, or {@code null} for none
     */
    public StaticLib[] createDefaultStaticLibs() {
        final StaticLibDefaults[] defaults = getDefaultStaticLibs();
        if (defaults == null)
            return null;
        final StaticLib[] staticLibs = new StaticLib[defaults.length];
        for (int i = 0; i < defaults.length; i++) {
            staticLibs[i] = defaults[i].create();
        }
        applyTo(staticLibs);
        return staticLibs;
    }

    /**
     * Returns the default settings used for incompletely initialised {@link HeaderFile} entries.
     * 
     * @return the default values, never {@code null}
     */
    public HeaderFileDefaults getHeaderFileDefaults() {
        return _headerFileDefaults;
    }

    /**
     * Returns the settings used to create default {@link HeaderFile} entries.
     * 
     * @return the default settings, or {@code null} if none
     */
    public HeaderFileDefaults[] getDefaultHeaderFiles() {
        return ArrayUtils.clone(_defaultHeaderFiles);
    }

    /**
     * Sets the settings used to create default {@link HeaderFile} entries.
     * 
     * @param defaults
     *          the settings to use, or {@code null} for none
     */
    public void setDefaultHeaderFiles(final HeaderFileDefaults[] defaults) {
        _defaultHeaderFiles = ArrayUtils.clone(defaults);
    }

    /**
     * Creates, configures, and returns any default {@link HeaderFile} entries from the configuration.
     * 
     * @return the default instances, or {@code null} for none
     */
    public HeaderFile[] createDefaultHeaderFiles() {
        final HeaderFileDefaults[] defaults = getDefaultHeaderFiles();
        if (defaults == null)
            return null;
        final HeaderFile[] headerFiles = new HeaderFile[defaults.length];
        for (int i = 0; i < defaults.length; i++) {
            headerFiles[i] = defaults[i].create();
        }
        applyTo(headerFiles);
        return headerFiles;
    }

    /**
     * Returns the default build command.
     * 
     * @return the default value, or {@code null} if none
     */
    public String getDefaultBuildCommand() {
        return _buildCommand;
    }

    /**
     * Sets the default build command.
     * 
     * @param buildCommand
     *          the build command, or {@code null} for none
     */
    public void setDefaultBuildCommand(final String buildCommand) {
        _buildCommand = buildCommand;
    }

    private static String getSingle(final Properties properties, final String key) {
        final String value = properties.getProperty(key);
        if (value != null) {
            return value.replace('/', File.separatorChar);
        } else {
            return null;
        }
    }

    private static String getSingle(final Properties properties, final String prefix, final String key) {
        return getSingle(properties, prefix + "." + key);
    }

    private static void setSingle(final Properties properties, final String prefix, final String key,
            final String value) {
        if (value != null) {
            properties.setProperty(prefix + "." + key, value);
        }
    }

    private static String[] getMultiple(String valueString) {
        if (valueString == null)
            return null;
        valueString = valueString.trim();
        if (valueString.length() == 0)
            return ArrayUtils.EMPTY_STRING_ARRAY;
        return valueString.split(";");
    }

    private static String[] getMultiple(final Properties properties, final String prefix, final String key) {
        return getMultiple(getSingle(properties, prefix, key));
    }

    private static void saveMultiple(final Properties properties, final String prefix, final String key,
            final SourceDefaults[] sources, final AtomicInteger identifier) {
        if (sources != null) {
            final String[] values = new String[sources.length];
            for (int i = 0; i < sources.length; i++) {
                sources[i].saveImpl(properties, values[i] = new StringBuilder().append(key.charAt(0))
                        .append(identifier.incrementAndGet()).toString(), identifier);
            }
            setSingle(properties, prefix, key, StringUtils.join(values, ';'));
        }
    }

    private static HeaderFileDefaults[] loadHeaderDefaults(final Properties properties, final String prefix) {
        final String[] headers = getMultiple(properties, prefix, HEADER_FILE_KEY);
        if (headers == null)
            return null;
        final HeaderFileDefaults[] defaults = new HeaderFileDefaults[headers.length];
        for (int i = 0; i < headers.length; i++) {
            defaults[i] = new HeaderFileDefaults();
            defaults[i].load(properties, headers[i]);
        }
        return defaults;
    }

    private static HeaderFile[] createHeaderDefaults(final HeaderFileDefaults[] headerDefaults) {
        if (headerDefaults == null)
            return null;
        final HeaderFile[] headers = new HeaderFile[headerDefaults.length];
        for (int i = 0; i < headers.length; i++) {
            headers[i] = headerDefaults[i].create();
        }
        return headers;
    }

    /**
     * Writes the default document to a properties file, in the form that it can be restored from
     * later.
     * 
     * @param properties
     *          the buffer to receive the property entries, not {@code null}
     */
    public void save(final Properties properties) {
        properties.put(IDENTIFIER_KEY, getIdentifier());
        getDynamicLibDefaults().save(properties, DYNAMIC_LIB_KEY);
        getExecutableDefaults().save(properties, EXECUTABLE_KEY);
        getStaticLibDefaults().save(properties, STATIC_LIB_KEY);
        getHeaderFileDefaults().save(properties, HEADER_FILE_KEY);
        properties.put(BUILD_COMMAND_KEY, getDefaultBuildCommand());
    }

}