/** Cancels the language selection. */ private void cancel() { dispose(); // Cancel was pressed, revert to the default language Language.loadAndActivateLanguage(Settings.DEFAULT_APP_LANGUAGE); Settings.remove(Settings.KEY_SETTINGS_VOICE); synchronized (WelcomeFrame.this) { Sounds.playSoundSample(Sounds.SAMPLE_THANK_YOU, false); WelcomeFrame.this.notify(); // Notify the main thread to continue starting the application } }
static { int TEXT_KEY_COLUMN_ = 0; int ORIGINAL_TEXT_COLUMN_ = 0; int TRANSLATION_COLUMN_ = 0; for (int i = 0; i < TEXTS_HEADER_KEYS.length; i++) { final String headerKey = TEXTS_HEADER_KEYS[i]; TEXTS_HEADER_NAME_VECTOR.addElement(Language.getText(headerKey)); if ("translationTool.tab.texts.table.header.textKeyContext".equals(headerKey)) TEXT_KEY_COLUMN_ = i; else if ("translationTool.tab.texts.table.header.originalText".equals(headerKey)) ORIGINAL_TEXT_COLUMN_ = i; else if ("translationTool.tab.texts.table.header.translation".equals(headerKey)) TRANSLATION_COLUMN_ = i; } TEXT_KEY_COLUMN = TEXT_KEY_COLUMN_; ORIGINAL_TEXT_COLUMN = ORIGINAL_TEXT_COLUMN_; TRANSLATION_COLUMN = TRANSLATION_COLUMN_; }
/** Gets current text values and reassigns them to the GUI components. */ private void reassignTexts() { setTitle(Language.getText("welcome.welcome") + " - " + Consts.APPLICATION_NAME); welcomeLabel.setText(Language.getText("welcome.welcome") + '!'); firstRunLabel.setText(Language.getText("welcome.firstRun", Consts.APPLICATION_NAME + "™")); chooseLabel.setText(Language.getText("welcome.selectLanguage")); thankYouLabel.setText(Language.getText("welcome.thankYou", Consts.APPLICATION_NAME + "™")); languageLabel.setText(Language.getText("welcome.language")); voiceLabel.setText(Language.getText("welcome.voice")); languageLabel.setPreferredSize(null); voiceLabel.setPreferredSize(null); final int maxWidth = Math.max(languageLabel.getPreferredSize().width, voiceLabel.getPreferredSize().width); languageLabel.setPreferredSize( new Dimension(maxWidth, languageLabel.getPreferredSize().height)); voiceLabel.setPreferredSize(new Dimension(maxWidth, voiceLabel.getPreferredSize().height)); GuiUtils.updateButtonText(okButton, "button.ok"); GuiUtils.updateButtonText(cancelButton, "button.cancel"); pack(); SharedUtils.centerWindow(this); }
/** * The entry point of the program. * * <p>Checks for running instances, and passes arguments if have to. Else loads the settings, * language files etc. and then instantiates the main frame. * * @param arguments if command line mode is desired, they will be handled by the {@link * CliHandler}; else they will be treated as files (like replays, replay lists, replay * sources) and will be opened properly * @throws MalformedURLException */ public static void main(final String[] arguments) { // Add Sc2gears version and OS info to the User-Agent HTTP request property. // The final user agent string will be the value of this property + the default (which is the // Java version). System.setProperty( "http.agent", Consts.APPLICATION_NAME + "/" + Consts.APPLICATION_VERSION + " (" + System.getProperty("os.name") + "; " + System.getProperty("os.version") + "; " + System.getProperty("os.arch") + ")"); checkFolders(); Settings.loadProperties(); final boolean cliMode = CliHandler.checkCliMode(arguments); if (!cliMode) { InstanceMonitor.checkRunningInstance(arguments); Log.init(); } if (!cliMode) { installExtraLAFs(); GuiUtils.setLAF(Settings.getString(Settings.KEY_SETTINGS_LAF)); } if (Settings.doesSettingsFileExist()) { Language.loadAndActivateLanguage(Settings.getString(Settings.KEY_SETTINGS_LANGUAGE)); } else { // Only show the welcome frame if we're not in CLI mode if (!cliMode) { final WelcomeFrame welcomeFrame = new WelcomeFrame(); synchronized (welcomeFrame) { try { welcomeFrame.wait(); // Wait until the welcome frame is closed. } catch (final InterruptedException ie) { // This should never happen. } } } } // Now language is loaded, initialize codes that build on it. Language.applyDateTimeFormats(); Settings.completeDefaultPropertiesInitialization(); GuiUtils.initFileFilters(); // Must be after completeDefaultPropertiesInitialization()! checkAndPerformPostUpdate(); // Ensure ReplayUtils and AbilityCodesRepository is initialized // (else opening a replay from the file menu or by the CilHandler would fail!) try { Class.forName(ReplayUtils.class.getName()); } catch (final ClassNotFoundException cfe) { // Never to happen cfe.printStackTrace(); } if (cliMode) System.exit(CliHandler.handleArguments(arguments)); else { // Load the Native class now, else later it might hang (dead lock?) if (GeneralUtils.isWindows()) // Currently JNA is only used on windows try { Class.forName(Native.class.getName()); } catch (final ClassNotFoundException cfe) { // Never to happen cfe.printStackTrace(); } // Apply proxy config applyProxyConfig(); // Load plugins PluginManager.loadPlugins(); // Now instantiate the main frame new MainFrame(arguments); } }
/** Creates a new TranslationToolDialog. */ public TranslationToolDialog() { super("translationTool.title", Icons.LOCALE); final JPanel northPanel = new JPanel(new BorderLayout()); northPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); final Box languageChooserBox = Box.createVerticalBox(); languageChooserBox.setBorder(BorderFactory.createEmptyBorder(10, 0, 15, 0)); Box row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.chooseLanguageToEdit"))); final JComboBox<String> editedLanguageComboBox = new JComboBox<>(); final String EMPTY_LANGUAGE = " "; final Runnable rebuildEditedLanguageComboBoxTask = new Runnable() { @Override public void run() { editedLanguageComboBox.removeAllItems(); editedLanguageComboBox.addItem(EMPTY_LANGUAGE); for (final String language : Language.getAvailableLanguages()) if (!Settings.DEFAULT_APP_LANGUAGE.equals(language)) editedLanguageComboBox.addItem(language); editedLanguageComboBox.setMaximumRowCount(editedLanguageComboBox.getItemCount()); } }; rebuildEditedLanguageComboBoxTask.run(); editedLanguageComboBox.setSelectedIndex(0); editedLanguageComboBox.setRenderer( new BaseLabelListCellRenderer<String>(2) { @Override public Icon getIcon(final String value) { return EMPTY_LANGUAGE.equals(value) ? null : Icons.getLanguageIcon(value); } }); row.add(editedLanguageComboBox); languageChooserBox.add(row); languageChooserBox.add(Box.createVerticalStrut(7)); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.orCreateNewTranslationWithName"))); final JTextField newLanguageNameTextField = new JTextField(10); row.add(newLanguageNameTextField); final JButton createNewLanguageButton = new JButton(); GuiUtils.updateButtonText(createNewLanguageButton, "translationTool.createButton"); row.add(createNewLanguageButton); languageChooserBox.add(row); GuiUtils.alignBox(languageChooserBox, 1); languageChooserBox.add(Box.createVerticalStrut(20)); final JPanel buttonsPanel = new JPanel(new GridLayout(1, 2)); final JButton saveButton = new JButton(Icons.DISK); GuiUtils.updateButtonText(saveButton, "translationTool.saveChangesButton"); buttonsPanel.add(saveButton); final JButton closeButton = createCloseButton("button.close"); buttonsPanel.add(closeButton); languageChooserBox.add(buttonsPanel); northPanel.add(GuiUtils.wrapInPanel(languageChooserBox), BorderLayout.WEST); final JEditorPane notesEditorPane = new JEditorPane(); notesEditorPane.setContentType("text/html"); notesEditorPane.setText( "<html>" + "<b>NOTES</b><br><br>" + "With this Translation tool you can translate " + Consts.APPLICATION_NAME + " into other languages or edit any existing translations. If you create a new translation or update an existing one, please send the language file to me via email so I can include it in the next release.<br>" + "Each translation is stored in its own file. Language files are saved in the <i>\"" + Consts.FOLDER_LANGUAGES + "\"</i> folder inside " + Consts.APPLICATION_NAME + ".<br>" + "Official language files appear in the application with a country flag. If you send me language files that I approve, I will add and associate the proper country flag for the language.<br>" + "Not all texts have to be translated, but of course the more the better. If a language file is incomplete, the original English version will be displayed for the missing texts.<br>" + "<br><i>Warning! If you update " + Consts.APPLICATION_NAME + ", the Updater will overwrite existing language files! Be sure to keep a copy of the language file you edit!</i><br><br><hr>" + "<b>PARAMETERS</b><br><br>" + "The values of texts may contain parameters which will be substituted by values specified by the program.<br>" + "The places and order of parameters are indicated with <span style='color:green'>$x</span> where <span style='color:green'>x</span> is the number identifier of the parameter (starting from 0).<br>" + "For example the following text: <span style='color:green'>Hello $1, this is a $0 day! You have $2$3.</span><br>" + "with the parameters: <span style='color:green'>\"fine\", \"Mr. Hunter\", '$', 10</span><br>" + "will result in: <span style='color:green'>Hello Mr. Hunter, this is a fine day! You have $10.<span style='color:green'><br><br><hr>" + "<b>HOTKEYS / MNEMONICS</b><br><br>" + "Menus and buttons may have hotkeys / mnemonics. The hotkey character can be marked with an _ (underscore) sign before the intended character.<br>" + "The hotkey indicators will be removed when displayed to the user.<br>" + "The hotkeys are optional. For example if we want a <span style='color:green'>\"Visit home page\"</span> button to have the ALT+M hotkey, the following text is to be specified: <span style='color:green'>\"Visit ho_me page\"</span><br><br><hr>" + "<b>ICONS</b><br><br>" + "Some texts appear with icons in the texts table. The following icons are defined:<ul>" + "<li><img border=0 src=\"" + Icons.HTML.resource + "\"> This icon indicates that the text is specified as HTML text. This is the case if the text starts with <i>\"<html>\"</i>; and in this case it has to end with <i>\"</html>\"</i>." + "<li><img border=0 src=\"" + Icons.KEYBOARD.resource + "\"> This icon indicates that the text contains a hotkey / mnemonic marker." + "<li><img border=0 src=\"" + Icons.DOCUMENT_ATTRIBUTE_P.resource + "\"> This icon indicates that the text contains parameters." + "</ul>" + "</html>"); notesEditorPane.setEditable(false); JScrollPane scrollPane = new JScrollPane(notesEditorPane); scrollPane.setPreferredSize(new Dimension(10, 120)); northPanel.add(scrollPane, BorderLayout.CENTER); SwingUtilities.invokeLater( new Runnable() { @Override public void run() { notesEditorPane.scrollRectToVisible(new Rectangle(0, 0, 1, 1)); } }); getContentPane().add(northPanel, BorderLayout.NORTH); final JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); final JPanel generalInfoPanel = new JPanel(new BorderLayout()); final Box box = Box.createVerticalBox(); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.languageFileVersion"))); final JTextField languageFileVersionTextField = new JTextField(20); row.add(languageFileVersionTextField); final JButton setCurrentVersionButton = new JButton(); GuiUtils.updateButtonText( setCurrentVersionButton, "translationTool.tab.generalInfo.setCurrentVersionButton"); row.add(setCurrentVersionButton); box.add(row); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.languageFileSubversion"))); final JTextField languageFileSubversionTextField = new JTextField(20); row.add(languageFileSubversionTextField); row.add(new JLabel()); setCurrentVersionButton.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { languageFileVersionTextField.setText(Consts.APPLICATION_LANGUAGE_VERSION); languageFileSubversionTextField.setText("1"); } }); box.add(row); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.translatorFirstName"))); final JTextField translatorFirstNameTextField = new JTextField(20); row.add(translatorFirstNameTextField); row.add(new JLabel()); box.add(row); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.translatorLastName"))); final JTextField translatorLastNameTextField = new JTextField(20); row.add(translatorLastNameTextField); row.add(new JLabel()); box.add(row); // Date/time formats final String dateTimeFormatToolTip = Language.getText("miscSettings.customDateTimeFormatToolTip"); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.dateFormat"))); final JComboBox<String> dateFormatTextComboBox = GuiUtils.createPredefinedListComboBox(PredefinedList.CUSTOM_DATE_FORMAT, false); dateFormatTextComboBox.setToolTipText(dateTimeFormatToolTip); row.add(dateFormatTextComboBox); final JPanel dateControlPanel = new JPanel(new BorderLayout()); final JButton testDateButton = new JButton(Language.getText("miscSettings.testFormatButton")); dateControlPanel.add(testDateButton, BorderLayout.CENTER); dateControlPanel.add(GuiUtils.createDateTimeFormatHelpLinkLabel(), BorderLayout.EAST); row.add(dateControlPanel); box.add(row); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.timeFormat"))); final JComboBox<String> timeFormatTextComboBox = GuiUtils.createPredefinedListComboBox(PredefinedList.CUSTOM_TIME_FORMAT, false); timeFormatTextComboBox.setToolTipText(dateTimeFormatToolTip); row.add(timeFormatTextComboBox); final JPanel timeControlPanel = new JPanel(new BorderLayout()); final JButton testTimeButton = new JButton(Language.getText("miscSettings.testFormatButton")); timeControlPanel.add(testTimeButton, BorderLayout.CENTER); timeControlPanel.add(GuiUtils.createDateTimeFormatHelpLinkLabel(), BorderLayout.EAST); row.add(timeControlPanel); box.add(row); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.dateTimeFormat"))); final JComboBox<String> dateTimeFormatTextComboBox = GuiUtils.createPredefinedListComboBox(PredefinedList.CUSTOM_DATE_TIME_FORMAT, false); dateTimeFormatTextComboBox.setToolTipText(dateTimeFormatToolTip); row.add(dateTimeFormatTextComboBox); final JPanel dateTimeControlPanel = new JPanel(new BorderLayout()); final JButton testDateTimeButton = new JButton(Language.getText("miscSettings.testFormatButton")); dateTimeControlPanel.add(testDateTimeButton, BorderLayout.CENTER); dateTimeControlPanel.add(GuiUtils.createDateTimeFormatHelpLinkLabel(), BorderLayout.EAST); row.add(dateTimeControlPanel); final ActionListener testDateTimeActionListener = new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { final String pattern = (event.getSource() == testDateButton ? dateFormatTextComboBox : event.getSource() == testTimeButton ? timeFormatTextComboBox : dateTimeFormatTextComboBox) .getSelectedItem() .toString(); try { final String currentTime = new SimpleDateFormat(pattern).format(new Date()); GuiUtils.showInfoDialog( new Object[] { Language.getText("miscSettings.dateTimeFormatValid"), " ", Language.getText("miscSettings.currentDateTimeWithFormat"), currentTime }); } catch (IllegalArgumentException iae) { iae.printStackTrace(); GuiUtils.showErrorDialog(Language.getText("miscSettings.dateTimeFormatInvalid")); } } }; testDateButton.addActionListener(testDateTimeActionListener); testTimeButton.addActionListener(testDateTimeActionListener); testDateTimeButton.addActionListener(testDateTimeActionListener); box.add(row); row = Box.createHorizontalBox(); row.add(new JLabel(Language.getText("translationTool.tab.generalInfo.personNameFormat"))); final JComboBox<String> personNameFormatComboBox = new JComboBox<>( new String[] { Language.getText( "translationTool.tab.generalInfo.personNameFormat.firstNameLastName"), Language.getText("translationTool.tab.generalInfo.personNameFormat.lastNameFirstName") }); row.add(personNameFormatComboBox); row.add(new JLabel()); box.add(row); GuiUtils.alignBox(box, 3); generalInfoPanel.add(new JScrollPane(GuiUtils.wrapInPanel(box)), BorderLayout.CENTER); GuiUtils.addNewTab( Language.getText("translationTool.tab.generalInfo.title"), Icons.INFORMATION_BALLOON, false, tabbedPane, generalInfoPanel, null); final JPanel textsPanel = new JPanel(new BorderLayout()); final JProgressBar progressBar = new JProgressBar(0, Language.DEFAULT_LANGUAGE.textMap.size()); progressBar.setStringPainted(true); textsPanel.add(progressBar, BorderLayout.NORTH); final JTable textsTable = new JTable() { // Custom cell renderer because we want icons and html source to be rendered... final DefaultTableCellRenderer customCellRenderer = new DefaultTableCellRenderer() { @Override public Component getTableCellRendererComponent( final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); Icon icon = null; if (value != null && ((String) value).startsWith("<html>")) { icon = Icons.HTML; setText(' ' + (String) value); } if (value != null && ((String) value).indexOf('$') >= 0) icon = icon == null ? Icons.DOCUMENT_ATTRIBUTE_P : GuiUtils.concatenateIcons(icon, Icons.DOCUMENT_ATTRIBUTE_P); if (value != null && ((String) value).indexOf('_') >= 0) icon = icon == null ? Icons.KEYBOARD : GuiUtils.concatenateIcons(icon, Icons.KEYBOARD); setIcon(icon); return this; } }; @Override public boolean isCellEditable(final int row, final int column) { return column == TRANSLATION_COLUMN; } int tipCounter = 0; // To generate unique tips so it will follow the mouse cursor @Override public String getToolTipText(final MouseEvent event) { if (columnAtPoint(event.getPoint()) == TRANSLATION_COLUMN) return Language.getText("translationTool.tab.texts.table.toolTip") + ((tipCounter++ & 0x01) == 1 ? " " : " "); return super.getToolTipText(event); } @Override public TableCellRenderer getCellRenderer(final int row, final int column) { return customCellRenderer; } }; textsTable.setAutoCreateRowSorter(true); textsTable.setColumnSelectionAllowed(true); textsTable.setShowVerticalLines(true); textsTable.setPreferredScrollableViewportSize(new Dimension(850, 350)); textsTable.getTableHeader().setReorderingAllowed(false); ((DefaultTableModel) textsTable.getModel()) .setDataVector(new Vector<Vector<String>>(), TEXTS_HEADER_NAME_VECTOR); // Start editing for 1 click: ((DefaultCellEditor) textsTable.getDefaultEditor(textsTable.getColumnClass(TRANSLATION_COLUMN))) .setClickCountToStart(1); // Gain focus and cursor when editing started due to typing textsTable.setSurrendersFocusOnKeystroke(true); final TableBox tableBox = new TableBox(textsTable, getLayeredPane(), null); tableBox.getFilterComponentsWrapper().add(Box.createHorizontalStrut(10)); final JCheckBox showOnlyUntranslatedCheckBox = new JCheckBox(Language.getText("translationTool.tab.texts.showOnlyUntranslated")); showOnlyUntranslatedCheckBox.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { tableBox.fireAdditionalRowFilterChanged(); } }); tableBox.getFilterComponentsWrapper().add(showOnlyUntranslatedCheckBox); tableBox.getFilterComponentsWrapper().add(Box.createHorizontalStrut(5)); final JButton previousButton = new JButton(Icons.ARROW_180); GuiUtils.updateButtonText(previousButton, "translationTool.tab.texts.previousButton"); tableBox.getFilterComponentsWrapper().add(previousButton); previousButton.setToolTipText( Language.getText("translationTool.tab.texts.previousButtonToolTip")); final JButton nextButton = new JButton(Icons.ARROW); nextButton.setHorizontalTextPosition(SwingConstants.LEFT); GuiUtils.updateButtonText(nextButton, "translationTool.tab.texts.nextButton"); nextButton.setToolTipText(Language.getText("translationTool.tab.texts.nextButtonToolTip")); tableBox.getFilterComponentsWrapper().add(nextButton); final ActionListener prevNextActionListener = new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { final int direction = event.getSource() == previousButton ? -1 : 1; // Visible rows count final int rowsCount = textsTable.getRowCount(); if (rowsCount == 0) return; // No visible rows if (textsTable.isEditing()) textsTable.getCellEditor().stopCellEditing(); int start = textsTable.getSelectedRow(); if (start < 0) start = 0; int i = start; do { i += direction; if (i == rowsCount) i = 0; if (i < 0) i = rowsCount - 1; final String translation = (String) textsTable.getValueAt(i, TRANSLATION_COLUMN); if (translation == null || translation.length() == 0) { // select row (and column) textsTable.getSelectionModel().setSelectionInterval(i, i); textsTable.setColumnSelectionInterval(TRANSLATION_COLUMN, TRANSLATION_COLUMN); textsTable.scrollRectToVisible(textsTable.getCellRect(i, TRANSLATION_COLUMN, true)); break; } } while (i != start); final String translation = (String) textsTable.getValueAt(i, TRANSLATION_COLUMN); if (i == start && translation != null && translation.length() > 0) GuiUtils.showInfoDialog( Language.getText("translationTool.tab.texts.allDisplayedTextsAreTranslated")); textsTable.requestFocusInWindow(); } }; previousButton.addActionListener(prevNextActionListener); nextButton.addActionListener(prevNextActionListener); tableBox.getFilterComponentsWrapper().add(new JLabel("<html></html>")); textsPanel.add(tableBox, BorderLayout.CENTER); final Box southBox = Box.createVerticalBox(); final JPanel previewPanel = new JPanel(new GridLayout(1, 2)); final JEditorPane originalTextPreviewPane = new JEditorPane(); originalTextPreviewPane.setEditable(false); scrollPane = new JScrollPane(originalTextPreviewPane); scrollPane.setBorder( BorderFactory.createTitledBorder( Language.getText("translationTool.tab.texts.originalTextPreview"))); scrollPane.setPreferredSize(new Dimension(10, 140)); previewPanel.add(scrollPane); final JEditorPane translationPreviewPane = new JEditorPane(); translationPreviewPane.setEditable(false); scrollPane = new JScrollPane(translationPreviewPane); scrollPane.setBorder( BorderFactory.createTitledBorder( Language.getText("translationTool.tab.texts.translationPreview"))); scrollPane.setPreferredSize(new Dimension(10, 140)); previewPanel.add(scrollPane); southBox.add(previewPanel); final JTextArea commentTextArea = new JTextArea(2, 1); commentTextArea.setEditable(false); scrollPane = new JScrollPane(commentTextArea); scrollPane.setBorder( BorderFactory.createTitledBorder(Language.getText("translationTool.tab.texts.comments"))); southBox.add(scrollPane); textsPanel.add(southBox, BorderLayout.SOUTH); GuiUtils.addNewTab( Language.getText("translationTool.tab.texts.title"), Icons.BALLOONS, false, tabbedPane, textsPanel, null); getContentPane().add(tabbedPane, BorderLayout.CENTER); final Runnable updateProgressBarTask = new Runnable() { @Override public void run() { @SuppressWarnings("unchecked") final Vector<Vector<String>> dataVector = ((DefaultTableModel) textsTable.getModel()).getDataVector(); int translatedCount = 0; for (final Vector<String> row : dataVector) { final String translation = row.get(TRANSLATION_COLUMN); if (translation != null && translation.length() > 0) translatedCount++; } progressBar.setValue(translatedCount); progressBar.setString( Language.getText( "translationTool.tab.texts.translationProgress", translatedCount, TOTAL_TEXTS_COUNT, 100 * translatedCount / TOTAL_TEXTS_COUNT)); } }; final Task<Holder<String>> updateTranslationPreviewTask = new Task<Holder<String>>() { /** * @param translationHolder if provided, it will be used as the translation; else the * translation from the table will be read (from the selected row) */ @Override public void execute(final Holder<String> translationHolder) { int selectedRow = -1; if (translationHolder != null || (selectedRow = textsTable.getSelectedRow()) >= 0) { String translation = translationHolder == null ? (String) textsTable.getValueAt(selectedRow, TRANSLATION_COLUMN) : translationHolder.value; // If text has a mnemonic, show it as HTML text where the mnemonic is underlined: int underScoreIndex; if (translation != null && (underScoreIndex = translation.indexOf('_')) >= 0 && underScoreIndex < translation.length() - 1) translation = "<html>" + translation.substring(0, underScoreIndex) + "<u>" + translation.charAt(underScoreIndex + 1) + "</u>" + translation.substring(underScoreIndex + 2) + "</html>"; translationPreviewPane.setContentType( translation != null && translation.startsWith("<html>") ? "text/html" : "text/plain"); translationPreviewPane.setText(translation); SwingUtilities.invokeLater( new Runnable() { @Override public void run() { translationPreviewPane.scrollRectToVisible(new Rectangle(0, 0, 1, 1)); } }); } else { translationPreviewPane.setContentType("text/plain"); translationPreviewPane.setText(null); } } }; textsTable .getSelectionModel() .addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(final ListSelectionEvent event) { // Update translation preview updateTranslationPreviewTask.execute(null); // Update original text preview and comments final int selectedRow = textsTable.getSelectedRow(); if (selectedRow >= 0) { // Update original text preview String originalText = (String) textsTable.getValueAt(selectedRow, ORIGINAL_TEXT_COLUMN); // If text has a mnemonic, show it as HTML text where the mnemonic is underlined: int underScoreIndex; if (originalText != null && (underScoreIndex = originalText.indexOf('_')) >= 0 && underScoreIndex < originalText.length() - 1) originalText = "<html>" + originalText.substring(0, underScoreIndex) + "<u>" + originalText.charAt(underScoreIndex + 1) + "</u>" + originalText.substring(underScoreIndex + 2) + "</html>"; originalTextPreviewPane.setContentType( originalText != null && originalText.startsWith("<html>") ? "text/html" : "text/plain"); originalTextPreviewPane.setText(originalText); SwingUtilities.invokeLater( new Runnable() { @Override public void run() { originalTextPreviewPane.scrollRectToVisible(new Rectangle(0, 0, 1, 1)); } }); // Update comments commentTextArea.setText(null); final String textKey = (String) textsTable.getValueAt(selectedRow, TEXT_KEY_COLUMN); // Text group comments: boolean firstGroupComment = true; int dotIndex = 0; while ((dotIndex = textKey.indexOf('.', dotIndex)) >= 0) { final String groupComment = Language.DEFAULT_LANGUAGE.textGroupCommentsMap.get( textKey.substring(0, dotIndex)); if (groupComment != null) { if (firstGroupComment) firstGroupComment = false; else commentTextArea.append(" => "); commentTextArea.append(groupComment); } dotIndex++; } if (!firstGroupComment) commentTextArea.append("\n"); // Text comment commentTextArea.append( Language.DEFAULT_LANGUAGE.textCommentsMap.get( textKey)); // TextArea.append() ommits nulls commentTextArea.setCaretPosition(0); } else { originalTextPreviewPane.setContentType("text/plain"); originalTextPreviewPane.setText(null); commentTextArea.setText(null); } } }); // Show real time preview while editing: ((JTextField) ((DefaultCellEditor) textsTable.getDefaultEditor(textsTable.getColumnClass(TRANSLATION_COLUMN))) .getComponent()) .getDocument() .addDocumentListener( new DocumentListener() { @Override public void removeUpdate(final DocumentEvent event) { changedUpdate(event); } @Override public void insertUpdate(final DocumentEvent event) { changedUpdate(event); } @Override public void changedUpdate(final DocumentEvent event) { try { final String translation = event.getDocument().getText(0, event.getDocument().getLength()); updateTranslationPreviewTask.execute(new Holder<String>(translation)); } catch (final BadLocationException ble) { ble.printStackTrace(); } } }); textsTable .getModel() .addTableModelListener( new TableModelListener() { @Override public void tableChanged(final TableModelEvent event) { // event.getColumn() returns the column model index if (event.getColumn() >= 0 && event.getColumn() == TRANSLATION_COLUMN) { updateProgressBarTask.run(); if (showOnlyUntranslatedCheckBox.isSelected()) { // If only untranslated texts are displayed, then after editing the current row // will be hidden, // so the selected row have to be moved up by 1 // (If the entered text will be empty, then this will have a side effect of not // changing the selected row // but that's not really a problem, it's even the intended operation.) final int newSelectedRow = Math.max(0, textsTable.getEditingRow()); tableBox.fireAdditionalRowFilterChanged(); textsTable .getSelectionModel() .setSelectionInterval(newSelectedRow, newSelectedRow); } } } }); editedLanguageComboBox.addActionListener( new ActionListener() { { actionPerformed(null); } // Initialize @Override public void actionPerformed(final ActionEvent event) { final String selectedLanguage = (String) editedLanguageComboBox.getSelectedItem(); if (selectedLanguage == null) return; // selectedLanguage can be null when the combo box is being rebuilt... final boolean isLanguageSelected = selectedLanguage != null && !EMPTY_LANGUAGE.equals(selectedLanguage); if (isLanguageSelected) { final Language language = Language.loadLanguage(selectedLanguage); if (language == null) { GuiUtils.showErrorDialog( Language.getText("translationTool.failedToLoadLanguage", selectedLanguage)); editedLanguageComboBox.setSelectedIndex(0); GuiUtils.setComponentTreeEnabled(tabbedPane, false); saveButton.setEnabled(false); } else { languageFileVersionTextField.setText(language.languageFileVersion); languageFileSubversionTextField.setText(language.languageFileSubversion); translatorFirstNameTextField.setText(language.translatorFirstName); translatorLastNameTextField.setText(language.translatorLastName); dateFormatTextComboBox.setSelectedItem(language.defaultDateFormatPattern); timeFormatTextComboBox.setSelectedItem(language.defaultTimeFormatPattern); dateTimeFormatTextComboBox.setSelectedItem(language.defaultDateTimeFormatPattern); personNameFormatComboBox.setSelectedIndex( language.personNameFormatFirstNameFirst ? 0 : 1); final Vector<Vector<String>> dataVector = new Vector<Vector<String>>(Language.DEFAULT_LANGUAGE.textMap.size()); final Map<String, String> languageTextMap = language.textMap; for (final Entry<String, String> textEntry : Language.DEFAULT_LANGUAGE.textMap.entrySet()) { final Vector<String> row = new Vector<String>(3); final String textKey = textEntry.getKey(); row.add(textKey); row.add(textEntry.getValue()); row.add(languageTextMap.get(textKey)); dataVector.add(row); } ((DefaultTableModel) textsTable.getModel()) .setDataVector(dataVector, TEXTS_HEADER_NAME_VECTOR); textsTable .getRowSorter() .setSortKeys(Arrays.asList(new SortKey(0, SortOrder.ASCENDING))); tableBox.setAdditionalRowFilter( new RowFilter<TableModel, Integer>() { @Override public boolean include( final Entry<? extends TableModel, ? extends Integer> entry) { if (showOnlyUntranslatedCheckBox.isSelected()) { final String translation = dataVector.get(entry.getIdentifier()).get(TRANSLATION_COLUMN); return translation == null || translation.length() == 0; } else return true; } }); updateProgressBarTask.run(); GuiUtils.setComponentTreeEnabled(tabbedPane, true); saveButton.setEnabled(true); } } else { GuiUtils.setComponentTreeEnabled(tabbedPane, false); saveButton.setEnabled(false); } } }); final ActionListener saveActionListener = new ActionListener() { /** If <code>event</code> is <code>null</code> a new language file will be saved! */ @Override public void actionPerformed(final ActionEvent event) { if (textsTable.isEditing()) textsTable.getCellEditor().stopCellEditing(); try { final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); final Element rootElement = document.createElement("language"); rootElement.setAttribute( Language.VERSION_ATTRIBUTE_NAME, event == null ? Consts.APPLICATION_LANGUAGE_VERSION : languageFileVersionTextField.getText()); rootElement.setAttribute( Language.SUBVERSION_ATTRIBUTE_NAME, event == null ? "1" : languageFileSubversionTextField.getText()); rootElement.setAttribute( Language.TRANSLATOR_FIRST_NAME_ATTRIBUTE_NAME, event == null ? "" : translatorFirstNameTextField.getText()); rootElement.setAttribute( Language.TRANSLATOR_LAST_NAME_ATTRIBUTE_NAME, event == null ? "" : translatorLastNameTextField.getText()); final Element dateFormatElement = document.createElement(Language.DATE_FORMAT_TAG_NAME); dateFormatElement.setTextContent( event == null ? Language.DEFAULT_LANGUAGE.defaultDateFormatPattern : (String) dateFormatTextComboBox.getSelectedItem()); rootElement.appendChild(dateFormatElement); final Element timeFormatElement = document.createElement(Language.TIME_FORMAT_TAG_NAME); timeFormatElement.setTextContent( event == null ? Language.DEFAULT_LANGUAGE.defaultTimeFormatPattern : (String) timeFormatTextComboBox.getSelectedItem()); rootElement.appendChild(timeFormatElement); final Element dateTimeFormatElement = document.createElement(Language.DATE_TIME_FORMAT_TAG_NAME); dateTimeFormatElement.setTextContent( event == null ? Language.DEFAULT_LANGUAGE.defaultDateTimeFormatPattern : (String) dateTimeFormatTextComboBox.getSelectedItem()); rootElement.appendChild(dateTimeFormatElement); final Element personNameFormatElement = document.createElement(Language.PERSON_NAME_FORMAT_TAG_NAME); personNameFormatElement.setTextContent( event == null ? (Language.DEFAULT_LANGUAGE.personNameFormatFirstNameFirst ? Language.PERSON_NAME_FORMAT_FIRST_NAME_LAST_NAME : Language.PERSON_NAME_FORMAT_LAST_NAME_FISRT_NAME) : personNameFormatComboBox.getSelectedIndex() == 0 ? Language.PERSON_NAME_FORMAT_FIRST_NAME_LAST_NAME : Language.PERSON_NAME_FORMAT_LAST_NAME_FISRT_NAME); rootElement.appendChild(personNameFormatElement); if (event != null) { // Now add the texts. // First create the text groups // Create groups in the order of their node counts (for example "menu.file" has 2 // nodes) final List<String> textGroupList = new ArrayList<String>(Language.DEFAULT_LANGUAGE.textGroupCommentsMap.keySet()); Collections.sort( textGroupList, new Comparator<String>() { @Override public int compare(final String g1, final String g2) { int c1 = 0; for (int i = g1.length() - 2; i > 0; i--) // Cannot start or end with '.' if (g1.charAt(i) == '.') c1++; int c2 = 0; for (int i = g2.length() - 2; i > 0; i--) // Cannot start or end with '.' if (g2.charAt(i) == '.') c2++; return c1 - c2; } }); final Map<String, Element> textGroupElementMap = new HashMap<String, Element>( textGroupList.size()); // Store the elements mapped from text group keys for (final String textGroup : textGroupList) { // Check if there is a parent group Element parentElement = null; String relativeKey = null; int dotIndex = textGroup.length() - 1; // The last node will be cut off (the parent cannot have the same // key, do not check it) while ((dotIndex = textGroup.lastIndexOf('.', dotIndex)) >= 0) { parentElement = textGroupElementMap.get(textGroup.substring(0, dotIndex)); if (parentElement != null) { relativeKey = textGroup.substring(dotIndex + 1); break; } dotIndex--; } final Element groupElement = document.createElement(Language.TEXT_GROUP_TAG_NAME); groupElement.setAttribute( Language.KEY_ATTRIBUTE_NAME, parentElement == null ? textGroup : relativeKey); (parentElement == null ? rootElement : parentElement).appendChild(groupElement); textGroupElementMap.put(textGroup, groupElement); } // And finally create text elements @SuppressWarnings("unchecked") final Vector<Vector<String>> dataVector = ((DefaultTableModel) textsTable.getModel()).getDataVector(); for (final Vector<String> row : dataVector) { final String translation = row.get(TRANSLATION_COLUMN); if (translation == null || translation.length() == 0) continue; final String key = row.get(TEXT_KEY_COLUMN); // Find "closest" parent text group Element parentElement = null; String relativeKey = null; int dotIndex = key.length() - 1; // The last node will be cut off (do not check the text key itself as // a group key) while ((dotIndex = key.lastIndexOf('.', dotIndex)) >= 0) { parentElement = textGroupElementMap.get(key.substring(0, dotIndex)); if (parentElement != null) { relativeKey = key.substring(dotIndex + 1); break; } dotIndex--; } final Element textElement = document.createElement(Language.TEXT_TAG_NAME); textElement.setAttribute( Language.KEY_ATTRIBUTE_NAME, parentElement == null ? key : relativeKey); textElement.setTextContent(translation); (parentElement == null ? rootElement : parentElement).appendChild(textElement); } } document.appendChild(rootElement); final Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.transform( new DOMSource(document), new StreamResult( new FileOutputStream( Language.getLanguageFile( event == null ? newLanguageNameTextField.getText() : (String) editedLanguageComboBox.getSelectedItem())))); if (event != null) GuiUtils.showInfoDialog( Language.getText("translationTool.changesSavedSuccessfully")); } catch (final Exception e) { e.printStackTrace(); GuiUtils.showErrorDialog(Language.getText("translationTool.failedToSaveTranslation")); } } }; saveButton.addActionListener(saveActionListener); createNewLanguageButton.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { final String newLanguageName = newLanguageNameTextField.getText(); if (newLanguageName.length() == 0) return; if (newLanguageName.indexOf(' ') >= 0) { GuiUtils.showErrorDialog( Language.getText("translationTool.doNotUseSpacesInLanguageName")); return; } if (Language.getLanguageFile(newLanguageName).exists()) { GuiUtils.showErrorDialog( Language.getText("translationTool.languageAlreadyExists", newLanguageName)); return; } saveActionListener.actionPerformed(null); rebuildEditedLanguageComboBoxTask.run(); editedLanguageComboBox.setSelectedItem(newLanguageName); tabbedPane.setSelectedIndex(0); translatorFirstNameTextField.requestFocusInWindow(); } }); maximizeWithMarginAndShow(30, null, editedLanguageComboBox, true); }
/** Creates a new WelcomeFrame and makes it visible. */ public WelcomeFrame() { setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener( new WindowAdapter() { public void windowClosing(final WindowEvent event) { cancel(); }; }); setIconImage(Icons.SC2GEARS.getImage()); final Box box = Box.createVerticalBox(); box.add(Box.createVerticalStrut(5)); box.add(GuiUtils.wrapInPanel(SharedUtils.createAnimatedLogoLabel())); box.add(Box.createVerticalStrut(10)); GuiUtils.changeFontToBold(welcomeLabel); box.add(Box.createVerticalStrut(15)); box.add(GuiUtils.wrapInPanel(welcomeLabel)); box.add(Box.createVerticalStrut(15)); box.add(GuiUtils.wrapInPanel(firstRunLabel)); box.add(GuiUtils.wrapInPanel(chooseLabel)); box.add(Box.createVerticalStrut(15)); box.add(GuiUtils.wrapInPanel(thankYouLabel)); box.add(Box.createVerticalStrut(15)); final JPanel languagePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 1)); languagePanel.add(languageLabel); final JComboBox<String> languagesComboBox = new JComboBox<>(Language.getAvailableLanguages()); languagesComboBox.setMaximumRowCount( languagesComboBox.getModel().getSize()); // Display all languages languagesComboBox.setRenderer( new BaseLabelListCellRenderer<String>() { @Override public Icon getIcon(final String value) { return Icons.getLanguageIcon(value); } }); languagesComboBox.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { final String language = (String) languagesComboBox.getSelectedItem(); Language.loadAndActivateLanguage(language); reassignTexts(); } }); languagePanel.add(languagesComboBox); box.add(languagePanel); final JPanel voicePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 1)); voicePanel.add(voiceLabel); final JComboBox<VoiceDescription> voiceComboBox = new JComboBox<>(Sounds.VOICE_DESCRIPTIONS); voiceComboBox.setMaximumRowCount(15); // Not too many languages, display them all voiceComboBox.setRenderer( new BaseLabelListCellRenderer<VoiceDescription>() { @Override public Icon getIcon(final VoiceDescription value) { return Icons.getLanguageIcon(value.language); } }); voiceComboBox.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { final VoiceDescription voiceDescription = (VoiceDescription) voiceComboBox.getSelectedItem(); Settings.set(Settings.KEY_SETTINGS_VOICE, voiceDescription.name); Sounds.playSoundSample(Sounds.SAMPLE_WELCOME, false); } }); voicePanel.add(voiceComboBox); box.add(voicePanel); int maxWidth = Math.max( languagesComboBox.getPreferredSize().width, voiceComboBox.getPreferredSize().width); maxWidth += 5; languagesComboBox.setPreferredSize( new Dimension(maxWidth, languagesComboBox.getPreferredSize().height)); voiceComboBox.setPreferredSize( new Dimension(maxWidth, voiceComboBox.getPreferredSize().height)); box.add(Box.createVerticalStrut(15)); final JPanel buttonsPanel = new JPanel(); okButton.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { dispose(); Settings.set( Settings.KEY_SETTINGS_LANGUAGE, (String) languagesComboBox.getSelectedItem()); Settings.saveProperties(); synchronized (WelcomeFrame.this) { Sounds.playSoundSample(Sounds.SAMPLE_THANK_YOU, false); WelcomeFrame.this .notify(); // Notify the main thread to continue starting the application } } }); buttonsPanel.add(okButton); cancelButton.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { cancel(); } }); buttonsPanel.add(cancelButton); box.add(buttonsPanel); box.add(Box.createVerticalStrut(15)); final JPanel panel = new JPanel(); panel.add(Box.createHorizontalStrut(15)); panel.add(box); panel.add(Box.createHorizontalStrut(15)); getContentPane().add(panel); setResizable(false); reassignTexts(); setVisible(true); okButton.requestFocusInWindow(); Sounds.playSoundSample(Sounds.SAMPLE_WELCOME, false); }
/** * The Parsing service servlet. * * @author Andras Belicza */ @SuppressWarnings("serial") public class ParsingServlet extends BaseServlet { private static final Logger LOGGER = Logger.getLogger(ParsingServlet.class.getName()); private static final String DATE_TIME_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss"; private static final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat(DATE_TIME_FORMAT_PATTERN); private static final String TEXT_UNKNOWN = Language.getText("general.unknown"); private static String[] ACTION_TYPE_STRINGS; static { loadResources(); } private static class ResponseWrapper extends HttpServletResponseWrapper { private int status = HttpServletResponse.SC_OK; /** * Creates a new ResponseWrapper. * * @param response */ public ResponseWrapper(final HttpServletResponse response) { super(response); } @Override public void setStatus(final int sc) { status = sc; super.setStatus(sc); } @SuppressWarnings("deprecation") // This is due to Tomcat 7 (not applicable to AppEngine) @Override public void setStatus(final int sc, final String sm) { status = sc; super.setStatus(sc, sm); } @Override public void sendError(final int sc) throws IOException { status = sc; super.sendError(sc); } @Override public void sendError(final int sc, final String msg) throws IOException { status = sc; super.sendError(sc, msg); } /** * Tells if error is returned by comparing the {@link #status} to {@link * HttpServletResponse#SC_OK}. * * @return true if error is returned; false otherwise */ public boolean isError() { return status != HttpServletResponse.SC_OK; } } public static void loadResources() { if (ACTION_TYPE_STRINGS != null) return; // Already loaded // Ensure ability codes repository is initialized when it is needed: try { Class.forName(ReplayUtils.class.getName()); final ActionType[] actionTypeValues = ActionType.values(); ACTION_TYPE_STRINGS = new String[actionTypeValues.length]; for (int i = 0; i < ACTION_TYPE_STRINGS.length; i++) ACTION_TYPE_STRINGS[i] = Character.toString(actionTypeValues[i].stringValue.charAt(0)); } catch (final ClassNotFoundException cnfe) { LOGGER.log( Level.SEVERE, "Exception during initialization (processing ReplayUtils class)!", cnfe); } } /* * doPost() is more common in case of this servlet, so doGet() calls doPost(). */ @Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { final long startNanoTime = System.nanoTime(); final String operation = checkProtVerAndGetOperation(PROTOCOL_VERSION_1, request, response); if (operation == null) return; final String apiKey = request.getParameter(PARAM_API_KEY); if (apiKey == null || apiKey.isEmpty()) { LOGGER.warning("Missing API key!"); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing API key!"); return; } long opsCharged = 0; ApiAccount apiAccount = null; PersistenceManager pm = null; ResponseWrapper responseWrapper = null; boolean denied = false; try { pm = PMF.get().getPersistenceManager(); // Check API key final List<ApiAccount> apiAccountList = new JQBuilder<>(pm, ApiAccount.class).filter("apiKey==p1", "String p1").get(apiKey); if (apiAccountList.isEmpty()) { LOGGER.warning("Unauthorized access, invalid API Key: " + apiKey); response.sendError( HttpServletResponse.SC_FORBIDDEN, "Unauthorized access, invalid API Key!"); return; } apiAccount = apiAccountList.get(0); responseWrapper = new ResponseWrapper(response); // Check Ops quota final List<ApiCallStat> totalApiCallStatList = new JQBuilder<>(pm, ApiCallStat.class) .filter("ownerKey==p1 && day==p2", "KEY p1, String p2") .get(apiAccount.getKey(), ApiCallStat.DAY_TOTAL); final long totalUsedOps = totalApiCallStatList.isEmpty() ? 0 : totalApiCallStatList.get(0).getUsedOps(); if (!OPERATION_INFO.equals(operation) && totalUsedOps >= apiAccount.getPaidOps()) { denied = true; LOGGER.warning( "Ops quota have been exceeded, serving denied! (API account: " + apiAccount.getUser().getEmail() + ")"); responseWrapper.sendError( HttpServletResponse.SC_PAYMENT_REQUIRED, "Ops quota have been exceeded, serving denied!"); return; } switch (operation) { case OPERATION_INFO: opsCharged = infoOp(request, responseWrapper, pm, apiAccount); break; case OPERATION_MAP_INFO: opsCharged = mapInfoOp(request, responseWrapper, pm, apiAccount); break; case OPERATION_PARSE_REPLAY: opsCharged = parseRepOp(request, responseWrapper, pm, apiAccount); break; case OPERATION_PROFILE_INFO: opsCharged = profInfoOp(request, responseWrapper, pm, apiAccount); break; default: LOGGER.warning( "Invalid Operation! (API account: " + apiAccount.getUser().getEmail() + ")"); responseWrapper.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Operation!"); return; } // Notification available Ops will be checked in the task servlet, update API call stat task } finally { if (apiAccount != null) TaskServlet.register_updateApiCallStat( apiAccount.getKey(), apiAccount.getPaidOps(), apiAccount.getNotificationAvailOps(), opsCharged, (System.nanoTime() - startNanoTime) / 1000000l, denied, responseWrapper == null ? true : responseWrapper.isError(), operation); if (pm != null) pm.close(); } } /** * XML builder. * * @author Andras Belicza */ private static class XmlBuilder { /** The document we build. */ private final Document document; /** Parent element where new elements are attached to. */ private Element parentElement; /** * Creates a new XmlBuilder. * * @param docVersion document version to set as the {@link ParsingServletApi#XATTR_DOC_VERSION} * value for the {@link ParsingServletApi#XTAG_RESPONSE} root element */ public XmlBuilder(final String docVersion) throws ParserConfigurationException { document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); // Create root element parentElement = document.createElement(XTAG_RESPONSE); parentElement.setAttribute(XATTR_DOC_VERSION, docVersion); document.appendChild(parentElement); } /** * Sets the parent element where new elements are attached to. * * @param parentElement parent element to be set; can be <code>null</code> in which case the * root element will be set * @return the parent element */ public Element setParentElement(final Element parentElement) { return this.parentElement = parentElement == null ? document.getDocumentElement() : parentElement; } /** * Returns the parent element where new elements are attached to. * * @return the parent element where new elements are attached to */ public Element getParentElement() { return parentElement; } /** * Creates and attaches a new element to the parent element. * * @param elementName name of the new element to be created and attached * @return the created and attached new element */ public Element createElement(final String elementName) { return createElement(elementName, null, null); } /** * Creates and attaches a new element to the parent element with a {@link * ParsingServletApi#XATTR_VALUE} attribute having the specified value. * * @param elementName name of the new element to be created and attached * @param valueAttrValue value of {@link ParsingServletApi#XATTR_VALUE} attribute to be set for * the new element * @return the created and attached new element */ public Element createElement(final String elementName, final Object valueAttrValue) { return createElement(elementName, XATTR_VALUE, valueAttrValue); } /** * Creates and attaches a new element to the parent element with an attribute. * * @param elementName name of the new element to be created and attached * @param attrName name of the attribute to be set * @param attrValue value of the attribute to set; can be <code>null</code> in which case no * attribute will be set * @return the created and attached new element */ public Element createElement( final String elementName, final String attrName, final Object attrValue) { final Element element = document.createElement(elementName); if (attrValue != null) element.setAttribute(attrName, attrValue.toString()); parentElement.appendChild(element); return element; } /** * Creates a {@link ParsingServletApi#XTAG_RESULT} tag from the specified result and attaches it * to the parent element . * * @param result result to create an XML tag from */ public void createResultElement(final IResult result) { createElement(XTAG_RESULT, XATTR_CODE, result.getCode()).setTextContent(result.getMessage()); } /** * Creates and attaches a new element to the parent element with a value attribute from a {@link * Date}. Also attaches a {@link ParsingServletApi#XATTR_PATTERN} describing the date-time * pattern used in the value attribute. * * @param result result to create an XML tag from * @param elementName */ public void createDateTimeElement(final String elementName, final Date date) { createElement(elementName, DATE_TIME_FORMAT.format(date)) .setAttribute(XATTR_PATTERN, DATE_TIME_FORMAT_PATTERN); } /** * Prints the document to the specified HTTP servlet response. * * @param response response to print the document to */ public void printDocument(final HttpServletResponse response) throws TransformerFactoryConfigurationError, TransformerException, IOException { response.setContentType("text/xml"); response.setCharacterEncoding("UTF-8"); setNoCache(response); final Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(new DOMSource(document), new StreamResult(response.getOutputStream())); } } /** Info operation. */ private long infoOp( final HttpServletRequest request, final HttpServletResponse response, final PersistenceManager pm, final ApiAccount apiAccount) throws IOException { Integer daysCount = getIntParam(request, PARAM_DAYS_COUNT); LOGGER.fine("API account: " + apiAccount.getUser().getEmail() + ", days count: " + daysCount); if (daysCount != null) { if (daysCount < 0 || daysCount > 14) { LOGGER.warning("Invalid days count, must be between 0 and 14!"); response.sendError( HttpServletResponse.SC_BAD_REQUEST, "Invalid days count, must be between 0 and 14!"); return 0; } } else daysCount = 2; try { // Total + days count (last days count) final List<ApiCallStat> apiCallStatList = new JQBuilder<>(pm, ApiCallStat.class) .filter("ownerKey==p1 && day==p2", "KEY p1") .range(0, daysCount + 1) .desc("day") .get(apiAccount.getKey()); final XmlBuilder xb = new XmlBuilder("1.0"); xb.createResultElement(InfoResult.OK); xb.createElement( XTAG_ENGINE_VER, ReplayFactory .getVersion()); // DO NOT USE ReplayFactory.VERSION because the compiler replaces the // actual ReplayFactory.VERSION and in live environment not the value // from sc2gears-parsing-engine.jar will be used! xb.createDateTimeElement(XTAG_SERVER_TIME, new Date()); xb.createElement(XTAG_PAID_OPS, apiAccount.getPaidOps()); xb.createElement( XTAG_AVAIL_OPS, apiCallStatList.isEmpty() ? apiAccount.getPaidOps() : apiAccount.getPaidOps() - apiCallStatList.get(0).getUsedOps()); final Element callStatsElement = xb.createElement(XTAG_CALL_STATS, XATTR_COUNT, apiCallStatList.size()); callStatsElement.setAttribute(XATTR_PATTERN, ApiCallStat.DAY_PATTERN); for (final ApiCallStat apiCallStat : apiCallStatList) { xb.setParentElement(callStatsElement); xb.setParentElement(xb.createElement(XTAG_CALL_STAT, XATTR_DAY, apiCallStat.getDay())); xb.createElement(XTAG_API_CALLS, apiCallStat.getCalls()); xb.createElement(XTAG_USED_OPS, apiCallStat.getUsedOps()); xb.createElement(XTAG_AVG_EXEC_TIME, apiCallStat.getAvgExecTime()) .setAttribute(XATTR_UNIT, "ms"); xb.createElement(XTAG_DENIED_CALLS, apiCallStat.getDeniedCalls()); xb.createElement(XTAG_ERRORS, apiCallStat.getErrors()); xb.createElement(XTAG_INFO_CALLS, apiCallStat.getInfoCalls()); xb.createElement(XTAG_AVG_INFO_EXEC_TIME, apiCallStat.getAvgInfoExecTime()) .setAttribute(XATTR_UNIT, "ms"); xb.createElement(XTAG_MAP_INFO_CALLS, apiCallStat.getMapInfoCalls()); xb.createElement(XTAG_AVG_MAP_INFO_EXEC_TIME, apiCallStat.getAvgMapInfoExecTime()) .setAttribute(XATTR_UNIT, "ms"); xb.createElement(XTAG_PARSE_REP_CALLS, apiCallStat.getParseRepCalls()); xb.createElement(XTAG_AVG_PARSE_REP_EXEC_TIME, apiCallStat.getAvgParseRepExecTime()) .setAttribute(XATTR_UNIT, "ms"); xb.createElement(XTAG_PROF_INFO_CALLS, apiCallStat.getProfInfoCalls()); xb.createElement(XTAG_AVG_PROF_INFO_EXEC_TIME, apiCallStat.getAvgProfInfoExecTime()) .setAttribute(XATTR_UNIT, "ms"); } xb.printDocument(response); return 0; } catch (final Exception e) { LOGGER.log(Level.SEVERE, "", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return 0; } } /** Get map info operation. */ private long mapInfoOp( final HttpServletRequest request, final HttpServletResponse response, final PersistenceManager pm, final ApiAccount apiAccount) throws IOException { String mapFileName = request.getParameter(PARAM_MAP_FILE_NAME); LOGGER.fine( "API account: " + apiAccount.getUser().getEmail() + ", map file name: " + mapFileName); if (mapFileName == null || mapFileName.isEmpty()) { LOGGER.warning("Missing map file name!"); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing map file name!"); return 0; } if ((mapFileName = ServerUtils.checkMapFileName(mapFileName)) == null) { LOGGER.warning("Invalid map file name: " + mapFileName); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid map file name!"); return 0; } try { final XmlBuilder xb = new XmlBuilder("1.0"); long opsCharged = 0; final List<Map> mapList = new JQBuilder<>(pm, Map.class).filter("fname==p1", "String p1").get(mapFileName); if (mapList.isEmpty()) { // Register a task to process the map TaskServlet.register_processMapTask(mapFileName); xb.createResultElement(MapInfoResult.PROCESSING); } else { final Map map = mapList.get(0); switch (map.getStatus()) { case Map.STATUS_PROCESSING: xb.createResultElement(MapInfoResult.PROCESSING); break; case Map.STATUS_PARSING_ERROR: xb.createResultElement(MapInfoResult.PARSING_ERROR); break; case Map.STATUS_DL_ERROR: xb.createResultElement(MapInfoResult.DOWNLOAD_ERROR); break; case Map.STATUS_READY: { opsCharged = 1; xb.createResultElement(MapInfoResult.OK); Element element = xb.createElement(XTAG_MAP); element.setAttribute(XATTR_NAME, map.getName()); element.setAttribute(XATTR_WIDTH, Integer.toString(map.getMwidth())); element.setAttribute(XATTR_HEIGHT, Integer.toString(map.getMheight())); element = xb.createElement(XTAG_MAP_IMAGE); element.setAttribute(XATTR_FORMAT, "JPEG"); element.setAttribute(XATTR_SIZE, Integer.toString(map.getSize())); element.setAttribute(XATTR_WIDTH, Integer.toString(map.getWidth())); element.setAttribute(XATTR_HEIGHT, Integer.toString(map.getHeight())); element.setTextContent( javax.xml.bind.DatatypeConverter.printBase64Binary(map.getImage().getBytes())); break; } } } xb.printDocument(response); return opsCharged; } catch (final Exception e) { LOGGER.log(Level.SEVERE, "", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return 0; } } /** Parse replay operation. */ private long parseRepOp( final HttpServletRequest request, final HttpServletResponse response, final PersistenceManager pm, final ApiAccount apiAccount) throws IOException { final String fileContent = request.getParameter(PARAM_FILE_CONTENT); final Integer fileLength = getIntParam(request, PARAM_FILE_LENGTH); LOGGER.fine("API account: " + apiAccount.getUser().getEmail() + ", file length: " + fileLength); if (fileContent == null || fileContent.isEmpty() || fileLength == null) { LOGGER.warning("Missing parameters!"); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing parameters!"); return 0; } final byte[] decodedFileContent = ServerUtils.decodeBase64String(fileContent); if (decodedFileContent == null) { LOGGER.warning("Invalid Base64 encoded file content!"); response.sendError( HttpServletResponse.SC_BAD_REQUEST, "Invalid Base64 encoded file content!"); return 0; } if (decodedFileContent.length != fileLength) { LOGGER.warning( "Supplied file length does not match decoded file content length: " + fileLength + " != " + decodedFileContent.length); response.sendError( HttpServletResponse.SC_BAD_REQUEST, "Supplied file length does not match decoded file content length!"); return 0; } try { long opsCharged = 1; final Boolean parseMessagesParam = getBooleanParam(request, PARAM_PARSE_MESSAGES); final Boolean parseActionsParam = getBooleanParam(request, PARAM_PARSE_ACTIONS); // Default or requested values: final boolean parseMessages = parseMessagesParam == null ? true : parseMessagesParam; final boolean parseActions = parseActionsParam == null ? true : parseActionsParam; final Set<ReplayContent> contentToExtractSet = EnumSet.copyOf(ReplayFactory.GENERAL_INFO_CONTENT); if (parseMessages) contentToExtractSet.add(ReplayContent.MESSAGE_EVENTS); if (parseActions) { contentToExtractSet.add(ReplayContent.GAME_EVENTS); opsCharged++; } final Replay replay = ReplayFactory.parseReplay( "attachedFile.SC2Replay", new MpqParser(new ByteArrayMpqDataInput(decodedFileContent)), contentToExtractSet); final XmlBuilder xb = new XmlBuilder("1.0"); xb.createResultElement(replay == null ? ParseRepResult.PARSING_ERROR : ParseRepResult.OK); xb.createElement(XTAG_ENGINE_VER, ReplayFactory.getVersion()); if (replay == null) { LOGGER.fine("Replay parsing error!"); xb.printDocument(response); return opsCharged; } final Element repInfoElement = xb.setParentElement(xb.createElement(XTAG_REP_INFO)); xb.createElement(XTAG_VERSION, replay.version); xb.createElement(XTAG_EXPANSION, replay.details.expansion); final Element gameLengthSecElement = xb.createElement( XTAG_GAME_LENGTH, replay.converterGameSpeed.convertToRealTime(replay.gameLengthSec)); gameLengthSecElement.setAttribute(XATTR_UNIT, "sec"); gameLengthSecElement.setAttribute( XATTR_GAME_TIME_VALUE, Integer.toString(replay.gameLengthSec)); xb.createElement(XTAG_GAME_LENGTH, replay.frames).setAttribute(XATTR_UNIT, "frame"); xb.createElement(XTAG_GAME_TYPE, replay.initData.gameType); if (replay.initData.competitive != null) xb.createElement(XTAG_IS_COMPETITIVE, replay.initData.competitive); xb.createElement(XTAG_GAME_SPEED, replay.initData.gameSpeed); xb.createElement(XTAG_FORMAT, replay.initData.format); xb.createElement(XTAG_GATEWAY, replay.initData.gateway); xb.createElement(XTAG_MAP_FILE_NAME, replay.initData.mapFileName); xb.setParentElement(xb.createElement(XTAG_CLIENTS)); final Player[] players = replay.details.players; final String[] arrangedClientNames = replay.initData.getArrangedClientNames(players); xb.getParentElement().setAttribute(XATTR_COUNT, Integer.toString(arrangedClientNames.length)); for (int i = 0; i < arrangedClientNames.length; i++) xb.createElement(XTAG_CLIENT, arrangedClientNames[i]) .setAttribute(XATTR_INDEX, Integer.toString(i)); xb.setParentElement(repInfoElement); xb.createElement(XTAG_MAP_NAME, replay.details.originalMapName); xb.createDateTimeElement(XTAG_SAVE_TIME, new Date(replay.details.saveTime)); xb.createElement( XTAG_SAVE_TIME_ZONE, String.format(Locale.US, "%+.2f", replay.details.saveTimeZone)); final Element playersElement = xb.createElement(XTAG_PLAYERS); xb.setParentElement(playersElement); playersElement.setAttribute(XATTR_COUNT, Integer.toString(players.length)); for (int i = 0; i < players.length; i++) { final Player player = players[i]; xb.setParentElement(playersElement); xb.setParentElement(xb.createElement(XTAG_PLAYER, XATTR_INDEX, Integer.toString(i))); final Element playerElement = xb.createElement(XTAG_PLAYER_ID, XATTR_NAME, player.playerId.name); playerElement.setAttribute(XATTR_BNET_ID, Integer.toString(player.playerId.battleNetId)); playerElement.setAttribute( XATTR_BNET_SUBID, Integer.toString(player.playerId.battleNetSubId)); playerElement.setAttribute(XATTR_GATEWAY, player.playerId.gateway.toString()); playerElement.setAttribute(XATTR_GW_CODE, player.playerId.gateway.binaryValue); playerElement.setAttribute(XATTR_REGION, player.playerId.getRegion().toString()); playerElement.setAttribute( XATTR_PROFILE_URL, player.playerId.getBattleNetProfileUrl(player.playerId.gateway.defaultLanguage)); xb.createElement( XTAG_TEAM, player.team == Player.TEAM_UNKNOWN ? TEXT_UNKNOWN : player.team); xb.createElement(XTAG_RACE, player.race); xb.createElement(XTAG_FINAL_RACE, player.finalRace); xb.createElement(XTAG_LEAGUE, player.getLeague()); xb.createElement(XTAG_SWARM_LEVELS, player.getSwarmLevels()); final Element colorElement = xb.createElement(XTAG_COLOR, XATTR_NAME, player.playerColor); colorElement.setAttribute(XATTR_RED, Integer.toString(player.argbColor[1])); colorElement.setAttribute(XATTR_GREEN, Integer.toString(player.argbColor[2])); colorElement.setAttribute(XATTR_BLUE, Integer.toString(player.argbColor[3])); xb.createElement(XTAG_TYPE, player.type); xb.createElement(XTAG_DIFFICULTY, player.difficulty); xb.createElement(XTAG_HANDICAP, player.handicap); xb.createElement(XTAG_IS_WINNER, player.isWinner == null ? TEXT_UNKNOWN : player.isWinner); if (parseActions) { xb.createElement(XTAG_ACTIONS_COUNT, player.actionsCount); xb.createElement(XTAG_EFFECTIVE_ACTIONS_COUNT, player.effectiveActionsCount); xb.createElement(XTAG_LAST_ACTION_FRAME, player.lastActionFrame); xb.createElement(XTAG_APM, ReplayUtils.calculatePlayerApm(replay, player)) .setAttribute( XATTR_EXCLUDED_ACTIONS_COUNT, Integer.toString(player.excludedActionsCount)); xb.createElement(XTAG_EAPM, ReplayUtils.calculatePlayerEapm(replay, player)) .setAttribute( XATTR_EXCLUDED_ACTIONS_COUNT, Integer.toString(player.excludedEffectiveActionsCount)); Float fvalue; xb.createElement( XTAG_AVG_SPAWNING_RATIO, (fvalue = player.getAverageSpawningRatio()) == null ? TEXT_UNKNOWN : (int) (fvalue * 100)) .setAttribute(XATTR_UNIT, "%"); xb.createElement( XTAG_AVG_INJECTION_GAP, (fvalue = player.getAverageInjectionGap()) == null ? TEXT_UNKNOWN : ReplayUtils.formatFramesDecimal( fvalue.intValue(), replay.converterGameSpeed)) .setAttribute(XATTR_UNIT, "sec"); } } if (parseMessages) { xb.setParentElement(null); // Root element final Element inGameChatElement = xb.createElement(XTAG_IN_GAME_CHAT, XATTR_COUNT, replay.messageEvents.messages.length); inGameChatElement.setAttribute(XATTR_PATTERN, "HH:mm:ss"); xb.setParentElement(inGameChatElement); int ms = 0; for (final Message message : replay.messageEvents.messages) { ms += message.time; final Element messageElement = xb.createElement( message instanceof Text ? XTAG_TEXT : XTAG_PING, XATTR_CLIENT_INDEX, message.client); messageElement.setAttribute(XATTR_CLIENT, arrangedClientNames[message.client]); messageElement.setAttribute( XATTR_TIME, ReplayUtils.formatMs(ms, replay.converterGameSpeed)); if (message instanceof Text) { messageElement.setAttribute(XATTR_VALUE, ((Text) message).text); final byte opCode = ((Text) message).opCode; messageElement.setAttribute( XATTR_TARGET, opCode == MessageEvents.OP_CODE_CHAT_TO_ALL ? "all" : opCode == MessageEvents.OP_CODE_CHAT_TO_ALLIES ? "allies" : opCode == MessageEvents.OP_CODE_CHAT_TO_OBSERVERS ? "observers" : "unknown"); } else if (message instanceof Blink) { messageElement.setAttribute(XATTR_X, ReplayUtils.formatCoordinate(((Blink) message).x)); messageElement.setAttribute(XATTR_Y, ReplayUtils.formatCoordinate(((Blink) message).y)); } } } if (parseActions) { final Boolean sendActionsSelectParam = getBooleanParam(request, PARAM_SEND_ACTIONS_SELECT); final Boolean sendActionsBuildParam = getBooleanParam(request, PARAM_SEND_ACTIONS_BUILD); final Boolean sendActionsTrainParam = getBooleanParam(request, PARAM_SEND_ACTIONS_TRAIN); final Boolean sendActionsResearchParam = getBooleanParam(request, PARAM_SEND_ACTIONS_RESEARCH); final Boolean sendActionsUpgradeParam = getBooleanParam(request, PARAM_SEND_ACTIONS_UPGRADE); final Boolean sendActionsOtherParam = getBooleanParam(request, PARAM_SEND_ACTIONS_OTHER); final Boolean sendActionsInactionParam = getBooleanParam(request, PARAM_SEND_ACTIONS_INACTION); // Default or requested values: final boolean sendActionsSelect = sendActionsSelectParam == null ? false : sendActionsSelectParam; final boolean sendActionsBuild = sendActionsBuildParam == null ? true : sendActionsBuildParam; final boolean sendActionsTrain = sendActionsTrainParam == null ? true : sendActionsTrainParam; final boolean sendActionsResearch = sendActionsResearchParam == null ? true : sendActionsResearchParam; final boolean sendActionsUpgrade = sendActionsUpgradeParam == null ? true : sendActionsUpgradeParam; final boolean sendActionsOther = sendActionsOtherParam == null ? false : sendActionsOtherParam; final boolean sendActionsInaction = sendActionsInactionParam == null ? false : sendActionsInactionParam; final Set<ActionType> sendActionTypeSet = EnumSet.noneOf(ActionType.class); if (sendActionsSelect) sendActionTypeSet.add(ActionType.SELECT); if (sendActionsBuild) sendActionTypeSet.add(ActionType.BUILD); if (sendActionsTrain) sendActionTypeSet.add(ActionType.TRAIN); if (sendActionsResearch) sendActionTypeSet.add(ActionType.RESEARCH); if (sendActionsUpgrade) sendActionTypeSet.add(ActionType.UPGRADE); if (sendActionsOther) sendActionTypeSet.add(ActionType.OTHER); if (sendActionsInaction) sendActionTypeSet.add(ActionType.INACTION); if (sendActionsSelect || sendActionsOther) opsCharged++; if (sendActionsInaction) opsCharged++; xb.setParentElement(null); // Root element final Element actionsElement = xb.createElement( XTAG_ACTIONS, XATTR_ALL_ACTIONS_COUNT, replay.gameEvents.actions.length); actionsElement.setAttribute( XATTR_ERROR_PARSING, Boolean.toString(replay.gameEvents.errorParsing)); xb.setParentElement(actionsElement); int count = 0; // Sent actions count if (!sendActionTypeSet.isEmpty()) { final StringBuilder actionStringBuilder = new StringBuilder(); for (final Action action : replay.gameEvents.actions) if (sendActionTypeSet.contains(action.type)) { count++; final Element actionElement = xb.createElement(XTAG_ACTION_, XATTR_PLAYER_, action.player); actionElement.setAttribute(XATTR_TYPE_, ACTION_TYPE_STRINGS[action.type.ordinal()]); actionElement.setAttribute(XATTR_FRAME_, Integer.toString(action.frame)); actionStringBuilder.setLength(0); action.customToString(actionStringBuilder); actionElement.setAttribute(XATTR_STRING_, actionStringBuilder.toString()); } } actionsElement.setAttribute(XATTR_COUNT, Integer.toString(count)); } xb.printDocument(response); return opsCharged; } catch (final InvalidMpqArchiveException imae) { LOGGER.log(Level.WARNING, "", imae); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid SC2Replay file!"); return 0; } catch (final Exception e) { LOGGER.log(Level.SEVERE, "", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return 0; } } /** Profile info operation. */ private long profInfoOp( final HttpServletRequest request, final HttpServletResponse response, final PersistenceManager pm, final ApiAccount apiAccount) throws IOException { LOGGER.fine("API account: " + apiAccount.getUser().getEmail()); final Integer bnetId; final Integer bnetSubId; final String gatewayString; final String playerName; final Gateway gateway; final String bnetProfileUrlParam = request.getParameter(PARAM_BNET_PROFILE_URL); if (bnetProfileUrlParam == null || bnetProfileUrlParam.isEmpty()) { // Player id is provided explicitly bnetId = getIntParam(request, PARAM_BNET_ID); bnetSubId = getIntParam(request, PARAM_BNET_SUBID); gatewayString = request.getParameter(PARAM_GATEWAY); playerName = request.getParameter(PARAM_PLAYER_NAME); if (bnetId == null || bnetSubId == null || gatewayString == null || gatewayString.isEmpty() || playerName == null || playerName.isEmpty()) { LOGGER.warning("Missing parameters!"); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing parameters!"); return 0; } gateway = Gateway.fromBinaryValue(gatewayString); if (gateway == Gateway.UNKNOWN || gateway == Gateway.PUBLIC_TEST) { LOGGER.warning("Invalid gateway parameter: " + gatewayString); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid gateway parameter!"); return 0; } } else { // Player id is provided through his/her Battle.net profile URL try { Gateway foundGateway = null; for (final Gateway gateway_ : EnumCache.GATEWAYS) if (bnetProfileUrlParam.startsWith(gateway_.bnetUrl)) { foundGateway = gateway_; break; } if (foundGateway == null || foundGateway == Gateway.UNKNOWN || foundGateway == Gateway.PUBLIC_TEST) throw new Exception("No matching gateway!"); gateway = foundGateway; final String[] urlParts = bnetProfileUrlParam.split("/"); if (urlParts.length < 3) throw new Exception("Not enough parts in URL!"); playerName = URLDecoder.decode(urlParts[urlParts.length - 1], "UTF-8"); bnetSubId = Integer.valueOf(urlParts[urlParts.length - 2]); bnetId = Integer.valueOf(urlParts[urlParts.length - 3]); } catch (final Exception e) { LOGGER.log(Level.SEVERE, "Invalid Battle.net profile URL: " + bnetProfileUrlParam, e); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Battle.net profile URL!"); return 0; } } long opsCharged = 1; final Boolean retrieveExtInfoParam = getBooleanParam(request, PARAM_RETRIEVE_EXT_INFO); // Default or requested values: final boolean retrieveExtInfo = retrieveExtInfoParam == null ? false : retrieveExtInfoParam; try { final PlayerId playerId = new PlayerId(); playerId.battleNetId = bnetId; playerId.battleNetSubId = bnetSubId; playerId.gateway = gateway; playerId.name = playerName; final String bnetProfileUrl = playerId.getBattleNetProfileUrl(BnetLanguage.ENGLISH); LOGGER.fine("Bnet profile URL: " + bnetProfileUrl); final URLFetchService urlFetchService = URLFetchServiceFactory.getURLFetchService(); // Default deadline: 5 seconds... increase it! final HTTPRequest profileRequest = new HTTPRequest(new URL(bnetProfileUrl)); profileRequest .getFetchOptions() .setDeadline(50.0); // 50-sec deadline (leaving 10 seconds to process... final Future<HTTPResponse> profileFetchAsync = urlFetchService.fetchAsync(profileRequest); final Future<HTTPResponse> extProfileFetchAsync; if (retrieveExtInfo) { opsCharged++; // Start retrieving extended profile info in parallel final HTTPRequest extProfileRequest = new HTTPRequest(new URL(bnetProfileUrl + "ladder/leagues")); extProfileRequest .getFetchOptions() .setDeadline(50.0); // 50-sec deadline (leaving 10 seconds to process... extProfileFetchAsync = urlFetchService.fetchAsync(extProfileRequest); } else extProfileFetchAsync = null; final XmlBuilder xb = new XmlBuilder("1.1"); HTTPResponse profileResponse = null; Profile profile = null; Element profInfoElement = null; try { profileResponse = profileFetchAsync.get(); switch (profileResponse.getResponseCode()) { case HttpServletResponse.SC_OK: { final byte[] content = profileResponse.getContent(); if (content.length == 0) throw new Exception("Content length = 0!"); profile = BnetUtils.retrieveProfile(null, new ByteArrayInputStream(content)); if (profile != null) { LOGGER.fine("Parse OK"); xb.createResultElement(ProfInfoResult.OK); opsCharged += 2; profInfoElement = xb.setParentElement(xb.createElement(XTAG_PROFILE_INFO)); // Re-include player id final Element playerElement = xb.createElement(XTAG_PLAYER_ID, XATTR_NAME, playerName); playerElement.setAttribute(XATTR_BNET_ID, bnetId.toString()); playerElement.setAttribute(XATTR_BNET_SUBID, bnetSubId.toString()); playerElement.setAttribute(XATTR_GATEWAY, gateway.toString()); playerElement.setAttribute(XATTR_GW_CODE, gateway.binaryValue); playerElement.setAttribute(XATTR_REGION, playerId.getRegion().toString()); playerElement.setAttribute(XATTR_PROFILE_URL, bnetProfileUrl); final Element portraitElement = xb.createElement(XTAG_PORTRAIT, XATTR_GROUP, profile.portraitGroup); portraitElement.setAttribute(XATTR_ROW, Integer.toString(profile.portraitRow)); portraitElement.setAttribute( XATTR_COLUMN, Integer.toString(profile.portraitColumn)); xb.createElement(XTAG_ACHIEVEMENT_POINTS, profile.achievementPoints); xb.createElement(XTAG_TOTAL_CAREER_GAMES, profile.totalCareerGames); xb.createElement(XTAG_GAMES_THIS_SEASON, profile.gamesThisSeason); xb.createElement(XTAG_TERRAN_WINS, profile.terranWins); xb.createElement(XTAG_ZERG_WINS, profile.zergWins); xb.createElement(XTAG_PROTOSS_WINS, profile.protossWins); final Element highestSoloFlElement = xb.createElement(XTAG_HIGHEST_SOLO_FL, profile.highestSoloFinishLeague); if (profile.highestSoloFinishTimes > 0) highestSoloFlElement.setAttribute( XATTR_TIMES_ACHIEVED, Integer.toString(profile.highestSoloFinishTimes)); final Element highestTeamFlElement = xb.createElement(XTAG_HIGHEST_TEAM_FL, profile.highestTeamFinishLeague); if (profile.highestTeamFinishTimes > 0) highestTeamFlElement.setAttribute( XATTR_TIMES_ACHIEVED, Integer.toString(profile.highestTeamFinishTimes)); break; } else { LOGGER.fine("Parse error!"); xb.createResultElement(ProfInfoResult.PARSING_ERROR); // Parse fails } } case HttpServletResponse.SC_NOT_FOUND: LOGGER.fine("Invalid player!"); xb.createResultElement(ProfInfoResult.INVALID_PLAYER); break; default: // Treat other response HTTP status codes as BNET_ERROR throw new Exception("Response code: " + profileResponse.getResponseCode()); } } catch (final Exception e) { LOGGER.log(Level.SEVERE, "", e); xb.createResultElement(ProfInfoResult.BNET_ERROR); } finally { if (retrieveExtInfo && profile == null) extProfileFetchAsync.cancel(true); } if (retrieveExtInfo && profile != null) { try { profileResponse = extProfileFetchAsync.get(); final byte[] content; if (profileResponse.getResponseCode() == HttpServletResponse.SC_OK && (content = profileResponse.getContent()).length > 0) { profile = BnetUtils.retrieveExtProfile(null, new ByteArrayInputStream(content), profile); if (profile != null) { LOGGER.fine("Parse extended OK"); opsCharged += 2; xb.setParentElement(profInfoElement); final Element allRankGroupsElement = xb.setParentElement(xb.createElement(XTAG_ALL_RANK_GROUPS)); int allRankGroupsCount = 0; for (int bracket = 0; bracket < profile.allRankss.length; bracket++) { final TeamRank[] allRanks = profile.allRankss[bracket]; if (allRanks != null && allRanks.length > 0) { allRankGroupsCount++; xb.setParentElement(allRankGroupsElement); final Element allRankGroupElement = xb.createElement(XTAG_ALL_RANK_GROUP, XATTR_COUNT, allRanks.length); allRankGroupElement.setAttribute( XATTR_FORMAT, (bracket + 1) + "v" + (bracket + 1)); for (int i = 0; i < allRanks.length; i++) { xb.setParentElement(allRankGroupElement); final Element teamRankElement = xb.setParentElement( xb.createElement( XTAG_TEAM_RANK, XATTR_LEAGUE, allRanks[i].league.stringValue)); teamRankElement.setAttribute( XATTR_DIVISION_RANK, Integer.toString(allRanks[i].divisionRank)); // Team members xb.setParentElement( xb.createElement( XTAG_TEAM_MEMBERS, XATTR_COUNT, allRanks[i].teamMembers.length)); for (final String memberName : allRanks[i].teamMembers) xb.createElement(XTAG_TEAM_MEMBER, XATTR_NAME, memberName); } } } allRankGroupsElement.setAttribute(XATTR_COUNT, Integer.toString(allRankGroupsCount)); } else LOGGER.fine("Parse extended error!"); } } catch (final Exception e) { LOGGER.log( Level.SEVERE, "Failed to get extended profile info, we return the basic profile info silently.", e); // Failed to get extended profile info, we return the basic profile info silently } } xb.printDocument(response); return opsCharged; } catch (final Exception e) { LOGGER.log(Level.SEVERE, "", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return 0; } } }