@Parameters(separators = "=")
public class SignatureArgs {
    /* MAIN PARAMETERS */
    @Parameter(required = true,
            description = "Source filenames of the files to be signed")
    private List<String> source;

    @Parameter(names = {"-o", "--output"},
            description = "Destination path or file name for the signed document")
    private String output;

    @Parameter(names = {"-u", "--url"},
            description = "URL of the DSS Web Service")
    private String url;

    @Parameter(names = {"-log", "--log"},
               description = "Path for the output log file")
    private String log;
    /* END OF MAIN PARAMETERS */

    /* SIGNATURE FORMAT PARAMETERS */
    @Parameter(names = {"-f", "--format"}, required = true,
            description = "Signature format, depending on the file type can be \"CAdES\", \"XAdES\", \"PAdES\" " +
                    "(for PDF files only), \"ASiC-S\"")
    private String format;

    @Parameter(names = {"-l", "--level"}, required = true,
            description = "Signature level, depending on the format can be \"BES\", \"EPES\", \"LTV\", \"T\", \"C\", " +
                    "\"X\", \"XL\", \"A\"")
    private String level;

    @Parameter(names = {"-p", "--packaging"}, required = true,
            description = "Signature packaging, the way it is related with the content being signed. " +
                    "Can be \"ENVELOPED\", \"ENVELOPING\", \"DETACHED\"",
            converter = PackagingConverter.class)
    private SignaturePackaging packaging;

    @Parameter(names = {"-d", "--digest-algorithm"}, converter = DigestAlgorithmConverter.class,
            description = "The algorithm used for digesting. It can be \"SHA1\" (default), \"SHA256\", \"SHA512\"")
    private DigestAlgorithm digestAlgorithm;

    @Parameter(names = {"-w", "--wrap"}, hidden = true,
            description = "Wrap in ASiC-S, if existing, create a specially signed zip file for packaging the unaltered " +
                    "document and  its CAdES signature file in one container. " +
                    "Available only with \"-f=CAdES -p=DETACHED\"")
    private boolean wrapAsics;
    /* END OF SIGNATURE FORMAT PARAMETERS */

    /* SIGNATURE TOKEN PARAMETERS */
    @Parameter(names = {"-p11", "--pkcs11"}, arity = 2,
            description = "Specified if signature token is PKCS#11, evaluated with a previously installed card driver " +
                    "and the card encryption password")
    private List<String> pkcs11;

    @Parameter(names = {"-p12", "--pkcs12"}, arity = 2,
            description = "Specified if signature token is PKCS#12, evaluated with a the file containing the desired " +
                    "signature token and the file encryption password")
    private List<String> pkcs12;

    @Parameter(names = {"-ms", "--mscapi"},
            description = "Specified if signature token is MS CAPI. The list of digital IDs will be loaded from the " +
                    "user Personal Store handled by Windows")
    private boolean mscapi;

    @Parameter(names = {"-m", "--mocca"},
            description = "Specified if signature token is loaded via MOCCA. Can be one of \"SHA1\" or \"SHA256\"")
    private String mocca;

    @Parameter(names = {"-icn", "--issuercn"},
               description = "Regular expression to select keys based on the Issuer CN")
    private String issuerCN;
    /* END OF SIGNATURE TOKEN PARAMETERS */

    /* SIGNER CLAIMED ROLE */
    @Parameter(names = {"-sr", "--signer-role"},
            description = "The claimed role of the signer")
    private String claimedRole;
    /* END OF SIGNER CLAIMED ROLE */

    /* POLICY */
    /**
     * @deprecated Implicit policy does not exist as of DSS 3.0.2
     */
    @Parameter(names = {"-spi", "--signature-policy-implicit"},
            description = "If there is a signature policy to be implicitly inferred from the certificate/token")
    private boolean implicitPolicy;

    @Parameter(names = {"-spe", "--signature-policy-explicit"}, arity = 3,
            description = "Specify the policy with, in order, an OID, the base64 hash value of OID and the algorithm " +
                    "used to calculate the hash (only \"SHA1\" is allowed for now)")
    private List<String> explicitPolicy;
    /* END OF POLICY */

    @Parameter(names = {"-s", "--simulate"},
            description = "Loads the parameters and validates them, but does not start the actual signing process")
    private boolean simulate;

    /* GETTERS / SETTERS */
    public List<String> getSource() {
        return source;
    }

    public void setSource(List<String> source) {
        this.source = source;
    }

    public String getOutput() {
        return output;
    }

    public void setOutput(String output) {
        this.output = output;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getLog() {
        return log;
    }

    public void setLog(String log) {
        this.log = log;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    public SignaturePackaging getPackaging() {
        return packaging;
    }

    public void setPackaging(SignaturePackaging packaging) {
        this.packaging = packaging;
    }

    public DigestAlgorithm getDigestAlgorithm() {
        return digestAlgorithm;
    }

    public void setDigestAlgorithm(DigestAlgorithm digestAlgorithm) {
        this.digestAlgorithm = digestAlgorithm;
    }

    public boolean isWrapAsics() {
        return wrapAsics;
    }

    public void setWrapAsics(boolean wrapAsics) {
        this.wrapAsics = wrapAsics;
    }

    public List<String> getPkcs11() {
        return pkcs11;
    }

    public void setPkcs11(List<String> pkcs11) {
        this.pkcs11 = pkcs11;
    }

    public List<String> getPkcs12() {
        return pkcs12;
    }

    public void setPkcs12(List<String> pkcs12) {
        this.pkcs12 = pkcs12;
    }

    public boolean isMscapi() {
        return mscapi;
    }

    public void setMscapi(boolean mscapi) {
        this.mscapi = mscapi;
    }

    public String getMocca() {
        return mocca;
    }

    public void setMocca(String mocca) {
        this.mocca = mocca;
    }

    public String getIssuerCN() {
        return issuerCN;
    }

    public void setIssuerCN(String issuerCN) {
        this.issuerCN = issuerCN;
    }

    public String getClaimedRole() {
        return claimedRole;
    }

    public void setClaimedRole(String claimedRole) {
        this.claimedRole = claimedRole;
    }

    /**
     * @deprecated Implicit policy does not exist as of DSS 3.0.2
     */
    public boolean isImplicitPolicy() {
        return implicitPolicy;
    }

    /**
     * @deprecated Implicit policy does not exist as of DSS 3.0.2
     */
    public void setImplicitPolicy(boolean implicitPolicy) {
        this.implicitPolicy = implicitPolicy;
    }

    public List<String> getExplicitPolicy() {
        return explicitPolicy;
    }

    public void setExplicitPolicy(List<String> explicitPolicy) {
        this.explicitPolicy = explicitPolicy;
    }

    public boolean isSimulate() {
        return simulate;
    }

    public void setSimulate(boolean simulate) {
        this.simulate = simulate;
    }

    /* END OF GETTERS / SETTERS */
} public class SignatureBuilder implements IBuilder<File> {

    // All of the signature parameters
    private String source;
    private String target;
    private FormatBuilder formatBuilder;                // This is a builder
    private FormatBuilder.SignatureFormatLevelPackaging signatureFormatLevelPackaging;
    private TokenBuilder tokenBuilder;                  // This is a builder
    private TokenBuilder.SignatureToken token;
    private PolicyBuilder policyBuilder;                // This is a builder
    private PolicyBuilder.SignaturePolicy policy;
    private IDSSPrivateKeyChooser privateKeyChooser;
    private DigestAlgorithm digestAlgorithm;
    private String claimedRole;

    // This model will be built and signed on build()
    private SignatureCLIModel model;

    public SignatureBuilder() {
        model = new SignatureCLIModel();
    }

    public SignatureBuilder setSource(String file) {
        this.source = file;
        return this;
    }

    public SignatureBuilder setSource(File file) {
        this.source = file.getAbsolutePath();
        return this;
    }

    public SignatureBuilder setTarget(String file) {
        this.target = file;
        return this;
    }

    public SignatureBuilder setTarget(File file) {
        this.target = file.getAbsolutePath();
        return this;
    }

    public SignatureBuilder setFormatBuilder(FormatBuilder formatBuilder) {
        this.formatBuilder = formatBuilder;
        return this;
    }

    public SignatureBuilder setDigestAlgorithm(DigestAlgorithm digestAlgorithm) {
        if (digestAlgorithm == null) {
            digestAlgorithm = DigestAlgorithm.SHA1;
        }
        this.digestAlgorithm = digestAlgorithm;
        return this;
    }

    public SignatureBuilder setTokenBuilder(TokenBuilder tokenBuilder) {
        this.tokenBuilder = tokenBuilder;
        return this;
    }

    public SignatureBuilder setDSSPrivateKeyChooser(IDSSPrivateKeyChooser chooser) {
        this.privateKeyChooser = chooser;
        return this;
    }

    public SignatureBuilder setClaimedRole(String claimedRole) {
        this.claimedRole = claimedRole;
        return this;
    }

    public SignatureBuilder setPolicyBuilder(PolicyBuilder policyBuilder) {
        this.policyBuilder = policyBuilder;
        return this;
    }

    private File simulate() throws IOException, SignatureException, KeyStoreException, NoSuchAlgorithmException {
        return build(true);
    }

    public File build() throws IOException, SignatureException, KeyStoreException, NoSuchAlgorithmException {
        return build(false);
    }

    public File build(boolean simulate)
            throws SignatureSourceFileNotFoundException,
                   SignatureFormatMismatchException,
                   SignatureLevelMismatchException,
                   SignaturePackagingMismatchException,
                   SignatureServiceUrlException,
                   SignatureTokenException,
                   KeyStoreException,
                   BadPasswordException,
                   SignaturePolicyAlgorithmMismatchException,
                   SignaturePolicyLevelMismatch,
                   SignatureTargetFileException,
                   NoSuchAlgorithmException,
                   SignatureMoccaAlgorithmMismatchException,
                   SignatureMoccaUnavailabilityException {
        InnerSignatureBuilder innerBuilder = new InnerSignatureBuilder();

        // Set the source file
        innerBuilder.setSourceFile(source, model);
        innerBuilder.validateSourceFile(model);

        // Set the signature format, level and packaging
        innerBuilder.setSignatureFormatLevelPackaging(formatBuilder, model);
        // The format must match the file type
        innerBuilder.validateSignatureFormatFileTypeMatch(model);
        // The packaging must match the file type
        innerBuilder.validateSignaturePackagingFileTypeMatch(model);
        // The service URL must be set if some parameters are
        innerBuilder.validateServiceUrl(model);

        // Set the digest algorithm
        innerBuilder.setDigestAlgorithm(digestAlgorithm, model);

        // Set the token and token parameters
        try {
            innerBuilder.setToken(tokenBuilder, model);
        } catch (FileNotFoundException e) {
            throw new SignatureTokenException(e);
        }

        // Ensure that a key chooser has been selected
        innerBuilder.validatePrivateKeyChooser();
        // Open the connection to the selected keystore and set the chosen key
        innerBuilder.setPrivateKey(model);

        // Set the claimed role into the model
        innerBuilder.setClaimedRole(claimedRole, model);
        // Set the explicit policy
        innerBuilder.setPolicy(policyBuilder, model);
        // Validate explicit policy parameters
        innerBuilder.validatePolicy(model);

        // Set the target file
        innerBuilder.setTargetFile(target, model);

        File signedFile = null;
        if (!simulate) {
            signedFile = innerBuilder.signDocument(model);
        }

        return signedFile;
    }

    /**
     * Get the suggested target file name.
     * This method is a wrapper around the original {@link it.latraccia.dss.util.builder.SignatureBuilder#prepareTargetFileName(java.io.File,
     * eu.europa.ec.markt.dss.signature.SignaturePackaging, String)}.
     *
     * @return The suggested target file name
     */
    private String getSuggestedFileName() {
        return prepareTargetFileName(
                new File(source),
                SignaturePackaging.valueOf(signatureFormatLevelPackaging.getPackaging().getValue()),
                signatureFormatLevelPackaging.getFormat().getValue()).getName();
    }

    /**
     * Suggest the target file name.
     * Original code in {@link eu.europa.ec.markt.dss.applet.wizard.signature.SaveStep#prepareTargetFileName(java.io.File,
     * eu.europa.ec.markt.dss.signature.SignaturePackaging, String)}
     *
     * @param file               The selected file to sign
     * @param signaturePackaging The selected packaging
     * @param signatureFormat    The complete signature format (e.g. "CAdES")
     * @return The suggested target File
     */
    private File prepareTargetFileName(final File file,
                                       final SignaturePackaging signaturePackaging,
                                       final String signatureFormat) {

        final File parentDir = file.getParentFile();
        final String originalName = StringUtils.substringBeforeLast(file.getName(), ".");
        final String originalExtension = "." + StringUtils.substringAfterLast(file.getName(), ".");
        final String format = signatureFormat.toUpperCase();

        if ((SignaturePackaging.ENVELOPING == signaturePackaging || SignaturePackaging.DETACHED == signaturePackaging) && format.startsWith("XADES")) {
            return new File(parentDir, originalName + "-signed" + ".xml");
        }

        if (format.startsWith("CADES") && !originalExtension.toLowerCase().equals(".p7m")) {
            return new File(parentDir, originalName + originalExtension + ".p7m");
        }

        if (format.startsWith("ASIC")) {
            return new File(parentDir, originalName + originalExtension + ".asics");
        }

        return new File(parentDir, originalName + "-signed" + originalExtension);

    }

    public interface IDSSPrivateKeyChooser {
        public DSSPrivateKeyEntry getDSSPrivateKey(List<DSSPrivateKeyEntry> keys);
    }

    protected class InnerSignatureBuilder {
        /**
         * Set the source file to be signed.
         *
         * @param model         The signature model
         * @throws java.io.FileNotFoundException Thrown if the input file hasn't been found or it is not valid
         */
        protected void setSourceFile(String sourceFile, SignatureCLIModel model) throws SignatureSourceFileNotFoundException {
            // Search in resources, then absolute path
            try {
                String foundFile = Util.getFileInAbsolutePathOrResources(sourceFile);
                model.setSelectedFile(new File(foundFile));
            } catch (FileNotFoundException exception) {
                throw new SignatureSourceFileNotFoundException(exception);
            }
        }

        /**
         * Validate a source file.
         * Validation from {@link eu.europa.ec.markt.dss.applet.wizard.signature.FileStep#isValid()}
         *
         * @param model The signature model
         * @throws SignatureSourceFileNotFoundException Thrown if the file does not exists or the path is not a file
         */
        protected void validateSourceFile(SignatureCLIModel model) throws SignatureSourceFileNotFoundException {
            if (!model.getSelectedFile().exists() || !model.getSelectedFile().isFile()) {
                throw new SignatureSourceFileNotFoundException(
                        new FileNotFoundException("The source file was not found or it is not a valid file."));
            }
        }

        /**
         * Set the signature format, level and packaging.
         * Some of the code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.SignatureStep#init()}.
         *
         * @param model The signature model
         *
         * @throws SignatureLevelMismatchException  Thrown if there is a mismatch between the file extension and the
         *                                          signature level
         *
         * @throws SignaturePackagingMismatchException  Thrown if there is a mismatch between the file extension and the
         *                                              signature packaging
         * @throws SignatureFormatMismatchException     Thrown if there is a mismatch between the file extension and the
         *                                              signature format
         */
        protected void setSignatureFormatLevelPackaging(FormatBuilder formatBuilder, SignatureCLIModel model)
                throws SignatureLevelMismatchException, SignaturePackagingMismatchException, SignatureFormatMismatchException {

            // Build the object
            signatureFormatLevelPackaging = formatBuilder.build();

            // Set the signature format
            String format = signatureFormatLevelPackaging.getFormat().getValue();
            String level = signatureFormatLevelPackaging.getLevel().getValue();
            String packaging = signatureFormatLevelPackaging.getPackaging().getValue();

            if (format != null) {
                model.setFormat(format);
                if (packaging != null) {
                    model.setPackaging(packaging);
                    if (StringUtils.isNotEmpty(level)) {
                        model.setSimpleLevel(level);
                    }
                }
            }

            // Set the service URL and validate it
            model.setServiceURL(signatureFormatLevelPackaging.getServiceUrl());
        }

        /**
         * Validate the signature format according to the signature format.
         *
         * @param model The signature model
         * @throws SignatureFormatMismatchException Thrown if there is a format mismatch
         */
        protected void validateSignatureFormatFileTypeMatch(SignatureCLIModel model) throws SignatureFormatMismatchException {
            // If the file is not a PDF, the PAdES cannot be selected
            if (model.getFileType() != FileType.PDF) {
                if (!AssertHelper.stringMustNotEqual("signature level", model.getFormat(), "PAdES")) {
                    throw new SignatureFormatMismatchException();
                }
            }
        }

        /**
         * Validate the signature packaging according to the signature format.
         *
         * @param model The signature model
         * @throws SignaturePackagingMismatchException Thrown if there is a packaging mismatch
         */
        protected void validateSignaturePackagingFileTypeMatch(SignatureCLIModel model) throws SignaturePackagingMismatchException {
            // Validate the packaging
            String signatureFormat = model.getFormat();
            SignaturePackaging signaturePackaging = model.getPackaging();

            if (signatureFormat.equals(it.latraccia.dss.util.entity.format.SignatureFormat.XAdES)
                    && (model.getFileType() != FileType.XML)
                    && (signaturePackaging.equals(signaturePackaging))) {
                throw new SignaturePackagingMismatchException();
            }
        }

        /**
         * Validate the service URL by requiring it if PAdES is selected as format or
         * the level is any different then BES, EPES.
         *
         * @param model The signature model
         * @throws SignatureServiceUrlException Thrown if the service URL is required but was not specified
         */
        protected void validateServiceUrl(SignatureCLIModel model) throws SignatureServiceUrlException {
            String serviceUrl = model.getServiceURL();
            if (Util.isNullOrEmpty(serviceUrl)) {
                // If the format is PAdES or the level is not one of the accepted
                if (model.getFormat().startsWith(it.latraccia.dss.util.entity.format.SignatureFormat.PAdES.getValue())
                        || !AssertHelper.isStringInList(
                        model.getSimpleLevel(),
                        new String[]{
                                SignaturePAdESLevel.BES.getValue(),
                                SignaturePAdESLevel.EPES.getValue()
                        })) {
                    throw new SignatureServiceUrlException();
                }
            }

        }

        /**
         * Set the custom digest algorithm.
         *
         * @param model The signature model
         */
        protected void setDigestAlgorithm(DigestAlgorithm digestAlgorithm, SignatureCLIModel model) {
            // Set the digest algorithm, to SHA1 if null
            if (digestAlgorithm != null) {
                model.setDigestAlgorithm(digestAlgorithm.getValue());
            } else {
                model.setDigestAlgorithm(eu.europa.ec.markt.dss.DigestAlgorithm.SHA1);
            }
        }

        /**
         * Set the token (certificate) type; PKCS11, PKCS12, MOCCA, MSAPI.
         * Set some token-specific parameters for getting the certificate:
         * - PKCS11: library file and password
         * - PKCS12: certificate file and password
         * - MOCCA: signature algorithm
         * <p/>
         * Some of this code has been taken from:
         * - {@link eu.europa.ec.markt.dss.applet.wizard.signature.SignatureStep#getNextStep()}
         * - {@link eu.europa.ec.markt.dss.applet.wizard.signature.PKCS11Step#isValid()}
         * - {@link eu.europa.ec.markt.dss.applet.wizard.signature.PKCS12Step#isValid()}
         *
         * @param model The signature model
         */
        protected void setToken(TokenBuilder tokenBuilder, SignatureCLIModel model) throws
                                                                                    FileNotFoundException,
                                                                                    SignatureMoccaAlgorithmMismatchException,
                                                                                    SignatureMoccaUnavailabilityException {
            // Set the token file, password, do some pre-validation
            token = tokenBuilder.build();

            SignatureTokenType tokenType = SignatureTokenType.valueOf(token.getTokenType().getValue());
            // Set the token type
            model.setTokenType(tokenType);

            // If the algorithm is MOCCA, set the model MOCCA algorithm
            if (token.getTokenType().equals(it.latraccia.dss.util.entity.token.SignatureTokenType.MOCCA)) {
                model.setMoccaSignatureAlgorithm(token.getMoccaAlgorithm().getValue());
            }

            switch (tokenType) {
                case PKCS11:
                    // Set the PKCS11 library file
                    model.setPkcs11File(token.getTokenAsset());
                    // Set the card encryption password
                    model.setPkcs11Password(token.getPassword());
                    break;
                case PKCS12:
                    // Set the PKCS12 file
                    model.setPkcs12File(token.getTokenAsset());
                    // Set the file encryption password
                    model.setPkcs12Password(token.getPassword());
                    break;
                case MOCCA:
                    model.setMoccaSignatureAlgorithm(token.getMoccaAlgorithm().getValue().toLowerCase());
                    break;
            }
        }

        /**
         * Ensure that the private key chooser has been set.
         *
         * @throws java.lang.NullPointerException if the chooser was not set
         */
        protected void validatePrivateKeyChooser() throws NullPointerException {
            if (privateKeyChooser == null) {
                throw new NullPointerException("No private key chooser has been set, can't proceed!");
            }
        }

        /**
         * Open the connection to the keystore, read all of the keys and selects one.
         * This method executes code taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.CertificateStep#init()}.
         * If no exception is thrown, this method guarantees that a private key has been selected.
         *
         * @param model The signature model
         * @throws java.security.KeyStoreException If there are errors accessing the keystore
         */
        protected void setPrivateKey(SignatureCLIModel model) throws KeyStoreException, BadPasswordException {
            // Create the connection to the keystore provider
            SignatureTokenConnection connection = model.createTokenConnection();
            // Get the keys
            List<DSSPrivateKeyEntry> entries = connection.getKeys();

            // Get the selected key to be used
            DSSPrivateKeyEntry key = privateKeyChooser.getDSSPrivateKey(entries);

            // Set the key to be used for the signing process
            model.setSelectedPrivateKey(key);
        }

        /**
         * Set the claimed role of the user.
         *
         * @param claimedRole The claimed role
         * @param model       The signature model
         */
        protected void setClaimedRole(String claimedRole, SignatureCLIModel model) {
            model.setClaimedRole(claimedRole);
        }

        /**
         * Set the user personal data.
         * Part of the code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.PersonalDataStep#init()}.
         *
         * @param policyBuilder The {@link it.latraccia.dss.util.builder.policy.PolicyBuilder} that will be built to get
         *                      the set values
         * @param model         The signature model
         * @throws SignatureException Thrown if any of the policy parameters are invalid
         */
        protected void setPolicy(PolicyBuilder policyBuilder, SignatureCLIModel model) throws SignaturePolicyAlgorithmMismatchException {

            // Default to no policy
            model.setSignaturePolicyCheck(false);

            policy = policyBuilder.build();

            // If the explicit policy data are valid
            if (policy.getOid() != null
                    && policy.getOid().trim().length() > 0
                    && policy.getPolicyAlgorithm() != null) {
                // Set the explicit policy
                model.setSignaturePolicyId(policy.getOid());
                model.setSignaturePolicyAlgo(policy.getPolicyAlgorithm().getValue());
                model.setSignaturePolicyValue(policy.getHash());

                model.setSignaturePolicyCheck(true);
            }
        }

        /**
         * Validate the policy by checking that the signature level is different than BES.
         * Some of this code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.PersonalDataStep#init()}.
         *
         * @param model The signature model
         * @throws SignaturePolicyLevelMismatch Thrown if the signature level doesn't match
         */
        protected void validatePolicy(SignatureCLIModel model) throws SignaturePolicyLevelMismatch {
            boolean levelBES = model.getSimpleLevel().equalsIgnoreCase("BES");
            if (model.isSignaturePolicyCheck() && levelBES) {
                throw new SignaturePolicyLevelMismatch();
            }
        }

        /**
         * Sets the output file path, starting from the original file name and path, the signature format, level and
         * packaging.
         * Some of the called code has been taken from
         * {@link eu.europa.ec.markt.dss.applet.wizard.signature.SaveStep#prepareTargetFileName(java.io.File,
         * eu.europa.ec.markt.dss.signature.SignaturePackaging, String)}.
         *
         * @param targetFile The target file to set
         * @param model      The signature model
         */
        protected void setTargetFile(String targetFile, SignatureCLIModel model) {
            // The output path if and as requested by the user
            String destination = targetFile;

            File outputDir;
            String outputFile;
            File destinationFile;

            // If the destination wasn't set
            if (Util.isNullOrEmpty(destination)) {
                // Use the parent dir of the original file
                outputDir = model.getSelectedFile().getParentFile();
                // Use the suggested file name
                outputFile = getSuggestedFileName();
                destinationFile = new File(outputDir, outputFile);
            } else {
                // Tries to understand what destination is
                File outFileOrDir = new File(destination);

                // If the user requested a directory
                if (outFileOrDir.isDirectory()) {
                    // Store the file in the outFileOrDir directory
                    outputDir = outFileOrDir;
                    // Use the suggested file name
                    outputFile = getSuggestedFileName();
                    destinationFile = new File(outputDir, outputFile);
                } else {
                    // If the user explicitly requested a file name and directory, proceed regularly
                    destinationFile = outFileOrDir;
                }
            }

            // Set the user-selected destination path or file
            model.setTargetFile(destinationFile);
        }

        /**
         * Sign the document after all of the parameters have been loaded into the {@link SignatureCLIModel}.
         * Some of the code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.SignatureWizardController#signDocument()}.
         *
         * @param model The signature model
         * @return The {@link java.io.FileOutputStream} of the signed document
         *
         * @throws it.latraccia.dss.util.exception.SignatureTargetFileException Thrown if there is an input/output
         *                                                                      exception while/reading a file or a
         *                                                                      network stream
         *
         * @throws java.security.NoSuchAlgorithmException Thrown if a specified algorithm isn't available
         */
        private File signDocument(SignatureCLIModel model)
                throws SignatureTargetFileException, NoSuchAlgorithmException {
            final File fileToSign = model.getSelectedFile();
            final SignatureTokenConnection tokenConnection = model.getTokenConnection();
            final DSSPrivateKeyEntry privateKey = model.getSelectedPrivateKey();

            final SignatureParameters parameters = new SignatureParameters();
            parameters.setSigningDate(new Date());
            parameters.setPrivateKeyEntry(privateKey);
            parameters.setSignatureFormat(SignatureFormat.valueByName(model.getLevel()));
            parameters.setSignaturePackaging(model.getPackaging());

            if (model.isClaimedCheck()) {
                parameters.setClaimedSignerRole(model.getClaimedRole());
            }

            String moccaSignatureAlgorithm = model.getMoccaSignatureAlgorithm();
            // If a MOCCA algorithm is selected, ensure it is SHA256
            if ("SHA256".equalsIgnoreCase(moccaSignatureAlgorithm)) {
                parameters.setDigestAlgorithm(eu.europa.ec.markt.dss.DigestAlgorithm.SHA256);
            } else {
                // Set the custom digest algorithm
                parameters.setDigestAlgorithm(model.getDigestAlgorithm());
            }

            if (model.isSignaturePolicyCheck()) {
                final byte[] hashValue = Base64.decodeBase64(model.getSignaturePolicyValue());
                final SignatureParameters.Policy policy = parameters.getSignaturePolicy();
                policy.setHashValue(hashValue);
                policy.setId(model.getSignaturePolicyId());
                eu.europa.ec.markt.dss.DigestAlgorithm digestAlgo = eu.europa.ec.markt.dss.DigestAlgorithm.forName(model.getSignaturePolicyAlgo());
                policy.setDigestAlgo(digestAlgo);
            }

            FileOutputStream output;
            try {
                output = new FileOutputStream(model.getTargetFile());
            } catch (FileNotFoundException e) {
                throw new SignatureTargetFileException(e);
            }

            final TrustedListCertificateVerifier certificateVerifier = (TrustedListCertificateVerifier) model.getCertificateVerifier(
                    model.getCRLSource(), model.getOSCPSource(), model.getCertificateSource(model.getCertificateSource102853())
            );

            DSSDocument signedDocument;
            try {
                signedDocument = SigningUtils
                        .signDocument(fileToSign, parameters, model.getTSPSource(), certificateVerifier, tokenConnection, privateKey);
                IOUtils.copy(signedDocument.openStream(), output);
                output.close();
            } catch (DSSException dssException) {
                throw new SignatureTargetFileException(dssException);
            } catch (IOException ioException) {
                throw new SignatureTargetFileException(ioException);
            }

            System.out.println("SUCCESS.");
            System.out.println(String.format("Signed file: %s", model.getTargetFile().getAbsoluteFile()));
            return fileToSign;
        }
    }
} public class CAdESFormatBuilder extends FormatBuilder<SignatureCAdESFormat> {

    // FORMAT

    public CAdESFormatBuilder() {
        setFormat(SignatureFormat.CAdES);
    }

    // LEVEL

    public CAdESFormatBuilder BES() {
        setLevel(SignatureCAdESLevel.BES);
        return this;
    }

    public CAdESFormatBuilder EPES() {
        setLevel(SignatureCAdESLevel.EPES);
        return this;
    }

    public CAdESFormatBuilder T(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureCAdESLevel.T);
        setServiceUrl(serviceUrl);
        return this;
    }

    public CAdESFormatBuilder C(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureCAdESLevel.C);
        setServiceUrl(serviceUrl);
        return this;
    }

    public CAdESFormatBuilder X(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureCAdESLevel.BES);
        setServiceUrl(serviceUrl);
        return this;
    }

    public CAdESFormatBuilder XL(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureCAdESLevel.EPES);
        setServiceUrl(serviceUrl);
        return this;
    }

    public CAdESFormatBuilder A(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureCAdESLevel.A);
        setServiceUrl(serviceUrl);
        return this;
    }

    // PACKAGING

    public CAdESFormatBuilder enveloping() {
        setPackaging(SignatureCAdESPackaging.ENVELOPING);
        return this;
    }

    public CAdESFormatBuilder detached() {
        setPackaging(SignatureCAdESPackaging.DETACHED);
        return this;
    }
} public abstract class FormatBuilder<T
        extends SignatureFormat>
        implements IBuilder<FormatBuilder.SignatureFormatLevelPackaging> {

    protected SignatureFormat format;
    protected SignatureLevel level;
    protected SignaturePackaging packaging;
    protected String serviceUrl;

    public SignatureFormatLevelPackaging build() throws SignatureFormatMismatchException, SignaturePackagingMismatchException, SignatureLevelMismatchException {
        SignatureFormatLevelPackaging signatureFormatLevelPackaging = new SignatureFormatLevelPackaging();
        signatureFormatLevelPackaging.setFormat(format);
        signatureFormatLevelPackaging.setLevel(level);
        signatureFormatLevelPackaging.setPackaging(packaging);
        signatureFormatLevelPackaging.setServiceUrl(serviceUrl);

        validateSignatureFormat();
        validateSignatureLevel();
        validateSignaturePackaging();

        return signatureFormatLevelPackaging;
    }

    protected FormatBuilder setFormat(SignatureFormat format) {
        this.format = format;
        return this;
    }

    public FormatBuilder setLevel(SignatureLevel level) {
        this.level = level;
        return this;
    }

    public FormatBuilder setPackaging(SignaturePackaging packaging) {
        this.packaging = packaging;
        return this;
    }

    public FormatBuilder setServiceUrl(String serviceUrl) {
        this.serviceUrl = serviceUrl;
        return this;
    }

    public SignatureFormat getFormat() {
        return format;
    }

    public SignatureLevel getLevel() {
        return level;
    }

    public SignaturePackaging getPackaging() {
        return packaging;
    }

    public String getServiceUrl() {
        return serviceUrl;
    }

    /**
     * Validate signature format, level and packaging.
     *
     * @throws it.latraccia.dss.util.exception.SignatureFormatMismatchException If there is a mismatch of any kind
     *                                                                              between format, level and packaging
     */
    protected void validateSignatureFormat() throws SignatureFormatMismatchException {
        String signatureFormat = getFormat().getValue();

        // Validate the simple format
        String[] allowedFormats = new String[]{"PAdES", "CAdES", "XAdES", "ASiC-S"};
        if (!AssertHelper.stringMustBeInList(
                "signature format",
                signatureFormat,
                allowedFormats)) {
            throw new SignatureFormatMismatchException();
        }
    }

    /**
     * Validate the signature level accordingly to the format.
     *
     * @throws it.latraccia.dss.util.exception.SignatureLevelMismatchException Thrown if there is a level mismatch
     *                                                                             with the selected format
     */
    protected void validateSignatureLevel() throws SignatureLevelMismatchException {
        // Validate the level
        String signatureFormat = getFormat().getValue();
        String signatureLevel = getLevel().getValue();

        // The map of allowed levels for each simple format
        HashMap<String, String[]> allowedLevelsMap = new HashMap<String, String[]>();
        allowedLevelsMap.put("PAdES", new String[]{"BES", "EPES", "LTV"});
        allowedLevelsMap.put("CAdES", new String[]{"BES", "EPES", "T", "C", "X", "XL", "A"});
        allowedLevelsMap.put("XAdES", new String[]{"BES", "EPES", "T", "C", "X", "XL", "A"});
        allowedLevelsMap.put("ASiC-S", new String[]{"BES", "EPES", "T"});

        // Validate the level for the format set
        if (!AssertHelper.stringMustBeInList(
                "signature level for " + signatureFormat,
                signatureLevel,
                allowedLevelsMap.get(signatureFormat))) {
            throw new SignatureLevelMismatchException();
        }
    }

    /**
     * Validate the signature packaging according to the signature format.
     *
     * @throws it.latraccia.dss.util.exception.SignaturePackagingMismatchException Thrown if there is a packaging
     *                                                                                 mismatch
     */
    protected void validateSignaturePackaging() throws SignaturePackagingMismatchException {
        // Validate the packaging
        String signatureFormat = getFormat().getValue();
        eu.europa.ec.markt.dss.signature.SignaturePackaging signaturePackaging =
                eu.europa.ec.markt.dss.signature.SignaturePackaging.valueOf(getPackaging().getValue());

        // The map of allowed packaging for each simple format
        HashMap<String, eu.europa.ec.markt.dss.signature.SignaturePackaging[]> allowedPackagingMap = new HashMap<String, eu.europa.ec.markt.dss.signature.SignaturePackaging[]>();
        allowedPackagingMap.put("PAdES", new eu.europa.ec.markt.dss.signature.SignaturePackaging[]{eu.europa.ec.markt.dss.signature.SignaturePackaging.ENVELOPED});
        allowedPackagingMap.put("CAdES", new eu.europa.ec.markt.dss.signature.SignaturePackaging[]{eu.europa.ec.markt.dss.signature.SignaturePackaging.ENVELOPING, eu.europa.ec.markt.dss.signature.SignaturePackaging.DETACHED});
        allowedPackagingMap.put("ASiC-S", new eu.europa.ec.markt.dss.signature.SignaturePackaging[]{eu.europa.ec.markt.dss.signature.SignaturePackaging.DETACHED});
        allowedPackagingMap.put("XAdES", new eu.europa.ec.markt.dss.signature.SignaturePackaging[]{eu.europa.ec.markt.dss.signature.SignaturePackaging.ENVELOPING, eu.europa.ec.markt.dss.signature.SignaturePackaging.DETACHED, eu.europa.ec.markt.dss.signature.SignaturePackaging.ENVELOPED});

        // Validate the level for the format set
        if (!AssertHelper.packageMustBeInList(
                "packaging for " + signatureFormat,
                signaturePackaging,
                allowedPackagingMap.get(signatureFormat))) {
            throw new SignaturePackagingMismatchException();
        }
    }

    public class SignatureFormatLevelPackaging {
        protected SignatureFormat format;
        protected SignatureLevel level;
        protected SignaturePackaging packaging;
        protected String serviceUrl;

        public SignatureFormat getFormat() {
            return format;
        }

        public void setFormat(SignatureFormat format) {
            this.format = format;
        }

        public SignatureLevel getLevel() {
            return level;
        }

        public void setLevel(SignatureLevel level) {
            this.level = level;
        }

        public SignaturePackaging getPackaging() {
            return packaging;
        }

        public void setPackaging(SignaturePackaging packaging) {
            this.packaging = packaging;
        }

        public String getServiceUrl() {
            return serviceUrl;
        }

        public void setServiceUrl(String serviceUrl) {
            this.serviceUrl = serviceUrl;
        }
    }
} public class PAdESFormatBuilder extends FormatBuilder<SignaturePAdESFormat> {

    // FORMAT

    public PAdESFormatBuilder() {
        setFormat(SignatureFormat.PAdES);
    }

    // LEVEL

    public PAdESFormatBuilder BES(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignaturePAdESLevel.BES);
        setServiceUrl(serviceUrl);
        return this;
    }

    public PAdESFormatBuilder EPES(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignaturePAdESLevel.EPES);
        setServiceUrl(serviceUrl);
        return this;
    }

    public PAdESFormatBuilder LTV(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignaturePAdESLevel.LTV);
        setServiceUrl(serviceUrl);
        return this;
    }

    // PACKAGING

    public PAdESFormatBuilder enveloped() {
        setPackaging(SignaturePAdESPackaging.ENVELOPED);
        return this;
    }
} public class XAdESFormatBuilder extends FormatBuilder<SignatureXAdESFormat> {

    // FORMAT

    public XAdESFormatBuilder() {
        setFormat(SignatureFormat.XAdES);
    }

    // LEVEL

    public XAdESFormatBuilder BES() {
        setLevel(SignatureXAdESLevel.BES);
        return this;
    }

    public XAdESFormatBuilder EPES() {
        setLevel(SignatureXAdESLevel.EPES);
        return this;
    }

    public XAdESFormatBuilder T(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureXAdESLevel.T);
        setServiceUrl(serviceUrl);
        return this;
    }

    public XAdESFormatBuilder C(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureXAdESLevel.C);
        setServiceUrl(serviceUrl);
        return this;
    }

    public XAdESFormatBuilder X(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureXAdESLevel.BES);
        setServiceUrl(serviceUrl);
        return this;
    }

    public XAdESFormatBuilder XL(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureXAdESLevel.EPES);
        setServiceUrl(serviceUrl);
        return this;
    }

    public XAdESFormatBuilder A(String serviceUrl) throws SignatureServiceUrlException {
        setLevel(SignatureXAdESLevel.A);
        setServiceUrl(serviceUrl);
        return this;
    }

    // PACKAGING

    public XAdESFormatBuilder enveloping() {
        setPackaging(SignatureXAdESPackaging.ENVELOPING);
        return this;
    }

    public XAdESFormatBuilder enveloped() {
        setPackaging(SignatureXAdESPackaging.ENVELOPED);
        return this;
    }

    public XAdESFormatBuilder detached() {
        setPackaging(SignatureXAdESPackaging.DETACHED);
        return this;
    }

} public class PolicyBuilder implements IBuilder<PolicyBuilder.SignaturePolicy> {
    protected PolicyAlgorithm policyAlgorithm;
    protected String oid;
    protected String hash;

    public PolicyAlgorithm getPolicyAlgorithm() {
        return policyAlgorithm;
    }

    public PolicyBuilder setPolicyAlgorithm(PolicyAlgorithm policyAlgorithm) {
        this.policyAlgorithm = policyAlgorithm;
        return this;
    }

    public String getOid() {
        return oid;
    }

    public PolicyBuilder setOid(String oid) {
        this.oid = oid;
        return this;
    }

    public String getHash() {
        return hash;
    }

    public PolicyBuilder setHash(String hash) {
        this.hash = hash;
        return this;
    }

    @Override
    public SignaturePolicy build() throws SignaturePolicyAlgorithmMismatchException {
        SignaturePolicy signaturePolicy = new SignaturePolicy();
        signaturePolicy.setPolicyAlgorithm(policyAlgorithm);
        signaturePolicy.setOid(oid);
        signaturePolicy.setHash(hash);

        validate();

        return signaturePolicy;
    }

    private void validate() throws SignaturePolicyAlgorithmMismatchException {
        if (!Util.isNullOrEmpty(getPolicyAlgorithm().getValue())) {
            if (!AssertHelper.stringMustBeInList(
                    "explicit policy algorithm",
                    getPolicyAlgorithm().getValue(),
                    new String[]{"SHA1"})) {
                throw new SignaturePolicyAlgorithmMismatchException();
            }
        }
    }

    public class SignaturePolicy {
        protected PolicyAlgorithm policyAlgorithm;
        protected String oid;
        protected String hash;

        public PolicyAlgorithm getPolicyAlgorithm() {
            return policyAlgorithm;
        }

        public void setPolicyAlgorithm(PolicyAlgorithm policyAlgorithm) {
            this.policyAlgorithm = policyAlgorithm;
        }

        public String getOid() {
            return oid;
        }

        public void setOid(String oid) {
            this.oid = oid;
        }

        public String getHash() {
            return hash;
        }

        public void setHash(String hash) {
            this.hash = hash;
        }
    }
} public class MoccaAlgorithm extends GeneralEnum {
    public static MoccaAlgorithm SHA1 = new MoccaAlgorithm("SHA1");

    public MoccaAlgorithm(String value) {
        super(value);
    }
} public class PolicyAlgorithm extends GeneralEnum {
    public static PolicyAlgorithm SHA1 = new PolicyAlgorithm("SHA1");

    public PolicyAlgorithm(String value) {
        super(value);
    }
} public class SignatureCAdESLevel extends SignatureLevel {
    public static SignatureCAdESLevel BES = new SignatureCAdESLevel(SignatureLevel.BES);
    public static SignatureCAdESLevel EPES = new SignatureCAdESLevel(SignatureLevel.EPES);
    public static SignatureCAdESLevel T = new SignatureCAdESLevel(SignatureLevel.T);
    public static SignatureCAdESLevel C = new SignatureCAdESLevel(SignatureLevel.C);
    public static SignatureCAdESLevel X = new SignatureCAdESLevel(SignatureLevel.X);
    public static SignatureCAdESLevel XL = new SignatureCAdESLevel(SignatureLevel.XL);
    public static SignatureCAdESLevel A = new SignatureCAdESLevel(SignatureLevel.A);

    public SignatureCAdESLevel(String value) {
        super(value);
    }
} public class SignaturePAdESLevel extends SignatureLevel {
    public static SignaturePAdESLevel BES = new SignaturePAdESLevel(SignatureLevel.BES);
    public static SignaturePAdESLevel EPES = new SignaturePAdESLevel(SignatureLevel.EPES);
    public static SignaturePAdESLevel LTV = new SignaturePAdESLevel(SignatureLevel.LTV);

    public SignaturePAdESLevel(String value) {
        super(value);
    }
} public class SignatureXAdESLevel extends SignatureLevel {
    public static SignatureXAdESLevel BES = new SignatureXAdESLevel(SignatureLevel.BES);
    public static SignatureXAdESLevel EPES = new SignatureXAdESLevel(SignatureLevel.EPES);
    public static SignatureXAdESLevel T = new SignatureXAdESLevel(SignatureLevel.T);
    public static SignatureXAdESLevel C = new SignatureXAdESLevel(SignatureLevel.C);
    public static SignatureXAdESLevel X = new SignatureXAdESLevel(SignatureLevel.X);
    public static SignatureXAdESLevel XL = new SignatureXAdESLevel(SignatureLevel.XL);
    public static SignatureXAdESLevel A = new SignatureXAdESLevel(SignatureLevel.A);

    public SignatureXAdESLevel(String value) {
        super(value);
    }
} public class SignatureCAdESPackaging extends SignaturePackaging {
    public static SignatureCAdESPackaging ENVELOPING = new SignatureCAdESPackaging(SignaturePackaging.ENVELOPING);
    public static SignatureCAdESPackaging DETACHED = new SignatureCAdESPackaging(SignaturePackaging.DETACHED);

    public SignatureCAdESPackaging(String value) {
        super(value);
    }
} public class SignaturePAdESPackaging extends SignaturePackaging {
    public static SignaturePAdESPackaging ENVELOPED = new SignaturePAdESPackaging(SignaturePackaging.ENVELOPED);

    public SignaturePAdESPackaging(String value) {
        super(value);
    }
} public class SignatureXAdESPackaging extends SignaturePackaging {
    public static SignatureXAdESPackaging ENVELOPING = new SignatureXAdESPackaging(SignaturePackaging.ENVELOPING);
    public static SignatureXAdESPackaging ENVELOPED = new SignatureXAdESPackaging(SignaturePackaging.ENVELOPED);
    public static SignatureXAdESPackaging DETACHED = new SignatureXAdESPackaging(SignaturePackaging.DETACHED);

    public SignatureXAdESPackaging(String value) {
        super(value);
    }
} public class ExplicitSignaturePolicyModel {
    private String OID;
    private String hash;
    private String hashAlgo;

    public ExplicitSignaturePolicyModel(List<String> list) {
        if (list != null) {
            setOID(list.get(0));
            setHash(list.get(1));
            setHashAlgo(list.get(2));
        }
    }

    public String getOID() {
        return OID;
    }

    public void setOID(String OID) {
        this.OID = OID;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public String getHashAlgo() {
        return hashAlgo;
    }

    public void setHashAlgo(String hashAlgo) {
        this.hashAlgo = hashAlgo;
    }
} public class PKCSModel {
    private String file;
    private String password;

    public PKCSModel(List<String> list) {
        setFile(list.get(0));
        setPassword(list.get(1));
    }

    public PKCSModel(String file, String password) {
        this.file = file;
        this.password = password;
    }

    public String getFile() {
        return file;
    }

    public void setFile(String file) {
        this.file = file;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
} public class Util {

    /**
     * Check if a {@link String} is null or empty.
     * @param string The {@link String} to check
     * @return true if the {@link String} is null or empty, false otherwise
     */
    public static boolean isNullOrEmpty(String string) {
        return (string == null || string.isEmpty());
    }

    /**
     * Read a line from the standard input.
     * @return The read {@link String}
     * @throws IOException if a read error occurred
     */
    public static String readln() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        return br.readLine();
    }

    /**
     * Get the subject DN {@link String} from a certificate.
     * @param cert A {@link X509Certificate} to read the subject DN from
     * @return The {@link String} representing the subject for the {@link X509Certificate}
     */
    public static String getSubjectDN(X509Certificate cert) {
        String subjectDN = cert.getSubjectDN().getName();
        int dnStartIndex = subjectDN.indexOf("CN=") + 3;
        if (dnStartIndex > 0 && subjectDN.indexOf(",", dnStartIndex) > 0) {
            subjectDN = subjectDN.substring(dnStartIndex, subjectDN.indexOf(",", dnStartIndex)) + " (SN:"
                    + cert.getSerialNumber() + ")";
        }
        return subjectDN;
    }

    /**
     * Get the issuer CN {@link String} from a certificate.
     * @param cert A {@link X509Certificate} to read the issuer CN from
     * @return The {@link String} representing the issuer CN for the {@link X509Certificate}
     */
    public static String getIssuerCN(X509Certificate cert) {
        String issuerCN = cert.getIssuerDN().getName();
        int cnStartIndex = issuerCN.indexOf("CN=") + 3;
        if (cnStartIndex > 0 && issuerCN.indexOf(",", cnStartIndex) > 0) {
            issuerCN = issuerCN.substring(cnStartIndex, issuerCN.indexOf(",", cnStartIndex));
        }
        return issuerCN;
    }

    /**
     * Read an {@link Integer} from the standard input.
     * @return The read {@link Integer}
     * @throws IOException if a read error has occurred
     * @throws NumberFormatException if a parsing error has occurred
     */
    public static int readInt() throws IOException, NumberFormatException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String input = bufferedReader.readLine();
        return Integer.parseInt(input);
    }

    /**
     * Read an {@link Integer} from the standard input, and ask again until the parsing succeeds.
     * @param maxAttempts Number of maximum attempts for reading, unhandled if less than 1.
     * @param lBound Lower bound accepted for the integer number (included).
     * @param uBound Upper bound accepted for the integer number (included).
     * @return The read {@link Integer}
     */
    @SuppressWarnings("ConstantConditions")
    public static int readInt(int maxAttempts, int lBound, int uBound) {
        boolean parsed;
        int number = -1;
        int attempt = 0;
        do {
            if (attempt > 0) {
                System.out.println("Not a valid selection. Please try again.");
            }
            try {
                attempt++;
                number = readInt();
                parsed = true;
            } catch (IOException e) {
                parsed = false;
            } catch (NumberFormatException e) {
                parsed = false;
            }                                                   // Repeat if...
        } while (!parsed ||                                     // - the number is not parsed
                (attempt < maxAttempts && maxAttempts >= 0) ||  // - maxAttempts not reached and must be accounted for
                (number < lBound) || (number > uBound));        // - the upper or lower bound is not respected

        return number;
    }

    /**
     * Get the file extension from a file name.
     * @param fileName The file name
     * @return The last extension for the file name
     */
    public static String getFileExtension(String fileName) {
        String extension = null;
        int i2 = fileName.lastIndexOf(".");
        if (i2 > 0) {
            extension = fileName.substring(i2);
        }
        return extension;
    }

    /**
     * Get the file name without the extension.
     * @param fileName The file name
     * @return The file name without the final extension
     */
    public static String getFileNameWithoutExtension(String fileName) {
        int i2 = fileName.lastIndexOf(".");
        String simpleFileName = null;
        if (i2 > 0) {
            // Get the original name without the extension
            simpleFileName = fileName.substring(0, i2);
        }
        return simpleFileName;
    }

    /**
     * Search a file in the resources, and return its absolute path if it exists,
     * otherwise return a null {@link String}.
     *
     * @param fileName The name of the file in the resources
     * @return The path {@link String} of the file, if any, or null
     */
    public static String getFileInResources(String fileName) {
        URL fileURL = Util.class.getClassLoader().getResource(fileName);
        String filePath = null;
        if (fileURL != null) {
            filePath = fileURL.getFile();
        }
        return filePath;
    }

    /**
     * Search for a file in an absolute path, and always returns a {@link String},
     * even if the file does not exist.
     *
     * @param fileName The file absolute path
     * @return The path {@link String} of the file
     */
    public static String getFileInAbsolutePath(String fileName) {
        File file = new File(fileName);
        return file.getAbsolutePath();
    }

    /**
     * Search for a given file path:
     *  - handling the fileName as an absolute path
     *  - fallback in resources, returns it if it exists
     *
     * @param fileName File name or path of a file in resources or an absolute path
     * @return  The file absolute path as a {@link String}, if it was found anywhere
     *          in the absolute path or in the resources
     */
    public static String getFileInAbsolutePathOrResources(String fileName) throws FileNotFoundException {
        File file = new File(fileName);
        String filePath = file.getAbsolutePath();
        if (!file.exists()) {
             filePath = getFileInResources(fileName);
        }
        return filePath;
    }
}
import com.beust.jcommander.JCommander;
import eu.europa.ec.markt.dss.exception.BadPasswordException;
import eu.europa.ec.markt.dss.signature.SignaturePackaging;
import eu.europa.ec.markt.dss.signature.token.DSSPrivateKeyEntry;
import it.latraccia.dss.util.cli.argument.SignatureArgs;
import it.latraccia.dss.util.builder.SignatureBuilder;
import it.latraccia.dss.util.builder.format.CAdESFormatBuilder;
import it.latraccia.dss.util.builder.format.FormatBuilder;
import it.latraccia.dss.util.builder.format.PAdESFormatBuilder;
import it.latraccia.dss.util.builder.format.XAdESFormatBuilder;
import it.latraccia.dss.util.builder.policy.PolicyBuilder;
import it.latraccia.dss.util.builder.token.*;
import it.latraccia.dss.util.entity.MoccaAlgorithm;
import it.latraccia.dss.util.entity.PolicyAlgorithm;
import it.latraccia.dss.util.entity.level.SignatureCAdESLevel;
import it.latraccia.dss.util.entity.level.SignaturePAdESLevel;
import it.latraccia.dss.util.entity.level.SignatureXAdESLevel;
import it.latraccia.dss.util.entity.packaging.SignatureCAdESPackaging;
import it.latraccia.dss.util.entity.packaging.SignaturePAdESPackaging;
import it.latraccia.dss.util.entity.packaging.SignatureXAdESPackaging;
import it.latraccia.dss.util.exception.*;
import it.latraccia.dss.util.model.ExplicitSignaturePolicyModel;
import it.latraccia.dss.util.model.PKCSModel;
import it.latraccia.dss.util.util.Util;
import java.io.*;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
/*
 * SD-DSS-Util, a Utility Library and a Command Line Interface for SD-DSS.
 * Copyright (C) 2013 La Traccia http://www.latraccia.it/en/
 * Developed by Francesco Pontillo
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see [http://www.gnu.org/licenses/].
 */

package it.latraccia.dss.util.cli;



public class SignCLI {
    private static Class[] possibleExceptions = new Class[] {
            SignatureSourceFileNotFoundException.class,
            SignatureFormatMismatchException.class,
            SignatureLevelMismatchException.class,
            SignaturePackagingMismatchException.class,
            SignatureServiceUrlException.class,
            SignaturePolicyAlgorithmMismatchException.class,
            SignaturePolicyLevelMismatch.class,
            SignatureTokenException.class,
            KeyStoreException.class,
            BadPasswordException.class,
            NoSuchAlgorithmException.class,
            SignatureTargetFileException.class
    };

    public static void main(String[] args) throws Exception {
        // Read and parse the arguments
        SignatureArgs signatureArgs = new SignatureArgs();
        new JCommander(signatureArgs, args);
        Exception returnedException = null;

        try {
            execute(signatureArgs);
        } catch (Exception e) {
            e.printStackTrace();
            returnedException = e;
        }
        writeLogFile(signatureArgs, returnedException);

        if (returnedException != null) {
            throw returnedException;
        }
    }

    private static File execute(SignatureArgs signatureArgs) throws NoSuchAlgorithmException, SignaturePolicyLevelMismatch,
                                                                    SignatureFormatMismatchException, SignatureServiceUrlException,
                                                                    SignatureLevelMismatchException, SignatureSourceFileNotFoundException,
                                                                    SignaturePackagingMismatchException, SignaturePolicyAlgorithmMismatchException,
                                                                    KeyStoreException, SignatureTargetFileException, SignatureTokenException,
                                                                    SignatureMoccaUnavailabilityException, SignatureMoccaAlgorithmMismatchException {
        // Create the signature wizard builder
        SignatureBuilder signatureBuilder = new SignatureBuilder();

        // Set the parameters inside the SignatureBuilder, step by step
        setSourceFile(signatureArgs, signatureBuilder);
        setSignatureFormatLevelPackagingServiceUrl(signatureArgs, signatureBuilder);
        setDigestAlgorithm(signatureArgs, signatureBuilder);
        setToken(signatureArgs, signatureBuilder);
        setPrivateKeyChooser(signatureArgs, signatureBuilder);
        setClaimedRole(signatureArgs, signatureBuilder);
        setPolicy(signatureArgs, signatureBuilder);
        setOutputFile(signatureArgs, signatureBuilder);

        return signatureBuilder.build(signatureArgs.isSimulate());
    }

    /**
     * Set the source file to be signed.
     *
     * @param signatureArgs The input arguments
     * @param builder       The signature builder
     */
    protected static void setSourceFile(SignatureArgs signatureArgs, SignatureBuilder builder) {
        // Set the FileDocument from the user source path
        String sourceFile = signatureArgs.getSource().get(0);
        builder.setSource(sourceFile);
    }

    /**
     * Set the signature format, level and packaging.
     *
     * @param signatureArgs The input arguments
     * @param builder       The signature builder
     */
    protected static void setSignatureFormatLevelPackagingServiceUrl(SignatureArgs signatureArgs, SignatureBuilder builder) {
        // Set the signature format
        String format = signatureArgs.getFormat();
        String level = signatureArgs.getLevel();
        SignaturePackaging packaging = signatureArgs.getPackaging();

        if (format != null) {
            FormatBuilder formatBuilder = null;
            if (format.equalsIgnoreCase(it.latraccia.dss.util.entity.format.SignatureFormat.CAdES_NAME)) {
                formatBuilder = new CAdESFormatBuilder();
                formatBuilder.setLevel(new SignatureCAdESLevel(level.toUpperCase()));
                formatBuilder.setPackaging(new SignatureCAdESPackaging(packaging.name()));
            } else if (format.equalsIgnoreCase(it.latraccia.dss.util.entity.format.SignatureFormat.PAdES_NAME)) {
                formatBuilder = new PAdESFormatBuilder();
                formatBuilder.setLevel(new SignaturePAdESLevel(level.toUpperCase()));
                formatBuilder.setPackaging(new SignaturePAdESPackaging(packaging.name()));
            } else if (format.equalsIgnoreCase(it.latraccia.dss.util.entity.format.SignatureFormat.XAdES_NAME)) {
                formatBuilder = new XAdESFormatBuilder();
                formatBuilder.setLevel(new SignatureXAdESLevel(level.toUpperCase()));
                formatBuilder.setPackaging(new SignatureXAdESPackaging(packaging.name()));
            }
            if (formatBuilder != null) {
                formatBuilder.setServiceUrl(signatureArgs.getUrl());
            }
            builder.setFormatBuilder(formatBuilder);
        }
    }

    /**
     * Set the custom digest algorithm.
     *
     * @param signatureArgs The input arguments
     * @param builder       The signature builder
     */
    protected static void setDigestAlgorithm(SignatureArgs signatureArgs, SignatureBuilder builder) {
        if (signatureArgs.getDigestAlgorithm() != null) {
            builder.setDigestAlgorithm(
                    new it.latraccia.dss.util.entity.DigestAlgorithm(signatureArgs.getDigestAlgorithm().name()));
        }
    }

    /**
     * Set the key token.
     *
     * @param signatureArgs The input arguments
     * @param builder       The signature builder
     */
    protected static void setToken(SignatureArgs signatureArgs, SignatureBuilder builder) {
        // Get the parameters
        List<String> pkcs11 = signatureArgs.getPkcs11();
        List<String> pkcs12 = signatureArgs.getPkcs12();
        boolean mscapi = signatureArgs.isMscapi();
        String mocca = signatureArgs.getMocca();

        TokenBuilder tokenBuilder = null;

        // Set the token type
        if (pkcs11 != null && pkcs11.size() == 2) {
            PKCSModel pkcs11Model = new PKCSModel(pkcs11);
            tokenBuilder = new PKCS11TokenBuilder()
                    .setFile(pkcs11Model.getFile())
                    .setPassword(pkcs11Model.getPassword());
        } else if (pkcs12 != null && pkcs12.size() == 2) {
            PKCSModel pkcs12Model = new PKCSModel(pkcs12);
            tokenBuilder = new PKCS12TokenBuilder()
                    .setFile(pkcs12Model.getFile())
                    .setPassword(pkcs12Model.getPassword());
        } else if (mscapi) {
            tokenBuilder = new MSCAPITokenBuilder();
        } else if (!Util.isNullOrEmpty(mocca)) {
            tokenBuilder = new MoccaTokenBuilder()
                    .setMoccaAlgorithm(new MoccaAlgorithm(signatureArgs.getMocca()));
        }

        builder.setTokenBuilder(tokenBuilder);
    }

    /**
     * Set the default chooser object for the private key.
     *
     * @param builder The signature builder
     */
    protected static void setPrivateKeyChooser(SignatureArgs signatureArgs, SignatureBuilder builder) {
        if (Util.isNullOrEmpty(signatureArgs.getIssuerCN())) {
            builder.setDSSPrivateKeyChooser(new InputDSSPrivateKeyChooser());
        } else {
            builder.setDSSPrivateKeyChooser(new RegexDSSPrivateKeyChooser(signatureArgs.getIssuerCN()));
        }
    }

    /**
     * Set the claimed role of the user.
     *
     * @param signatureArgs The input arguments
     * @param builder       The signature builder
     */
    protected static void setClaimedRole(SignatureArgs signatureArgs, SignatureBuilder builder) {
        builder.setClaimedRole(signatureArgs.getClaimedRole());
    }

    /**
     * Set the user personal data.
     *
     * @param signatureArgs The input arguments
     * @param builder       The signature builder
     */
    protected static void setPolicy(SignatureArgs signatureArgs, SignatureBuilder builder) {
        // Convert explicit policy data
        ExplicitSignaturePolicyModel explicitPolicy =