<commit_before>/*
 *
 * Copyright (c) 2000-2003 by <NAME>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.build.module;

import java.awt.event.ActionEvent;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.util.UUID;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

import VASSAL.build.module.metadata.AbstractMetaData;
import VASSAL.build.module.metadata.MetaDataFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import VASSAL.Info;
import VASSAL.build.AbstractBuildable;
import VASSAL.build.Buildable;
import VASSAL.build.Builder;
import VASSAL.build.GameModule;
import VASSAL.build.GpIdChecker;
import VASSAL.build.GpIdSupport;
import VASSAL.build.IllegalBuildException;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.metadata.ExtensionMetaData;
import VASSAL.build.widget.PieceSlot;
import VASSAL.command.Command;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.Resources;
import VASSAL.tools.ArchiveWriter;
import VASSAL.tools.DataArchive;
import VASSAL.tools.version.VersionUtils;

/**
 * An optional extension to a GameModule
 * Like a GameModule, it is built from scratch from a 'buildFile' in a DataArchive
 * The components described in the buildFile are appended to components in the base DataArchive
 */
public class ModuleExtension extends AbstractBuildable implements GameComponent, PluginsLoader.PluginElement, GpIdSupport {

  private static final Logger logger =
    LoggerFactory.getLogger(ModuleExtension.class);

  public static final String BASE_MODULE_NAME = "module"; //$NON-NLS-1$
  public static final String BASE_MODULE_VERSION = "moduleVersion"; //$NON-NLS-1$
  public static final String VERSION = "version"; //$NON-NLS-1$
  public static final String VASSAL_VERSION_CREATED = "vassalVersion"; //$NON-NLS-1$
  // NB The following key MUST sort before the other keys for universal modules to load
  public static final String UNIVERSAL = "anyModule"; //$NON-NLS-1$
  public static final String NEXT_PIECESLOT_ID = "nextPieceSlotId"; //$NON-NLS-1$
  public static final String EXTENSION_ID = "extensionId"; //$NON-NLS-1$
  public static final String DESCRIPTION = "description"; //NON-NLS

  private final DataArchive archive;
  private String version = "0.0"; //$NON-NLS-1$
  protected boolean universal = false;

  private String lastSave;
  private String vassalVersionCreated;
  private AbstractAction editAction;

  protected int nextGpId = 0;
  protected String extensionId = ""; //NON-NLS
  protected JTextField idDisplay;
  protected String description = ""; //NON-NLS

  public ModuleExtension(DataArchive archive) {
    this.archive = archive;
  }

  public String getVersion() {
    return version;
  }

  public DataArchive getDataArchive() {
    return archive;
  }

  public String getDescription() {
    return description;
  }

  public boolean getUniversal() {
    return universal;
  }

  public void build() {
    final AbstractMetaData data = MetaDataFactory.buildMetaData(archive.getArchive().getFile());

    if (!(data instanceof ExtensionMetaData)) {
      final String archName = archive.getArchive().getName();
      logger.error("Not an extension file {}", archName); //NON-NLS
      throw new ExtensionsLoader.LoadExtensionException("Not an extension file " + archName); //NON-NLS
    }

    GameModule.getGameModule().getDataArchive().addExtension(archive);

    // Record that we are currently building this Extension
    GameModule.getGameModule().setGpIdSupport(this);

    final String buildFile = VersionUtils.compareVersions(VersionUtils.truncateToMinorVersion(data.getVassalVersion()), "3.5") < 0 ? GameModule.BUILDFILE_OLD : GameModule.BUILDFILE; //NON-NLS

    try (BufferedInputStream in = new BufferedInputStream(archive.getInputStream(buildFile))) {
      try {
        final Document doc = Builder.createDocument(in);
        if (doc != null) {
          build(doc.getDocumentElement());
        }
      }
      catch (IOException e) {
        logger.error("Error while loading XML data from file {}", buildFile, e); //NON-NLS
        throw new ExtensionsLoader.LoadExtensionException(e);
      }
    }
    catch (FileNotFoundException | NoSuchFileException e) {
      logger.error("File {} not found in archive", buildFile, e); //NON-NLS
    }
    catch (IOException e) {
      logger.error("Error while reading file {} from archive", buildFile, e); //NON-NLS
    }

    GameModule.getGameModule().add(this);
    GameModule.getGameModule().getGameState().addGameComponent(this);

    if (archive instanceof ArchiveWriter) {
      lastSave = buildString();

      // Has an Extension Id been allocated yet?
      if (extensionId.length() == 0) {
        final String id = UUID.randomUUID().toString();
        extensionId = id.substring(id.length() - 3);
      }

      // Fix missing, duplicate and illegal GamePieceId's
      final GpIdChecker checker = new GpIdChecker(this);
      for (final Buildable b : getBuildables()) {
        checkGpIds(b, checker);
      }
      checker.fixErrors();
    }
  }

  protected void checkGpIds(Buildable b, GpIdChecker checker) {
    if (b instanceof PieceSlot) {<commit_msg>Don't immediately reject files which have no extension metadata when loading an extension, since new extensions also lack metadata.<commit_after>/*
 *
 * Copyright (c) 2000-2003 by <NAME>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.build.module;

import java.awt.event.ActionEvent;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.util.UUID;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

import VASSAL.build.module.metadata.AbstractMetaData;
import VASSAL.build.module.metadata.MetaDataFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import VASSAL.Info;
import VASSAL.build.AbstractBuildable;
import VASSAL.build.Buildable;
import VASSAL.build.Builder;
import VASSAL.build.GameModule;
import VASSAL.build.GpIdChecker;
import VASSAL.build.GpIdSupport;
import VASSAL.build.IllegalBuildException;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.metadata.ExtensionMetaData;
import VASSAL.build.widget.PieceSlot;
import VASSAL.command.Command;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.Resources;
import VASSAL.tools.ArchiveWriter;
import VASSAL.tools.DataArchive;
import VASSAL.tools.version.VersionUtils;

/**
 * An optional extension to a GameModule
 * Like a GameModule, it is built from scratch from a 'buildFile' in a DataArchive
 * The components described in the buildFile are appended to components in the base DataArchive
 */
public class ModuleExtension extends AbstractBuildable implements GameComponent, PluginsLoader.PluginElement, GpIdSupport {

  private static final Logger logger =
    LoggerFactory.getLogger(ModuleExtension.class);

  public static final String BASE_MODULE_NAME = "module"; //$NON-NLS-1$
  public static final String BASE_MODULE_VERSION = "moduleVersion"; //$NON-NLS-1$
  public static final String VERSION = "version"; //$NON-NLS-1$
  public static final String VASSAL_VERSION_CREATED = "vassalVersion"; //$NON-NLS-1$
  // NB The following key MUST sort before the other keys for universal modules to load
  public static final String UNIVERSAL = "anyModule"; //$NON-NLS-1$
  public static final String NEXT_PIECESLOT_ID = "nextPieceSlotId"; //$NON-NLS-1$
  public static final String EXTENSION_ID = "extensionId"; //$NON-NLS-1$
  public static final String DESCRIPTION = "description"; //NON-NLS

  private final DataArchive archive;
  private String version = "0.0"; //$NON-NLS-1$
  protected boolean universal = false;

  private String lastSave;
  private String vassalVersionCreated;
  private AbstractAction editAction;

  protected int nextGpId = 0;
  protected String extensionId = ""; //NON-NLS
  protected JTextField idDisplay;
  protected String description = ""; //NON-NLS

  public ModuleExtension(DataArchive archive) {
    this.archive = archive;
  }

  public String getVersion() {
    return version;
  }

  public DataArchive getDataArchive() {
    return archive;
  }

  public String getDescription() {
    return description;
  }

  public boolean getUniversal() {
    return universal;
  }

  public void build() {
    final AbstractMetaData data = MetaDataFactory.buildMetaData(archive.getArchive().getFile());

    GameModule.getGameModule().getDataArchive().addExtension(archive);

    // Record that we are currently building this Extension
    GameModule.getGameModule().setGpIdSupport(this);

    final String buildFile = data instanceof ExtensionMetaData &&
      VersionUtils.compareVersions(VersionUtils.truncateToMinorVersion(
        data.getVassalVersion()), "3.5" // NON-NLS
      ) < 0 ? GameModule.BUILDFILE_OLD : GameModule.BUILDFILE;

    try (BufferedInputStream in = new BufferedInputStream(archive.getInputStream(buildFile))) {
      try {
        final Document doc = Builder.createDocument(in);
        if (doc != null) {
          build(doc.getDocumentElement());
        }
      }
      catch (IOException e) {
        logger.error("Error while loading XML data from file {}", buildFile, e); //NON-NLS
        throw new ExtensionsLoader.LoadExtensionException(e);
      }
    }
    catch (FileNotFoundException | NoSuchFileException e) {
      // This is not necessarily an error; it's normal with new extensions
      logger.info("File {} not found in archive", buildFile); //NON-NLS
    }
    catch (IOException e) {
      logger.error("Error while reading file {} from archive", buildFile, e); //NON-NLS
    }

    GameModule.getGameModule().add(this);
    GameModule.getGameModule().getGameState().addGameComponent(this);

    if (archive instanceof ArchiveWriter) {
      lastSave = buildString();

      // Has an Extension Id been allocated yet?
      if (extensionId.length() == 0) {
        final String id = UUID.randomUUID().toString();
        extensionId = id.substring(id.length() - 3);
      }

      // Fix missing, duplicate and illegal GamePieceId's
      final GpIdChecker checker = new GpIdChecker(this);
      for (final Buildable b : getBuildables()) {
        checkGpIds(b, checker);
      }
      checker.fixErrors();
    }
  }

  protected void checkGpIds(Buildable b, GpIdChecker checker) {
    if (b instanceof PieceSlot) {
{'fraction_non_alphanumeric': 0.09557649313318428, 'fraction_numerical': 0.004631108272117534, 'mean_word_length': 4.052440500201694, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4635689', 'n_tokens_mistral': 3632, 'n_tokens_neox': 3553, 'n_words': 1060}