/** {@inheritDoc} */
  @Override
  protected boolean performExecution() throws MojoExecutionException, MojoFailureException {

    boolean updateStaleFileTimestamp = false;

    try {

      // Setup the Tool's execution environment
      ToolExecutionEnvironment environment = null;
      try {

        // Create a LocaleFacet if the user has configured an explicit Locale for the tool.
        final LocaleFacet localeFacet =
            locale == null ? null : LocaleFacet.createFor(locale, getLog());

        // Create the ToolExecutionEnvironment
        environment =
            new ToolExecutionEnvironment(
                getLog(),
                ThreadContextClassLoaderBuilder.createFor(this.getClass(), getLog())
                    .addPaths(getClasspath()),
                LoggingHandlerEnvironmentFacet.create(getLog(), getClass(), getEncoding(false)),
                localeFacet);

        // Add any extra configured EnvironmentFacets, as configured in the POM.
        if (extraFacets != null) {
          for (EnvironmentFacet current : extraFacets) {
            environment.add(current);
          }
        }

        // Setup the environment.
        environment.setup();

        // Compile the XJC arguments
        final String[] xjcArguments =
            getXjcArguments(environment.getClassPathAsArgument(), STANDARD_EPISODE_FILENAME);

        // Ensure that the outputDirectory exists, but only clear it if does not already
        FileSystemUtilities.createDirectory(getOutputDirectory(), clearOutputDir);

        // Do we need to re-create the episode file's parent directory.
        final boolean reCreateEpisodeFileParentDirectory = generateEpisode && clearOutputDir;
        if (reCreateEpisodeFileParentDirectory) {
          getEpisodeFile(STANDARD_EPISODE_FILENAME);
        }

        // Check the system properties.
        logSystemPropertiesAndBasedir();

        // Fire XJC
        if (XJC_COMPLETED_OK != Driver.run(xjcArguments, new XjcLogAdapter(getLog()))) {

          final StringBuilder errorMsgBuilder = new StringBuilder();
          errorMsgBuilder.append("\n+=================== [XJC Error]\n");
          errorMsgBuilder.append("|\n");

          final List<URL> sourceXSDs = getSources();
          for (int i = 0; i < sourceXSDs.size(); i++) {
            errorMsgBuilder
                .append("| " + i + ": ")
                .append(sourceXSDs.get(i).toString())
                .append("\n");
          }

          errorMsgBuilder.append("|\n");
          errorMsgBuilder.append("+=================== [End XJC Error]\n");
          throw new MojoExecutionException(errorMsgBuilder.toString());
        }

        // Indicate that the output directory was updated.
        getBuildContext().refresh(getOutputDirectory());

        // Update the modification timestamp of the staleFile.
        updateStaleFileTimestamp = true;

      } finally {

        if (environment != null) {
          environment.restore();
        }
      }

      // Add the generated source root to the project, enabling tooling and other plugins to see
      // them.
      addGeneratedSourcesToProjectSourceRoot();

      // Copy all source XSDs to the resulting artifact?
      if (xsdPathWithinArtifact != null) {

        final String buildOutputDirectory = getProject().getBuild().getOutputDirectory();
        final File targetXsdDirectory = new File(buildOutputDirectory, xsdPathWithinArtifact);
        FileUtils.forceMkdir(targetXsdDirectory);

        for (URL current : getSources()) {

          String fileName = null;
          if ("file".equalsIgnoreCase(current.getProtocol())) {
            fileName = new File(current.getPath()).getName();
          } else if ("jar".equalsIgnoreCase(current.getProtocol())) {

            // Typical JAR path
            // jar:file:/path/to/aJar.jar!/some/path/xsd/aResource.xsd
            final int bangIndex = current.toString().indexOf("!");
            if (bangIndex == -1) {
              throw new MojoExecutionException(
                  "Illegal JAR URL [" + current.toString() + "]: lacks a '!'");
            }

            final String internalPath = current.toString().substring(bangIndex + 1);
            fileName = new File(internalPath).getName();
          } else {
            throw new MojoExecutionException(
                "Could not extract FileName from URL [" + current + "]");
          }

          final File targetFile = new File(targetXsdDirectory, fileName);
          if (targetFile.exists()) {

            // TODO: Should we throw an exception here instead?
            getLog()
                .warn(
                    "File ["
                        + FileSystemUtilities.getCanonicalPath(targetFile)
                        + "] already exists. Not copying XSD file ["
                        + current.getPath()
                        + "] to it.");
          }
          IOUtil.copy(current.openStream(), new FileWriter(targetFile));
        }

        // Refresh the BuildContext
        getBuildContext().refresh(targetXsdDirectory);
      }
    } catch (MojoExecutionException e) {
      throw e;
    } catch (NoSchemasException e) {
      if (failOnNoSchemas) {
        throw new MojoExecutionException("", e);
      }
    } catch (Exception e) {
      throw new MojoExecutionException(e.getMessage(), e);
    }

    // All done.
    return updateStaleFileTimestamp;
  }
  private String[] getXjcArguments(final String classPath, final String episodeFileNameOrNull)
      throws MojoExecutionException, NoSchemasException {

    final ArgumentBuilder builder = new ArgumentBuilder();

    // Add all flags on the form '-flagName'
    builder.withFlag(true, sourceType.getXjcArgument());
    builder.withFlag(noPackageLevelAnnotations, "npa");
    builder.withFlag(laxSchemaValidation, "nv");
    builder.withFlag(verbose, "verbose");
    builder.withFlag(quiet, "quiet");
    builder.withFlag(enableIntrospection, "enableIntrospection");
    builder.withFlag(extension, "extension");
    builder.withFlag(readOnly, "readOnly");
    builder.withFlag(noGeneratedHeaderComments, "no-header");
    builder.withFlag(addGeneratedAnnotation, "mark-generated");

    // Add all arguments on the form '-argumentName argumentValue'
    // (i.e. in 2 separate elements of the returned String[])
    builder.withNamedArgument("httpproxy", getProxyString(settings.getActiveProxy()));
    builder.withNamedArgument("encoding", getEncoding(false));
    builder.withNamedArgument("p", packageName);
    builder.withNamedArgument("target", target);
    builder.withNamedArgument("d", getOutputDirectory().getAbsolutePath());
    builder.withNamedArgument("classpath", classPath);

    if (generateEpisode) {

      // We must use the -extension flag for the episode to work.
      if (!extension) {

        if (getLog().isInfoEnabled()) {
          getLog()
              .info(
                  "Adding 'extension' flag to XJC arguments, since the 'generateEpisode' argument is "
                      + "given. (XJCs 'episode' argument requires that the 'extension' argument is provided).");
        }
        builder.withFlag(true, "extension");
      }

      final File episodeFile = getEpisodeFile(episodeFileNameOrNull);
      builder.withNamedArgument("episode", FileSystemUtilities.getCanonicalPath(episodeFile));
    }
    if (catalog != null) {
      builder.withNamedArgument("catalog", FileSystemUtilities.getCanonicalPath(catalog));
    }

    if (arguments != null) {
      builder.withPreCompiledArguments(arguments);
    }

    for (File current : getSourceXJBs()) {

      // Shorten the argument?
      // final String strippedXjbPath = FileSystemUtilities.relativize(
      //         current.getAbsolutePath(), getProject().getBasedir());

      // Each XJB must be given as a separate argument.
      builder.withNamedArgument("-b", current.getAbsolutePath());
    }

    final List<URL> sourceXSDs = getSources();
    if (sourceXSDs.isEmpty()) {

      // If we have no XSDs, we are not going to be able to run XJC.
      getLog().warn("No XSD files found. Please check your plugin configuration.");
      throw new NoSchemasException();

    } else {

      final List<String> unwrappedSourceXSDs = new ArrayList<String>();
      for (URL current : sourceXSDs) {

        // Shorten the argument if possible.
        if ("file".equalsIgnoreCase(current.getProtocol())) {
          unwrappedSourceXSDs.add(
              FileSystemUtilities.relativize(
                  current.getPath(), new File(System.getProperty("user.dir"))));
        } else {
          unwrappedSourceXSDs.add(current.toString());
        }
      }

      builder.withPreCompiledArguments(unwrappedSourceXSDs);
    }

    // All done.
    return logAndReturnToolArguments(builder.build(), "XJC");
  }
  /** Java generation is required if any of the file products is outdated/stale. {@inheritDoc} */
  @Override
  protected boolean isReGenerationRequired() {

    //
    // Use the stale flag method to identify if we should re-generate the java source code from the
    // supplied
    // Xml Schema. Basically, we should regenerate the JAXB code if:
    //
    // a) The staleFile does not exist
    // b) The staleFile exists and is older than one of the sources (XSD or XJB files).
    //    "Older" is determined by comparing the modification timestamp of the staleFile and the
    // source files.
    //
    final File staleFile = getStaleFile();
    final String debugPrefix =
        "StaleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]";

    boolean stale = !staleFile.exists();
    if (stale) {
      getLog().debug(debugPrefix + " not found. JAXB (re-)generation required.");
    } else {

      final List<URL> sourceXSDs = getSources();
      final List<File> sourceXJBs = getSourceXJBs();

      if (getLog().isDebugEnabled()) {
        getLog()
            .debug(
                debugPrefix
                    + " found. Checking timestamps on source XSD and XJB "
                    + "files to determine if JAXB (re-)generation is required.");
      }

      final long staleFileLastModified = staleFile.lastModified();
      for (URL current : sourceXSDs) {

        final URLConnection sourceXsdConnection;
        try {
          sourceXsdConnection = current.openConnection();
          sourceXsdConnection.connect();
        } catch (Exception e) {

          // Can't determine if the staleFile is younger than this sourceXSD.
          // Re-generate to be on the safe side.
          stale = true;
          break;
        }

        try {
          if (sourceXsdConnection.getLastModified() > staleFileLastModified) {

            if (getLog().isDebugEnabled()) {
              getLog().debug(current.toString() + " is newer than the stale flag file.");
            }
            stale = true;
          }
        } finally {
          if (sourceXsdConnection instanceof HttpURLConnection) {
            ((HttpURLConnection) sourceXsdConnection).disconnect();
          }
        }
      }

      for (File current : sourceXJBs) {
        if (current.lastModified() > staleFileLastModified) {

          if (getLog().isDebugEnabled()) {
            getLog()
                .debug(
                    FileSystemUtilities.getCanonicalPath(current)
                        + " is newer than the stale flag file.");
          }

          stale = true;
          break;
        }
      }
    }

    // All done.
    return stale;
  }