Added a dialog to generate boilerplate metadata for new mods
This commit is contained in:
parent
3baf16db6f
commit
80c6bd812b
3 changed files with 359 additions and 0 deletions
181
src/main/java/net/vhati/modmanager/ui/CreateModDialog.java
Normal file
181
src/main/java/net/vhati/modmanager/ui/CreateModDialog.java
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package net.vhati.modmanager.ui;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.Box;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.ScrollPaneConstants;
|
||||||
|
import javax.swing.event.AncestorEvent;
|
||||||
|
import javax.swing.event.AncestorListener;
|
||||||
|
|
||||||
|
import net.vhati.modmanager.ui.FieldEditorPanel;
|
||||||
|
import net.vhati.modmanager.ui.FieldEditorPanel.ContentType;
|
||||||
|
import net.vhati.modmanager.ui.RegexDocument;
|
||||||
|
import net.vhati.modmanager.xml.JDOMModMetadataWriter;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
public class CreateModDialog extends JDialog implements ActionListener {
|
||||||
|
|
||||||
|
private static final Logger log = LogManager.getLogger( CreateModDialog.class );
|
||||||
|
|
||||||
|
protected static final String DIR_NAME = "Directory Name";
|
||||||
|
protected static final String TITLE = "Title";
|
||||||
|
protected static final String URL = "Thread URL";
|
||||||
|
protected static final String AUTHOR = "Author";
|
||||||
|
protected static final String VERSION = "Version";
|
||||||
|
protected static final String DESC = "Description";
|
||||||
|
|
||||||
|
protected FieldEditorPanel editorPanel;
|
||||||
|
protected JButton applyBtn;
|
||||||
|
|
||||||
|
protected File modsDir;
|
||||||
|
|
||||||
|
|
||||||
|
public CreateModDialog( Frame owner, File modsDir ) {
|
||||||
|
super( owner, "New Mod" );
|
||||||
|
this.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
|
||||||
|
|
||||||
|
this.modsDir = modsDir;
|
||||||
|
|
||||||
|
editorPanel = new FieldEditorPanel( false );
|
||||||
|
editorPanel.setBorder( BorderFactory.createEmptyBorder( 10, 10, 0, 10 ) );
|
||||||
|
editorPanel.setNameWidth( 100 );
|
||||||
|
editorPanel.setValueWidth( 350 );
|
||||||
|
|
||||||
|
editorPanel.addRow( DIR_NAME, ContentType.STRING );
|
||||||
|
editorPanel.getString( DIR_NAME ).setDocument( new RegexDocument( "[^\\/:;*?<>|^\"]*" ) );
|
||||||
|
editorPanel.addTextRow( String.format( "The name of a directory to create in the %s/ folder.", modsDir.getName() ) );
|
||||||
|
editorPanel.addSeparatorRow();
|
||||||
|
editorPanel.addRow( TITLE, ContentType.STRING );
|
||||||
|
editorPanel.addTextRow( "The title of this mod." );
|
||||||
|
editorPanel.addSeparatorRow();
|
||||||
|
editorPanel.addRow( URL, ContentType.STRING );
|
||||||
|
editorPanel.addTextRow( "This mod's thread on the forum." );
|
||||||
|
editorPanel.addSeparatorRow();
|
||||||
|
editorPanel.addRow( AUTHOR, ContentType.STRING );
|
||||||
|
editorPanel.addTextRow( "Your forum user name." );
|
||||||
|
editorPanel.addSeparatorRow();
|
||||||
|
editorPanel.addRow( VERSION, ContentType.STRING );
|
||||||
|
editorPanel.addTextRow( "The revision/variant of this release, preferably at least a number." );
|
||||||
|
editorPanel.addSeparatorRow();
|
||||||
|
editorPanel.addRow( DESC, ContentType.TEXT_AREA );
|
||||||
|
editorPanel.getTextArea( DESC ).setRows( 15 );
|
||||||
|
editorPanel.addTextRow( "Summary of gameplay effects; flavor; features; concerns about compatibility, preferred order, requirements; replaced ship slot; etc." );
|
||||||
|
|
||||||
|
JPanel ctrlPanel = new JPanel();
|
||||||
|
ctrlPanel.setLayout( new BoxLayout( ctrlPanel, BoxLayout.X_AXIS ) );
|
||||||
|
ctrlPanel.setBorder( BorderFactory.createEmptyBorder( 10, 0, 10, 0 ) );
|
||||||
|
ctrlPanel.add( Box.createHorizontalGlue() );
|
||||||
|
applyBtn = new JButton( "Generate Mod" );
|
||||||
|
applyBtn.addActionListener( this );
|
||||||
|
ctrlPanel.add( applyBtn );
|
||||||
|
ctrlPanel.add( Box.createHorizontalGlue() );
|
||||||
|
|
||||||
|
final JScrollPane editorScroll = new JScrollPane( editorPanel );
|
||||||
|
editorScroll.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS );
|
||||||
|
editorScroll.getVerticalScrollBar().setUnitIncrement( 10 );
|
||||||
|
int vbarWidth = editorScroll.getVerticalScrollBar().getPreferredSize().width;
|
||||||
|
editorScroll.setPreferredSize( new Dimension( editorPanel.getPreferredSize().width+vbarWidth+5, 400 ) );
|
||||||
|
|
||||||
|
JPanel contentPane = new JPanel( new BorderLayout() );
|
||||||
|
contentPane.add( editorScroll, BorderLayout.CENTER );
|
||||||
|
contentPane.add( ctrlPanel, BorderLayout.SOUTH );
|
||||||
|
this.setContentPane( contentPane );
|
||||||
|
this.pack();
|
||||||
|
this.setMinimumSize( new Dimension( 250, 250 ) );
|
||||||
|
|
||||||
|
|
||||||
|
editorScroll.addAncestorListener(new AncestorListener() {
|
||||||
|
@Override
|
||||||
|
public void ancestorAdded( AncestorEvent e ) {
|
||||||
|
editorScroll.getViewport().setViewPosition( new Point( 0, 0 ) );
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void ancestorMoved( AncestorEvent e ) {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void ancestorRemoved( AncestorEvent e ) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed( ActionEvent e ) {
|
||||||
|
Object source = e.getSource();
|
||||||
|
|
||||||
|
if ( source == applyBtn ) {
|
||||||
|
String dirName = editorPanel.getString( DIR_NAME ).getText().trim();
|
||||||
|
String modTitle = editorPanel.getString( TITLE ).getText().trim();
|
||||||
|
String modURL = editorPanel.getString( URL ).getText().trim();
|
||||||
|
String modAuthor = editorPanel.getString( AUTHOR ).getText().trim();
|
||||||
|
String modVersion = editorPanel.getString( VERSION ).getText().trim();
|
||||||
|
String modDesc = editorPanel.getTextArea( DESC ).getText().trim();
|
||||||
|
|
||||||
|
if ( dirName.length() == 0 ) {
|
||||||
|
JOptionPane.showMessageDialog( CreateModDialog.this, "No directory name was given.", "Nothing to do", JOptionPane.WARNING_MESSAGE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File genDir = new File( modsDir, dirName );
|
||||||
|
if ( !genDir.exists() ) {
|
||||||
|
try {
|
||||||
|
// Generate the mod.
|
||||||
|
if ( genDir.mkdir() ) {
|
||||||
|
File appendixDir = new File ( genDir, "mod-appendix" );
|
||||||
|
if ( appendixDir.mkdir() ) {
|
||||||
|
File metadataFile = new File( appendixDir, "metadata.xml" );
|
||||||
|
|
||||||
|
JDOMModMetadataWriter.writeMetadata( metadataFile, modTitle, modURL, modAuthor, modVersion, modDesc );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IOException( String.format( "Failed to create directory: %s", appendixDir.getName() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IOException( String.format( "Failed to create directory: %s", genDir.getName() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the folder.
|
||||||
|
try {
|
||||||
|
if ( Desktop.isDesktopSupported() ) {
|
||||||
|
Desktop.getDesktop().open( genDir.getCanonicalFile() );
|
||||||
|
} else {
|
||||||
|
log.error( String.format( "Java cannot open the %s/ folder for you on this OS", genDir.getName() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( IOException f ) {
|
||||||
|
log.error( String.format( "Error opening %s/ folder", genDir.getName() ), f );
|
||||||
|
}
|
||||||
|
|
||||||
|
// All done.
|
||||||
|
CreateModDialog.this.dispose();
|
||||||
|
}
|
||||||
|
catch ( IOException f ) {
|
||||||
|
log.error( String.format( "Failed to generate new mod: %s", genDir.getName() ), f );
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog( CreateModDialog.this, String.format( "Failed to generate new mod: %s\n%s", genDir.getName(), f.getMessage() ), "Error", JOptionPane.ERROR_MESSAGE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
JOptionPane.showMessageDialog( CreateModDialog.this, String.format( "A directory named \"%s\" already exists.", genDir.getName() ), "Nothing to do", JOptionPane.WARNING_MESSAGE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -130,6 +130,7 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse
|
||||||
private JMenu fileMenu;
|
private JMenu fileMenu;
|
||||||
private JMenuItem rescanMenuItem;
|
private JMenuItem rescanMenuItem;
|
||||||
private JMenuItem extractDatsMenuItem;
|
private JMenuItem extractDatsMenuItem;
|
||||||
|
private JMenuItem createModMenuItem;
|
||||||
private JMenuItem sandboxMenuItem;
|
private JMenuItem sandboxMenuItem;
|
||||||
private JMenuItem configMenuItem;
|
private JMenuItem configMenuItem;
|
||||||
private JMenuItem exitMenuItem;
|
private JMenuItem exitMenuItem;
|
||||||
|
@ -318,10 +319,16 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse
|
||||||
extractDatsMenuItem.addMouseListener( new StatusbarMouseListener( this, "Extract FTL resources into a folder." ) );
|
extractDatsMenuItem.addMouseListener( new StatusbarMouseListener( this, "Extract FTL resources into a folder." ) );
|
||||||
extractDatsMenuItem.addActionListener( this );
|
extractDatsMenuItem.addActionListener( this );
|
||||||
fileMenu.add( extractDatsMenuItem );
|
fileMenu.add( extractDatsMenuItem );
|
||||||
|
fileMenu.add( new JSeparator() );
|
||||||
|
createModMenuItem = new JMenuItem( "New Mod..." );
|
||||||
|
createModMenuItem.addMouseListener( new StatusbarMouseListener( this, "Generate boilerplace for a new mod." ) );
|
||||||
|
createModMenuItem.addActionListener( this );
|
||||||
|
fileMenu.add( createModMenuItem );
|
||||||
sandboxMenuItem = new JMenuItem( "XML Sandbox..." );
|
sandboxMenuItem = new JMenuItem( "XML Sandbox..." );
|
||||||
sandboxMenuItem.addMouseListener( new StatusbarMouseListener( this, "Experiment with advanced mod syntax." ) );
|
sandboxMenuItem.addMouseListener( new StatusbarMouseListener( this, "Experiment with advanced mod syntax." ) );
|
||||||
sandboxMenuItem.addActionListener( this );
|
sandboxMenuItem.addActionListener( this );
|
||||||
fileMenu.add( sandboxMenuItem );
|
fileMenu.add( sandboxMenuItem );
|
||||||
|
fileMenu.add( new JSeparator() );
|
||||||
configMenuItem = new JMenuItem( "Preferences..." );
|
configMenuItem = new JMenuItem( "Preferences..." );
|
||||||
configMenuItem.addMouseListener( new StatusbarMouseListener( this, "Edit preferences." ) );
|
configMenuItem.addMouseListener( new StatusbarMouseListener( this, "Edit preferences." ) );
|
||||||
configMenuItem.addActionListener( this );
|
configMenuItem.addActionListener( this );
|
||||||
|
@ -803,6 +810,15 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse
|
||||||
extractDlg.extract();
|
extractDlg.extract();
|
||||||
extractDlg.setVisible( true );
|
extractDlg.setVisible( true );
|
||||||
}
|
}
|
||||||
|
else if ( source == createModMenuItem ) {
|
||||||
|
setStatusText( "" );
|
||||||
|
|
||||||
|
CreateModDialog createModDlg = new CreateModDialog( ManagerFrame.this, modsDir );
|
||||||
|
createModDlg.addWindowListener( nerfListener );
|
||||||
|
//configDlg.setSize( 300, 400 );
|
||||||
|
createModDlg.setLocationRelativeTo( null );
|
||||||
|
createModDlg.setVisible( true );
|
||||||
|
}
|
||||||
else if ( source == sandboxMenuItem ) {
|
else if ( source == sandboxMenuItem ) {
|
||||||
setStatusText( "" );
|
setStatusText( "" );
|
||||||
File datsDir = new File( appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH ) );
|
File datsDir = new File( appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH ) );
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
package net.vhati.modmanager.xml;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
import org.jdom2.CDATA;
|
||||||
|
import org.jdom2.Comment;
|
||||||
|
import org.jdom2.Document;
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import org.jdom2.Text;
|
||||||
|
import org.jdom2.output.Format;
|
||||||
|
import org.jdom2.output.LineSeparator;
|
||||||
|
import org.jdom2.output.XMLOutputter;
|
||||||
|
|
||||||
|
import net.vhati.modmanager.core.EOLWriter;
|
||||||
|
|
||||||
|
|
||||||
|
public class JDOMModMetadataWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes boilerplate metadata for a mod.
|
||||||
|
*
|
||||||
|
* @param outFile "{modDir}/mod-appendix/metadata.xml"
|
||||||
|
* @param modTitle
|
||||||
|
* @param modURL
|
||||||
|
* @param modAuthor
|
||||||
|
* @param modVersion
|
||||||
|
* @param modDesc
|
||||||
|
*/
|
||||||
|
public static void writeMetadata( File outFile, String modTitle, String modURL, String modAuthor, String modVersion, String modDesc ) throws IOException {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
|
||||||
|
Element rootNode = new Element( "metadata" );
|
||||||
|
Document doc = new Document( rootNode );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\n" ) );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
buf.setLength( 0 );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\t\tCDATA tags mean no need to escape special characters.\n" );
|
||||||
|
buf.append( "\t\tDon't worry about spaces at the start/end. That gets trimmed.\n" );
|
||||||
|
buf.append( "\t" );
|
||||||
|
rootNode.addContent( new Comment( buf.toString() ) );
|
||||||
|
rootNode.addContent( new Text( "\n\n\n" ) );
|
||||||
|
|
||||||
|
// title.
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
rootNode.addContent( new Comment( String.format( " %s ", "The title of this mod." ) ) );
|
||||||
|
rootNode.addContent( new Text( "\n" ) );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
Element titleNode = new Element( "title" );
|
||||||
|
titleNode.setContent( new CDATA( String.format( " %s ", modTitle ) ) );
|
||||||
|
rootNode.addContent( titleNode );
|
||||||
|
rootNode.addContent( new Text( "\n\n\n" ) );
|
||||||
|
|
||||||
|
// threadUrl.
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
buf.setLength( 0 );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\t\tThis mod's thread on subsetgames.com.\n" );
|
||||||
|
buf.append( "\t\tIf there's no thread yet, create one to announce your upcoming mod in the\n" );
|
||||||
|
buf.append( "\t\tforum. Then paste the url here.\n" );
|
||||||
|
buf.append( "\t" );
|
||||||
|
rootNode.addContent( new Comment( buf.toString() ) );
|
||||||
|
rootNode.addContent( new Text( "\n" ) );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
Element urlNode = new Element( "threadUrl" );
|
||||||
|
urlNode.setContent( new CDATA( String.format( " %s ", modURL ) ) );
|
||||||
|
rootNode.addContent( urlNode );
|
||||||
|
rootNode.addContent( new Text( "\n\n\n" ) );
|
||||||
|
|
||||||
|
// author.
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
rootNode.addContent( new Comment( String.format( " %s ", "Your forum user name." ) ) );
|
||||||
|
rootNode.addContent( new Text( "\n" ) );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
Element authorNode = new Element( "author" );
|
||||||
|
authorNode.setContent( new CDATA( String.format( " %s ", modAuthor ) ) );
|
||||||
|
rootNode.addContent( authorNode );
|
||||||
|
rootNode.addContent( new Text( "\n\n\n" ) );
|
||||||
|
|
||||||
|
// version.
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
buf.setLength( 0 );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\t\tThe revision/variant of this release, preferably at least a number.\n" );
|
||||||
|
buf.append( "\t\tExamples:\n" );
|
||||||
|
buf.append( "\t\t\t0.3\n" );
|
||||||
|
buf.append( "\t\t\t2.1c Ships Only WIP\n" );
|
||||||
|
buf.append( "\t\t\t2.4.1 Hi-res Bkgs\n" );
|
||||||
|
buf.append( "\t\t\t1.0 for FTL 1.03.1\n" );
|
||||||
|
buf.append( "\t" );
|
||||||
|
rootNode.addContent( new Comment( buf.toString() ) );
|
||||||
|
rootNode.addContent( new Text( "\n" ) );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
Element versionNode = new Element( "version" );
|
||||||
|
versionNode.setContent( new CDATA( String.format( " %s ", modVersion ) ) );
|
||||||
|
rootNode.addContent( versionNode );
|
||||||
|
rootNode.addContent( new Text( "\n\n\n" ) );
|
||||||
|
|
||||||
|
// description.
|
||||||
|
rootNode.addContent( new Text( "\t" ) );
|
||||||
|
Element descNode = new Element( "description" );
|
||||||
|
descNode.addContent( new Text( "\n" ) );
|
||||||
|
descNode.addContent( new CDATA( String.format( "\n%s\n", modDesc ) ) );
|
||||||
|
descNode.addContent( new Text( "\n\t" ) );
|
||||||
|
rootNode.addContent( descNode );
|
||||||
|
|
||||||
|
rootNode.addContent( new Text( "\n\n" ) );
|
||||||
|
|
||||||
|
buf.setLength( 0 );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tSuggestions for the description...\n" );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tWrite a short paragraph about the mod's effect first (what style ship, how\n" );
|
||||||
|
buf.append( "\tdoes it affect gameplay). No need to introduce yourself.\n" );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tOptionally add a paragraph of background flavor.\n" );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tOptionally list important features.\n" );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tList any concerns about mod compatibility, preferred order, or requirements.\n" );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tMention \"Replaces the XYZ ship.\" if relevant.\n" );
|
||||||
|
buf.append( "\t\tKestrel-A, Stealth-A, Mantis-A,\n" );
|
||||||
|
buf.append( "\t\tEngi-A, Fed-A, Slug-A,\n" );
|
||||||
|
buf.append( "\t\tRock-A, Zoltan-A, Crystal-A\n" );
|
||||||
|
buf.append( "\n" );
|
||||||
|
buf.append( "\tAbove all, keep the description general, so you won't have to edit\n" );
|
||||||
|
buf.append( "\tthat again for each new version.\n" );
|
||||||
|
buf.append( "\t" );
|
||||||
|
rootNode.addContent( new Comment( buf.toString() ) );
|
||||||
|
rootNode.addContent( new Text( "\n" ) );
|
||||||
|
|
||||||
|
Format format = Format.getPrettyFormat();
|
||||||
|
format.setTextMode( Format.TextMode.PRESERVE );
|
||||||
|
format.setExpandEmptyElements( false );
|
||||||
|
format.setOmitDeclaration( false );
|
||||||
|
format.setIndent( "\t" );
|
||||||
|
format.setLineSeparator( LineSeparator.CRNL );
|
||||||
|
|
||||||
|
Writer writer = null;
|
||||||
|
try {
|
||||||
|
writer = new EOLWriter( new FileWriter( outFile ), "\r\n" );
|
||||||
|
|
||||||
|
XMLOutputter xmlOutput = new XMLOutputter();
|
||||||
|
xmlOutput.setFormat( format );
|
||||||
|
xmlOutput.output( doc, writer );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {if ( writer != null ) writer.close();}
|
||||||
|
catch ( IOException e ) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue